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
path: root/src/Http
diff options
context:
space:
mode:
authorPranav K <prkrishn@hotmail.com>2021-11-06 03:52:08 +0300
committerGitHub <noreply@github.com>2021-11-06 03:52:08 +0300
commita450cb69b5e4549f5515cdb057a68771f56cefd7 (patch)
treeb31cc4cd3257b8aeec14a5481121dac6964195a6 /src/Http
parentb9fcd82fe9d0883e9828864a8e8b2231f469254f (diff)
Use file scoped namespaces (#38076)
* Use file scoped namespaces
Diffstat (limited to 'src/Http')
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticateResult.cs219
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs423
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs157
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs381
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs81
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs75
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs103
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationToken.cs25
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticateResultFeature.cs19
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs25
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs51
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs23
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs30
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs160
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationService.cs93
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs23
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs22
-rw-r--r--src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs27
-rw-r--r--src/Http/Authentication.Abstractions/src/TokenExtensions.cs265
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs74
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationFeature.cs25
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs83
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs329
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationService.cs489
-rw-r--r--src/Http/Authentication.Core/src/NoopClaimsTransformation.cs23
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs661
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs343
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationServiceTests.cs699
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationTicketTests.cs67
-rw-r--r--src/Http/Authentication.Core/test/TokenExtensionTests.cs303
-rw-r--r--src/Http/Headers/src/BaseHeaderParser.cs97
-rw-r--r--src/Http/Headers/src/CacheControlHeaderValue.cs1377
-rw-r--r--src/Http/Headers/src/ContentDispositionHeaderValue.cs1245
-rw-r--r--src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs57
-rw-r--r--src/Http/Headers/src/ContentRangeHeaderValue.cs679
-rw-r--r--src/Http/Headers/src/CookieHeaderParser.cs39
-rw-r--r--src/Http/Headers/src/CookieHeaderValue.cs325
-rw-r--r--src/Http/Headers/src/EntityTagHeaderValue.cs439
-rw-r--r--src/Http/Headers/src/GenericHeaderParser.cs33
-rw-r--r--src/Http/Headers/src/HeaderNames.cs403
-rw-r--r--src/Http/Headers/src/HeaderQuality.cs25
-rw-r--r--src/Http/Headers/src/HeaderUtilities.cs1093
-rw-r--r--src/Http/Headers/src/HttpHeaderParser.cs239
-rw-r--r--src/Http/Headers/src/MediaTypeHeaderValue.cs1289
-rw-r--r--src/Http/Headers/src/MediaTypeHeaderValueComparer.cs211
-rw-r--r--src/Http/Headers/src/NameValueHeaderValue.cs739
-rw-r--r--src/Http/Headers/src/ObjectCollection.cs109
-rw-r--r--src/Http/Headers/src/RangeConditionHeaderValue.cs293
-rw-r--r--src/Http/Headers/src/RangeHeaderValue.cs287
-rw-r--r--src/Http/Headers/src/RangeItemHeaderValue.cs345
-rw-r--r--src/Http/Headers/src/SameSiteMode.cs33
-rw-r--r--src/Http/Headers/src/SetCookieHeaderValue.cs1135
-rw-r--r--src/Http/Headers/src/StringWithQualityHeaderValue.cs407
-rw-r--r--src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs107
-rw-r--r--src/Http/Headers/test/CacheControlHeaderValueTest.cs1013
-rw-r--r--src/Http/Headers/test/ContentDispositionHeaderValueTest.cs1041
-rw-r--r--src/Http/Headers/test/ContentRangeHeaderValueTest.cs517
-rw-r--r--src/Http/Headers/test/CookieHeaderValueTest.cs467
-rw-r--r--src/Http/Headers/test/DateParserTest.cs89
-rw-r--r--src/Http/Headers/test/EntityTagHeaderValueTest.cs591
-rw-r--r--src/Http/Headers/test/HeaderUtilitiesTest.cs483
-rw-r--r--src/Http/Headers/test/MediaTypeHeaderValueComparerTests.cs31
-rw-r--r--src/Http/Headers/test/MediaTypeHeaderValueTest.cs1561
-rw-r--r--src/Http/Headers/test/NameValueHeaderValueTest.cs921
-rw-r--r--src/Http/Headers/test/RangeConditionHeaderValueTest.cs323
-rw-r--r--src/Http/Headers/test/RangeHeaderValueTest.cs339
-rw-r--r--src/Http/Headers/test/RangeItemHeaderValueTest.cs255
-rw-r--r--src/Http/Headers/test/SetCookieHeaderValueTest.cs735
-rw-r--r--src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs31
-rw-r--r--src/Http/Headers/test/StringWithQualityHeaderValueTest.cs535
-rw-r--r--src/Http/Http.Abstractions/perf/Microbenchmarks/GetHeaderSplitBenchmark.cs83
-rw-r--r--src/Http/Http.Abstractions/perf/Microbenchmarks/PathStringBenchmark.cs43
-rw-r--r--src/Http/Http.Abstractions/src/BadHttpRequestException.cs95
-rw-r--r--src/Http/Http.Abstractions/src/ConnectionInfo.cs91
-rw-r--r--src/Http/Http.Abstractions/src/CookieBuilder.cs179
-rw-r--r--src/Http/Http.Abstractions/src/CookieSecurePolicy.cs49
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs43
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs89
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.cs213
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/IEndpointConventionBuilder.cs25
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs119
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs117
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapOptions.cs35
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs83
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs89
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs47
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs91
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs75
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs37
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs77
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs337
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs47
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs103
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs91
-rw-r--r--src/Http/Http.Abstractions/src/FragmentString.cs265
-rw-r--r--src/Http/Http.Abstractions/src/HostString.cs599
-rw-r--r--src/Http/Http.Abstractions/src/HttpContext.cs107
-rw-r--r--src/Http/Http.Abstractions/src/HttpMethods.cs353
-rw-r--r--src/Http/Http.Abstractions/src/HttpProtocol.cs221
-rw-r--r--src/Http/Http.Abstractions/src/HttpRequest.cs245
-rw-r--r--src/Http/Http.Abstractions/src/HttpResponse.cs283
-rw-r--r--src/Http/Http.Abstractions/src/IApplicationBuilder.cs77
-rw-r--r--src/Http/Http.Abstractions/src/ICorsMetadata.cs13
-rw-r--r--src/Http/Http.Abstractions/src/IHttpContextAccessor.cs25
-rw-r--r--src/Http/Http.Abstractions/src/IHttpContextFactory.cs31
-rw-r--r--src/Http/Http.Abstractions/src/IMiddleware.cs23
-rw-r--r--src/Http/Http.Abstractions/src/IMiddlewareFactory.cs31
-rw-r--r--src/Http/Http.Abstractions/src/IResult.cs21
-rw-r--r--src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs87
-rw-r--r--src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs486
-rw-r--r--src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs23
-rw-r--r--src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs233
-rw-r--r--src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs87
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IAcceptsMetadata.cs35
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IFromBodyMetadata.cs17
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IFromHeaderMetadata.cs17
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IFromQueryMetadata.cs17
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IFromRouteMetadata.cs17
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IFromServiceMetadata.cs13
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/IProducesResponseTypeMetadata.cs35
-rw-r--r--src/Http/Http.Abstractions/src/Metadata/ITagsMetadata.cs17
-rw-r--r--src/Http/Http.Abstractions/src/PathString.cs785
-rw-r--r--src/Http/Http.Abstractions/src/QueryString.cs459
-rw-r--r--src/Http/Http.Abstractions/src/RequestDelegate.cs17
-rw-r--r--src/Http/Http.Abstractions/src/RequestDelegateResult.cs40
-rw-r--r--src/Http/Http.Abstractions/src/Routing/Endpoint.cs79
-rw-r--r--src/Http/Http.Abstractions/src/Routing/EndpointHttpContextExtensions.cs89
-rw-r--r--src/Http/Http.Abstractions/src/Routing/EndpointMetadataCollection.cs311
-rw-r--r--src/Http/Http.Abstractions/src/Routing/IEndpointFeature.cs21
-rw-r--r--src/Http/Http.Abstractions/src/Routing/IRouteValuesFeature.cs21
-rw-r--r--src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs1127
-rw-r--r--src/Http/Http.Abstractions/src/StatusCodes.cs667
-rw-r--r--src/Http/Http.Abstractions/src/WebSocketManager.cs65
-rw-r--r--src/Http/Http.Abstractions/test/CookieBuilderTests.cs83
-rw-r--r--src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs249
-rw-r--r--src/Http/Http.Abstractions/test/EndpointMetadataCollectionTests.cs91
-rw-r--r--src/Http/Http.Abstractions/test/FragmentStringTests.cs55
-rw-r--r--src/Http/Http.Abstractions/test/HostStringTest.cs325
-rw-r--r--src/Http/Http.Abstractions/test/HttpMethodslTests.cs43
-rw-r--r--src/Http/Http.Abstractions/test/HttpProtocolTests.cs213
-rw-r--r--src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs117
-rw-r--r--src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs463
-rw-r--r--src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs193
-rw-r--r--src/Http/Http.Abstractions/test/PathStringTests.cs579
-rw-r--r--src/Http/Http.Abstractions/test/QueryStringTests.cs251
-rw-r--r--src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs3249
-rw-r--r--src/Http/Http.Abstractions/test/UseExtensionsTests.cs119
-rw-r--r--src/Http/Http.Abstractions/test/UseMiddlewareTest.cs601
-rw-r--r--src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs271
-rw-r--r--src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs235
-rw-r--r--src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs435
-rw-r--r--src/Http/Http.Extensions/src/HttpContextServerVariableExtensions.cs41
-rw-r--r--src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs343
-rw-r--r--src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs35
-rw-r--r--src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs337
-rw-r--r--src/Http/Http.Extensions/src/HttpValidationProblemDetails.cs57
-rw-r--r--src/Http/Http.Extensions/src/JsonConstants.cs11
-rw-r--r--src/Http/Http.Extensions/src/JsonOptions.cs39
-rw-r--r--src/Http/Http.Extensions/src/ProblemDetails.cs97
-rw-r--r--src/Http/Http.Extensions/src/QueryBuilder.cs179
-rw-r--r--src/Http/Http.Extensions/src/RequestDelegateFactory.cs2175
-rw-r--r--src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs43
-rw-r--r--src/Http/Http.Extensions/src/RequestHeaders.cs683
-rw-r--r--src/Http/Http.Extensions/src/ResponseExtensions.cs79
-rw-r--r--src/Http/Http.Extensions/src/ResponseHeaders.cs443
-rw-r--r--src/Http/Http.Extensions/src/SendFileResponseExtensions.cs239
-rw-r--r--src/Http/Http.Extensions/src/SessionExtensions.cs117
-rw-r--r--src/Http/Http.Extensions/src/StreamCopyOperation.cs49
-rw-r--r--src/Http/Http.Extensions/src/TagsAttribute.cs43
-rw-r--r--src/Http/Http.Extensions/src/UriHelper.cs453
-rw-r--r--src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs283
-rw-r--r--src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs39
-rw-r--r--src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs391
-rw-r--r--src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs779
-rw-r--r--src/Http/Http.Extensions/test/HttpValidationProblemDetailsJsonConverterTest.cs237
-rw-r--r--src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs1403
-rw-r--r--src/Http/Http.Extensions/test/ProblemDetailsJsonConverterTest.cs295
-rw-r--r--src/Http/Http.Extensions/test/QueryBuilderTests.cs139
-rw-r--r--src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs4609
-rw-r--r--src/Http/Http.Extensions/test/ResponseExtensionTests.cs99
-rw-r--r--src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs213
-rw-r--r--src/Http/Http.Extensions/test/TestStream.cs91
-rw-r--r--src/Http/Http.Extensions/test/UriHelperTests.cs377
-rw-r--r--src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs17
-rw-r--r--src/Http/Http.Features/src/CookieOptions.cs101
-rw-r--r--src/Http/Http.Features/src/HttpsCompressionMode.cs37
-rw-r--r--src/Http/Http.Features/src/IBadRequestExceptionFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IFormCollection.cs157
-rw-r--r--src/Http/Http.Features/src/IFormFeature.cs67
-rw-r--r--src/Http/Http.Features/src/IFormFile.cs105
-rw-r--r--src/Http/Http.Features/src/IFormFileCollection.cs61
-rw-r--r--src/Http/Http.Features/src/IHeaderDictionary.Keyed.cs363
-rw-r--r--src/Http/Http.Features/src/IHeaderDictionary.cs29
-rw-r--r--src/Http/Http.Features/src/IHttpBodyControlFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IHttpConnectionFeature.cs49
-rw-r--r--src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs43
-rw-r--r--src/Http/Http.Features/src/IHttpRequestBodyDetectionFeature.cs43
-rw-r--r--src/Http/Http.Features/src/IHttpRequestFeature.cs149
-rw-r--r--src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs19
-rw-r--r--src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs31
-rw-r--r--src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs31
-rw-r--r--src/Http/Http.Features/src/IHttpResetFeature.cs19
-rw-r--r--src/Http/Http.Features/src/IHttpResponseBodyFeature.cs81
-rw-r--r--src/Http/Http.Features/src/IHttpResponseFeature.cs97
-rw-r--r--src/Http/Http.Features/src/IHttpResponseTrailersFeature.cs25
-rw-r--r--src/Http/Http.Features/src/IHttpUpgradeFeature.cs31
-rw-r--r--src/Http/Http.Features/src/IHttpWebSocketFeature.cs31
-rw-r--r--src/Http/Http.Features/src/IHttpsCompressionFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IItemsFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IQueryCollection.cs147
-rw-r--r--src/Http/Http.Features/src/IQueryFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IRequestBodyPipeFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IRequestCookieCollection.cs147
-rw-r--r--src/Http/Http.Features/src/IRequestCookiesFeature.cs17
-rw-r--r--src/Http/Http.Features/src/IResponseCookies.cs83
-rw-r--r--src/Http/Http.Features/src/IResponseCookiesFeature.cs19
-rw-r--r--src/Http/Http.Features/src/IServerVariablesFeature.cs21
-rw-r--r--src/Http/Http.Features/src/IServiceProvidersFeature.cs17
-rw-r--r--src/Http/Http.Features/src/ISession.cs105
-rw-r--r--src/Http/Http.Features/src/ISessionFeature.cs19
-rw-r--r--src/Http/Http.Features/src/ITlsConnectionFeature.cs25
-rw-r--r--src/Http/Http.Features/src/ITlsTokenBindingFeature.cs53
-rw-r--r--src/Http/Http.Features/src/ITrackingConsentFeature.cs63
-rw-r--r--src/Http/Http.Features/src/SameSiteMode.cs33
-rw-r--r--src/Http/Http.Features/src/WebSocketAcceptContext.cs107
-rw-r--r--src/Http/Http.Results/src/AcceptedAtRouteResult.cs101
-rw-r--r--src/Http/Http.Results/src/AcceptedResult.cs97
-rw-r--r--src/Http/Http.Results/src/BadRequestObjectResult.cs11
-rw-r--r--src/Http/Http.Results/src/ChallengeResult.cs169
-rw-r--r--src/Http/Http.Results/src/ConflictObjectResult.cs11
-rw-r--r--src/Http/Http.Results/src/ContentResult.cs101
-rw-r--r--src/Http/Http.Results/src/CreatedAtRouteResult.cs101
-rw-r--r--src/Http/Http.Results/src/CreatedResult.cs81
-rw-r--r--src/Http/Http.Results/src/FileContentResult.cs105
-rw-r--r--src/Http/Http.Results/src/FileResult.cs123
-rw-r--r--src/Http/Http.Results/src/FileStreamResult.cs123
-rw-r--r--src/Http/Http.Results/src/ForbidResult.cs181
-rw-r--r--src/Http/Http.Results/src/IResultExtensions.cs15
-rw-r--r--src/Http/Http.Results/src/JsonResult.cs107
-rw-r--r--src/Http/Http.Results/src/LocalRedirectResult.cs163
-rw-r--r--src/Http/Http.Results/src/NoContentResult.cs9
-rw-r--r--src/Http/Http.Results/src/NotFoundObjectResult.cs11
-rw-r--r--src/Http/Http.Results/src/ObjectResult.cs179
-rw-r--r--src/Http/Http.Results/src/OkObjectResult.cs11
-rw-r--r--src/Http/Http.Results/src/PhysicalFileResult.cs187
-rw-r--r--src/Http/Http.Results/src/RedirectResult.cs117
-rw-r--r--src/Http/Http.Results/src/RedirectToRouteResult.cs331
-rw-r--r--src/Http/Http.Results/src/ResultExtensions.cs15
-rw-r--r--src/Http/Http.Results/src/Results.cs1123
-rw-r--r--src/Http/Http.Results/src/SignInResult.cs141
-rw-r--r--src/Http/Http.Results/src/SignOutResult.cs183
-rw-r--r--src/Http/Http.Results/src/StatusCodeResult.cs79
-rw-r--r--src/Http/Http.Results/src/UnauthorizedResult.cs9
-rw-r--r--src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs11
-rw-r--r--src/Http/Http.Results/src/VirtualFileResult.cs171
-rw-r--r--src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs141
-rw-r--r--src/Http/Http.Results/test/AcceptedResultTests.cs93
-rw-r--r--src/Http/Http.Results/test/BadRequestObjectResultTests.cs23
-rw-r--r--src/Http/Http.Results/test/ChallengeResultTest.cs93
-rw-r--r--src/Http/Http.Results/test/ConflictObjectResultTest.cs23
-rw-r--r--src/Http/Http.Results/test/ContentResultTest.cs125
-rw-r--r--src/Http/Http.Results/test/CreatedAtRouteResultTests.cs115
-rw-r--r--src/Http/Http.Results/test/CreatedResultTest.cs105
-rw-r--r--src/Http/Http.Results/test/FileContentResultTest.cs39
-rw-r--r--src/Http/Http.Results/test/FileStreamResultTest.cs121
-rw-r--r--src/Http/Http.Results/test/ForbidResultTest.cs215
-rw-r--r--src/Http/Http.Results/test/LocalRedirectResultTest.cs247
-rw-r--r--src/Http/Http.Results/test/NotFoundObjectResultTest.cs87
-rw-r--r--src/Http/Http.Results/test/ObjectResultTests.cs295
-rw-r--r--src/Http/Http.Results/test/OkObjectResultTest.cs53
-rw-r--r--src/Http/Http.Results/test/PhysicalFileResultTest.cs49
-rw-r--r--src/Http/Http.Results/test/RedirectResultTest.cs37
-rw-r--r--src/Http/Http.Results/test/RedirectToRouteResultTest.cs185
-rw-r--r--src/Http/Http.Results/test/SignInResultTest.cs135
-rw-r--r--src/Http/Http.Results/test/SignOutResultTest.cs137
-rw-r--r--src/Http/Http.Results/test/StatusCodeResultTests.cs51
-rw-r--r--src/Http/Http.Results/test/TestLinkGenerator.cs33
-rw-r--r--src/Http/Http.Results/test/UnauthorizedResultTests.cs19
-rw-r--r--src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs23
-rw-r--r--src/Http/Http.Results/test/VirtualFileResultTest.cs21
-rw-r--r--src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs489
-rw-r--r--src/Http/Http/perf/Microbenchmarks/HeaderUtilitiesBenchmark.cs23
-rw-r--r--src/Http/Http/perf/Microbenchmarks/QueryCollectionBenchmarks.cs145
-rw-r--r--src/Http/Http/perf/Microbenchmarks/RequestCookieCollectionBenchmarks.cs27
-rw-r--r--src/Http/Http/perf/Microbenchmarks/RouteValueDictionaryBenchmark.cs459
-rw-r--r--src/Http/Http/src/BindingAddress.cs353
-rw-r--r--src/Http/Http/src/Builder/ApplicationBuilder.cs213
-rw-r--r--src/Http/Http/src/DefaultHttpContext.cs393
-rw-r--r--src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs153
-rw-r--r--src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.cs17
-rw-r--r--src/Http/Http/src/Features/DefaultConnectionLifetimeNotificationFeature.cs43
-rw-r--r--src/Http/Http/src/Features/DefaultSessionFeature.cs19
-rw-r--r--src/Http/Http/src/Features/FormFeature.cs481
-rw-r--r--src/Http/Http/src/Features/FormOptions.cs201
-rw-r--r--src/Http/Http/src/Features/HttpConnectionFeature.cs33
-rw-r--r--src/Http/Http/src/Features/HttpRequestFeature.cs89
-rw-r--r--src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs89
-rw-r--r--src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs23
-rw-r--r--src/Http/Http/src/Features/HttpResponseFeature.cs73
-rw-r--r--src/Http/Http/src/Features/IHttpActivityFeature.cs17
-rw-r--r--src/Http/Http/src/Features/ItemsFeature.cs25
-rw-r--r--src/Http/Http/src/Features/QueryFeature.cs323
-rw-r--r--src/Http/Http/src/Features/RequestBodyPipeFeature.cs65
-rw-r--r--src/Http/Http/src/Features/RequestCookiesFeature.cs135
-rw-r--r--src/Http/Http/src/Features/RequestServicesFeature.cs131
-rw-r--r--src/Http/Http/src/Features/ResponseCookiesFeature.cs77
-rw-r--r--src/Http/Http/src/Features/RouteValuesFeature.cs37
-rw-r--r--src/Http/Http/src/Features/ServiceProvidersFeature.cs17
-rw-r--r--src/Http/Http/src/Features/TlsConnectionFeature.cs25
-rw-r--r--src/Http/Http/src/FormCollection.cs327
-rw-r--r--src/Http/Http/src/FormFile.cs177
-rw-r--r--src/Http/Http/src/FormFileCollection.cs55
-rw-r--r--src/Http/Http/src/HeaderDictionary.cs637
-rw-r--r--src/Http/Http/src/HttpContextAccessor.cs57
-rw-r--r--src/Http/Http/src/HttpServiceCollectionExtensions.cs31
-rw-r--r--src/Http/Http/src/Internal/BufferingHelper.cs69
-rw-r--r--src/Http/Http/src/Internal/DefaultConnectionInfo.cs183
-rw-r--r--src/Http/Http/src/Internal/DefaultHttpRequest.cs353
-rw-r--r--src/Http/Http/src/Internal/DefaultHttpResponse.cs245
-rw-r--r--src/Http/Http/src/Internal/DefaultWebSocketManager.cs109
-rw-r--r--src/Http/Http/src/Internal/ItemsDictionary.cs213
-rw-r--r--src/Http/Http/src/Internal/ReferenceReadStream.cs233
-rw-r--r--src/Http/Http/src/Internal/RequestCookieCollection.cs307
-rw-r--r--src/Http/Http/src/Internal/ResponseCookies.cs325
-rw-r--r--src/Http/Http/src/MiddlewareFactory.cs51
-rw-r--r--src/Http/Http/src/QueryCollection.cs361
-rw-r--r--src/Http/Http/src/QueryCollectionInternal.cs185
-rw-r--r--src/Http/Http/src/RequestFormReaderExtensions.cs65
-rw-r--r--src/Http/Http/src/SendFileFallback.cs79
-rw-r--r--src/Http/Http/src/StreamResponseBodyFeature.cs223
-rw-r--r--src/Http/Http/test/ApplicationBuilderTests.cs139
-rw-r--r--src/Http/Http/test/BindingAddressTests.cs181
-rw-r--r--src/Http/Http/test/DefaultHttpContextTests.cs805
-rw-r--r--src/Http/Http/test/Features/FakeResponseFeature.cs29
-rw-r--r--src/Http/Http/test/Features/FormFeatureTests.cs995
-rw-r--r--src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs53
-rw-r--r--src/Http/Http/test/Features/NonSeekableReadStream.cs119
-rw-r--r--src/Http/Http/test/Features/QueryFeatureTests.cs367
-rw-r--r--src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs67
-rw-r--r--src/Http/Http/test/Features/StreamResponseBodyFeatureTests.cs133
-rw-r--r--src/Http/Http/test/HeaderDictionaryTests.cs161
-rw-r--r--src/Http/Http/test/HttpContextAccessorTests.cs243
-rw-r--r--src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs37
-rw-r--r--src/Http/Http/test/Internal/DefaultHttpRequestTests.cs443
-rw-r--r--src/Http/Http/test/Internal/DefaultHttpResponseTests.cs401
-rw-r--r--src/Http/Http/test/Internal/ItemsDictionaryTests.cs40
-rw-r--r--src/Http/Http/test/Internal/ReferenceReadStreamTests.cs111
-rw-r--r--src/Http/Http/test/RequestCookiesCollectionTests.cs77
-rw-r--r--src/Http/Http/test/ResponseCookiesTest.cs403
-rw-r--r--src/Http/Metadata/src/IAllowAnonymous.cs13
-rw-r--r--src/Http/Metadata/src/IAuthorizeData.cs33
-rw-r--r--src/Http/Owin/src/DictionaryStringArrayWrapper.cs85
-rw-r--r--src/Http/Owin/src/DictionaryStringValuesWrapper.cs149
-rw-r--r--src/Http/Owin/src/IOwinEnvironmentFeature.cs17
-rw-r--r--src/Http/Owin/src/OwinConstants.cs235
-rw-r--r--src/Http/Owin/src/OwinEnvironment.cs615
-rw-r--r--src/Http/Owin/src/OwinEnvironmentFeature.cs17
-rw-r--r--src/Http/Owin/src/OwinExtensions.cs321
-rw-r--r--src/Http/Owin/src/OwinFeatureCollection.cs675
-rw-r--r--src/Http/Owin/src/Utilities.cs77
-rw-r--r--src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs225
-rw-r--r--src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs81
-rw-r--r--src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs313
-rw-r--r--src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs153
-rw-r--r--src/Http/Owin/src/WebSockets/WebSocketAdapter.cs265
-rw-r--r--src/Http/Owin/test/OwinEnvironmentTests.cs263
-rw-r--r--src/Http/Owin/test/OwinExtensionTests.cs263
-rw-r--r--src/Http/Owin/test/OwinFeatureCollectionTests.cs73
-rw-r--r--src/Http/Routing.Abstractions/src/IOutboundParameterTransformer.cs23
-rw-r--r--src/Http/Routing.Abstractions/src/IParameterPolicy.cs13
-rw-r--r--src/Http/Routing.Abstractions/src/IRouteConstraint.cs47
-rw-r--r--src/Http/Routing.Abstractions/src/IRouteHandler.cs29
-rw-r--r--src/Http/Routing.Abstractions/src/IRouter.cs31
-rw-r--r--src/Http/Routing.Abstractions/src/IRoutingFeature.cs17
-rw-r--r--src/Http/Routing.Abstractions/src/LinkGenerator.cs279
-rw-r--r--src/Http/Routing.Abstractions/src/LinkOptions.cs37
-rw-r--r--src/Http/Routing.Abstractions/src/Properties/AssemblyInfo.cs2
-rw-r--r--src/Http/Routing.Abstractions/src/RouteContext.cs75
-rw-r--r--src/Http/Routing.Abstractions/src/RouteData.cs455
-rw-r--r--src/Http/Routing.Abstractions/src/RouteDirection.cs25
-rw-r--r--src/Http/Routing.Abstractions/src/RoutingHttpContextExtensions.cs67
-rw-r--r--src/Http/Routing.Abstractions/src/VirtualPathContext.cs101
-rw-r--r--src/Http/Routing.Abstractions/src/VirtualPathData.cs141
-rw-r--r--src/Http/Routing.Abstractions/test/RouteDataTest.cs293
-rw-r--r--src/Http/Routing.Abstractions/test/VirtualPathDataTests.cs93
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/EndpointMetadataCollectionBenchmark.cs185
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/EndpointRoutingBenchmarkBase.cs209
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/LinkGeneration/LinkGenerationGithubBenchmark.cs107
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteRouteValuesAddressSchemeBenchmark.cs97
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithConstraintsBenchmark.cs101
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithNoParametersBenchmark.cs95
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithParametersBenchmark.cs101
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerBenchmarkBase.cs43
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerEmptyBenchmark.cs39
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerLargeBenchmark.cs47
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerPlaintextBenchmark.cs39
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerSmallBenchmark.cs39
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/HttpMethodPolicyJumpTableBenchmark.cs79
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableMultipleEntryBenchmark.cs255
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableSingleEntryBenchmark.cs191
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableZeroEntryBenchmark.cs77
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/MatcherAzureBenchmark.cs73
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderAzureBenchmark.cs37
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderGithubBenchmark.cs37
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderMultipleEntryBenchmark.cs263
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/MatcherGithubBenchmark.cs61
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/MatcherSingleEntryBenchmark.cs109
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/RouteEndpointAzureBenchmark.cs15
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcher.cs65
-rw-r--r--src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcherBuilder.cs23
-rw-r--r--src/Http/Routing/src/ArrayBuilder.cs247
-rw-r--r--src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs899
-rw-r--r--src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs247
-rw-r--r--src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs157
-rw-r--r--src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs359
-rw-r--r--src/Http/Routing/src/Builder/RouteHandlerBuilder.cs71
-rw-r--r--src/Http/Routing/src/Builder/RoutingBuilderExtensions.cs107
-rw-r--r--src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs233
-rw-r--r--src/Http/Routing/src/CompositeEndpointDataSource.cs321
-rw-r--r--src/Http/Routing/src/ConfigureRouteHandlerOptions.cs25
-rw-r--r--src/Http/Routing/src/ConfigureRouteOptions.cs37
-rw-r--r--src/Http/Routing/src/Constraints/AlphaRouteConstraint.cs19
-rw-r--r--src/Http/Routing/src/Constraints/BoolRouteConstraint.cs73
-rw-r--r--src/Http/Routing/src/Constraints/CompositeRouteConstraint.cs95
-rw-r--r--src/Http/Routing/src/Constraints/DateTimeRouteConstraint.cs85
-rw-r--r--src/Http/Routing/src/Constraints/DecimalRouteConstraint.cs73
-rw-r--r--src/Http/Routing/src/Constraints/DoubleRouteConstraint.cs81
-rw-r--r--src/Http/Routing/src/Constraints/FileNameRouteConstraint.cs255
-rw-r--r--src/Http/Routing/src/Constraints/FloatRouteConstraint.cs81
-rw-r--r--src/Http/Routing/src/Constraints/GuidRouteConstraint.cs77
-rw-r--r--src/Http/Routing/src/Constraints/HttpMethodRouteConstraint.cs129
-rw-r--r--src/Http/Routing/src/Constraints/IntRouteConstraint.cs73
-rw-r--r--src/Http/Routing/src/Constraints/LengthRouteConstraint.cs163
-rw-r--r--src/Http/Routing/src/Constraints/LongRouteConstraint.cs73
-rw-r--r--src/Http/Routing/src/Constraints/MaxLengthRouteConstraint.cs93
-rw-r--r--src/Http/Routing/src/Constraints/MaxRouteConstraint.cs91
-rw-r--r--src/Http/Routing/src/Constraints/MinLengthRouteConstraint.cs93
-rw-r--r--src/Http/Routing/src/Constraints/MinRouteConstraint.cs91
-rw-r--r--src/Http/Routing/src/Constraints/NonFileNameRouteConstraint.cs203
-rw-r--r--src/Http/Routing/src/Constraints/NullRouteConstraint.cs21
-rw-r--r--src/Http/Routing/src/Constraints/OptionalRouteConstraint.cs89
-rw-r--r--src/Http/Routing/src/Constraints/RangeRouteConstraint.cs113
-rw-r--r--src/Http/Routing/src/Constraints/RegexInlineRouteConstraint.cs21
-rw-r--r--src/Http/Routing/src/Constraints/RegexRouteConstraint.cs121
-rw-r--r--src/Http/Routing/src/Constraints/RequiredRouteConstraint.cs63
-rw-r--r--src/Http/Routing/src/Constraints/StringRouteConstraint.cs79
-rw-r--r--src/Http/Routing/src/DataSourceDependentCache.cs125
-rw-r--r--src/Http/Routing/src/DataTokensMetadata.cs35
-rw-r--r--src/Http/Routing/src/DecisionTree/DecisionCriterion.cs11
-rw-r--r--src/Http/Routing/src/DecisionTree/DecisionCriterionValue.cs23
-rw-r--r--src/Http/Routing/src/DecisionTree/DecisionCriterionValueEqualityComparer.cs31
-rw-r--r--src/Http/Routing/src/DecisionTree/DecisionTreeBuilder.cs353
-rw-r--r--src/Http/Routing/src/DecisionTree/DecisionTreeNode.cs23
-rw-r--r--src/Http/Routing/src/DecisionTree/IClassifier.cs13
-rw-r--r--src/Http/Routing/src/DecisionTree/ItemDescriptor.cs13
-rw-r--r--src/Http/Routing/src/DefaultEndpointConventionBuilder.cs55
-rw-r--r--src/Http/Routing/src/DefaultEndpointDataSource.cs75
-rw-r--r--src/Http/Routing/src/DefaultEndpointRouteBuilder.cs23
-rw-r--r--src/Http/Routing/src/DefaultInlineConstraintResolver.cs89
-rw-r--r--src/Http/Routing/src/DefaultLinkGenerator.cs733
-rw-r--r--src/Http/Routing/src/DefaultLinkParser.cs285
-rw-r--r--src/Http/Routing/src/DefaultParameterPolicyFactory.cs105
-rw-r--r--src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs203
-rw-r--r--src/Http/Routing/src/EndpointDataSource.cs29
-rw-r--r--src/Http/Routing/src/EndpointGroupNameAttribute.cs37
-rw-r--r--src/Http/Routing/src/EndpointMiddleware.cs149
-rw-r--r--src/Http/Routing/src/EndpointNameAddressScheme.cs143
-rw-r--r--src/Http/Routing/src/EndpointNameAttribute.cs47
-rw-r--r--src/Http/Routing/src/EndpointNameMetadata.cs43
-rw-r--r--src/Http/Routing/src/EndpointRoutingMiddleware.cs263
-rw-r--r--src/Http/Routing/src/ExcludeFromDescriptionAttribute.cs19
-rw-r--r--src/Http/Routing/src/HostAttribute.cs89
-rw-r--r--src/Http/Routing/src/HttpMethodMetadata.cs87
-rw-r--r--src/Http/Routing/src/IDataTokenMetadata.cs21
-rw-r--r--src/Http/Routing/src/IDynamicEndpointMetadata.cs47
-rw-r--r--src/Http/Routing/src/IEndpointAddressScheme.cs23
-rw-r--r--src/Http/Routing/src/IEndpointGroupNameMetadata.cs17
-rw-r--r--src/Http/Routing/src/IEndpointNameMetadata.cs25
-rw-r--r--src/Http/Routing/src/IEndpointRouteBuilder.cs37
-rw-r--r--src/Http/Routing/src/IExcludeFromDescriptionMetadata.cs21
-rw-r--r--src/Http/Routing/src/IHostMetadata.cs21
-rw-r--r--src/Http/Routing/src/IHttpMethodMetadata.cs27
-rw-r--r--src/Http/Routing/src/IInlineConstraintResolver.cs21
-rw-r--r--src/Http/Routing/src/INamedRouter.cs17
-rw-r--r--src/Http/Routing/src/IRouteBuilder.cs53
-rw-r--r--src/Http/Routing/src/IRouteCollection.cs19
-rw-r--r--src/Http/Routing/src/IRouteNameMetadata.cs19
-rw-r--r--src/Http/Routing/src/ISuppressLinkGenerationMetadata.cs21
-rw-r--r--src/Http/Routing/src/ISuppressMatchingMetadata.cs21
-rw-r--r--src/Http/Routing/src/InlineRouteParameterParser.cs415
-rw-r--r--src/Http/Routing/src/Internal/DfaGraphWriter.cs139
-rw-r--r--src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs409
-rw-r--r--src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs371
-rw-r--r--src/Http/Routing/src/LinkParser.cs55
-rw-r--r--src/Http/Routing/src/LinkParserEndpointNameAddressExtensions.cs77
-rw-r--r--src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs265
-rw-r--r--src/Http/Routing/src/Matching/AmbiguousMatchException.cs29
-rw-r--r--src/Http/Routing/src/Matching/Ascii.cs103
-rw-r--r--src/Http/Routing/src/Matching/Candidate.cs199
-rw-r--r--src/Http/Routing/src/Matching/CandidateSet.cs591
-rw-r--r--src/Http/Routing/src/Matching/CandidateState.cs85
-rw-r--r--src/Http/Routing/src/Matching/DataSourceDependentMatcher.cs143
-rw-r--r--src/Http/Routing/src/Matching/DefaultEndpointSelector.cs195
-rw-r--r--src/Http/Routing/src/Matching/DfaMatcher.cs615
-rw-r--r--src/Http/Routing/src/Matching/DfaMatcherBuilder.cs1421
-rw-r--r--src/Http/Routing/src/Matching/DfaMatcherFactory.cs53
-rw-r--r--src/Http/Routing/src/Matching/DfaNode.cs175
-rw-r--r--src/Http/Routing/src/Matching/DfaState.cs51
-rw-r--r--src/Http/Routing/src/Matching/DictionaryJumpTable.cs83
-rw-r--r--src/Http/Routing/src/Matching/EndpointComparer.cs219
-rw-r--r--src/Http/Routing/src/Matching/EndpointMetadataComparer.cs253
-rw-r--r--src/Http/Routing/src/Matching/EndpointSelector.cs37
-rw-r--r--src/Http/Routing/src/Matching/FastPathTokenizer.cs65
-rw-r--r--src/Http/Routing/src/Matching/HostMatcherPolicy.cs685
-rw-r--r--src/Http/Routing/src/Matching/HttpMethodDictionaryPolicyJumpTable.cs67
-rw-r--r--src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs779
-rw-r--r--src/Http/Routing/src/Matching/HttpMethodSingleEntryPolicyJumpTable.cs63
-rw-r--r--src/Http/Routing/src/Matching/IEndpointComparerPolicy.cs49
-rw-r--r--src/Http/Routing/src/Matching/IEndpointSelectorPolicy.cs77
-rw-r--r--src/Http/Routing/src/Matching/ILEmitTrieFactory.cs991
-rw-r--r--src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs153
-rw-r--r--src/Http/Routing/src/Matching/INodeBuilderPolicy.cs47
-rw-r--r--src/Http/Routing/src/Matching/IParameterLiteralNodeMatchingPolicy.cs29
-rw-r--r--src/Http/Routing/src/Matching/JumpTable.cs17
-rw-r--r--src/Http/Routing/src/Matching/JumpTableBuilder.cs151
-rw-r--r--src/Http/Routing/src/Matching/LinearSearchJumpTable.cs93
-rw-r--r--src/Http/Routing/src/Matching/Matcher.cs23
-rw-r--r--src/Http/Routing/src/Matching/MatcherBuilder.cs11
-rw-r--r--src/Http/Routing/src/Matching/MatcherFactory.cs9
-rw-r--r--src/Http/Routing/src/Matching/MatcherPolicy.cs93
-rw-r--r--src/Http/Routing/src/Matching/PathSegment.cs59
-rw-r--r--src/Http/Routing/src/Matching/PolicyJumpTable.cs25
-rw-r--r--src/Http/Routing/src/Matching/PolicyJumpTableEdge.cs45
-rw-r--r--src/Http/Routing/src/Matching/PolicyNodeEdge.cs45
-rw-r--r--src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs77
-rw-r--r--src/Http/Routing/src/Matching/SingleEntryJumpTable.cs77
-rw-r--r--src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs35
-rw-r--r--src/Http/Routing/src/ModelEndpointDataSource.cs43
-rw-r--r--src/Http/Routing/src/NullRouter.cs29
-rw-r--r--src/Http/Routing/src/ParameterPolicyActivator.cs273
-rw-r--r--src/Http/Routing/src/ParameterPolicyFactory.cs83
-rw-r--r--src/Http/Routing/src/PathTokenizer.cs277
-rw-r--r--src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs367
-rw-r--r--src/Http/Routing/src/Patterns/RouteParameterParser.cs427
-rw-r--r--src/Http/Routing/src/Patterns/RoutePattern.cs283
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternException.cs77
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternFactory.cs1491
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternLiteralPart.cs41
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternMatcher.cs737
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternParameterKind.cs35
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternParameterPart.cs185
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternParameterPolicyReference.cs53
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternParser.cs809
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternPart.cs61
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternPartKind.cs33
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternPathSegment.cs67
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternSeparatorPart.cs75
-rw-r--r--src/Http/Routing/src/Patterns/RoutePatternTransformer.cs55
-rw-r--r--src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs521
-rw-r--r--src/Http/Routing/src/Route.cs175
-rw-r--r--src/Http/Routing/src/RouteBase.cs539
-rw-r--r--src/Http/Routing/src/RouteBuilder.cs99
-rw-r--r--src/Http/Routing/src/RouteCollection.cs269
-rw-r--r--src/Http/Routing/src/RouteConstraintBuilder.cs295
-rw-r--r--src/Http/Routing/src/RouteConstraintMatcher.cs131
-rw-r--r--src/Http/Routing/src/RouteCreationException.cs41
-rw-r--r--src/Http/Routing/src/RouteEndpoint.cs87
-rw-r--r--src/Http/Routing/src/RouteEndpointBuilder.cs85
-rw-r--r--src/Http/Routing/src/RouteHandler.cs65
-rw-r--r--src/Http/Routing/src/RouteHandlerOptions.cs27
-rw-r--r--src/Http/Routing/src/RouteNameMetadata.cs39
-rw-r--r--src/Http/Routing/src/RouteOptions.cs255
-rw-r--r--src/Http/Routing/src/RouteValueEqualityComparer.cs77
-rw-r--r--src/Http/Routing/src/RouteValuesAddress.cs33
-rw-r--r--src/Http/Routing/src/RouteValuesAddressScheme.cs291
-rw-r--r--src/Http/Routing/src/RouterMiddleware.cs103
-rw-r--r--src/Http/Routing/src/RoutingFeature.cs17
-rw-r--r--src/Http/Routing/src/RoutingMarkerService.cs15
-rw-r--r--src/Http/Routing/src/SegmentState.cs23
-rw-r--r--src/Http/Routing/src/SuppressLinkGenerationMetadata.cs21
-rw-r--r--src/Http/Routing/src/SuppressMatchingMetadata.cs19
-rw-r--r--src/Http/Routing/src/Template/DefaultTemplateBinderFactory.cs109
-rw-r--r--src/Http/Routing/src/Template/InlineConstraint.cs57
-rw-r--r--src/Http/Routing/src/Template/RoutePrecedence.cs397
-rw-r--r--src/Http/Routing/src/Template/RouteTemplate.cs205
-rw-r--r--src/Http/Routing/src/Template/TemplateBinder.cs1197
-rw-r--r--src/Http/Routing/src/Template/TemplateBinderFactory.cs37
-rw-r--r--src/Http/Routing/src/Template/TemplateMatcher.cs139
-rw-r--r--src/Http/Routing/src/Template/TemplateParser.cs45
-rw-r--r--src/Http/Routing/src/Template/TemplatePart.cs295
-rw-r--r--src/Http/Routing/src/Template/TemplateSegment.cs91
-rw-r--r--src/Http/Routing/src/Template/TemplateValuesResult.cs41
-rw-r--r--src/Http/Routing/src/Tree/InboundMatch.cs33
-rw-r--r--src/Http/Routing/src/Tree/InboundRouteEntry.cs91
-rw-r--r--src/Http/Routing/src/Tree/LinkGenerationDecisionTree.cs397
-rw-r--r--src/Http/Routing/src/Tree/OutboundMatch.cs25
-rw-r--r--src/Http/Routing/src/Tree/OutboundMatchResult.cs19
-rw-r--r--src/Http/Routing/src/Tree/OutboundRouteEntry.cs97
-rw-r--r--src/Http/Routing/src/Tree/TreeEnumerator.cs141
-rw-r--r--src/Http/Routing/src/Tree/TreeRouteBuilder.cs403
-rw-r--r--src/Http/Routing/src/Tree/TreeRouter.cs489
-rw-r--r--src/Http/Routing/src/Tree/UrlMatchingNode.cs113
-rw-r--r--src/Http/Routing/src/Tree/UrlMatchingTree.cs293
-rw-r--r--src/Http/Routing/src/UriBuilderContextPooledObjectPolicy.cs21
-rw-r--r--src/Http/Routing/src/UriBuildingContext.cs473
-rw-r--r--src/Http/Routing/test/FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs85
-rw-r--r--src/Http/Routing/test/FunctionalTests/Benchmarks/RouterBenchmarkTest.cs87
-rw-r--r--src/Http/Routing/test/FunctionalTests/EndpointRoutingIntegrationTest.cs587
-rw-r--r--src/Http/Routing/test/FunctionalTests/EndpointRoutingSampleTest.cs445
-rw-r--r--src/Http/Routing/test/FunctionalTests/HostMatchingTests.cs209
-rw-r--r--src/Http/Routing/test/FunctionalTests/MapFallbackTest.cs185
-rw-r--r--src/Http/Routing/test/FunctionalTests/RouteHandlerTest.cs85
-rw-r--r--src/Http/Routing/test/FunctionalTests/RouterSampleTest.cs179
-rw-r--r--src/Http/Routing/test/FunctionalTests/RoutingTestFixture.cs75
-rw-r--r--src/Http/Routing/test/FunctionalTests/WebHostBuilderExtensionsTest.cs71
-rw-r--r--src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs567
-rw-r--r--src/Http/Routing/test/UnitTests/Builder/RequestDelegateEndpointRouteBuilderExtensionsTest.cs307
-rw-r--r--src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs1385
-rw-r--r--src/Http/Routing/test/UnitTests/Builder/RoutingBuilderExtensionsTest.cs209
-rw-r--r--src/Http/Routing/test/UnitTests/Builder/RoutingEndpointConventionBuilderExtensionsTest.cs265
-rw-r--r--src/Http/Routing/test/UnitTests/CompositeEndpointDataSourceTest.cs297
-rw-r--r--src/Http/Routing/test/UnitTests/ConstraintMatcherTest.cs347
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/AlphaRouteConstraintTests.cs39
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/BoolRouteConstraintTests.cs45
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/CompositeRouteConstraintTests.cs71
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/ConstraintsTestHelper.cs17
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/DateTimeRouteConstraintTests.cs65
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/DecimalRouteConstraintTests.cs47
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/DoubleRouteConstraintTests.cs35
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/FIleNameRouteConstraintTest.cs97
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/FloatRouteConstraintTests.cs33
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/GuidRouteConstraintTests.cs41
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/HttpMethodRouteConstraintTests.cs165
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/IntRouteConstraintsTests.cs33
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/LengthRouteConstraintTests.cs183
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/LongRouteConstraintTests.cs37
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/MaxLengthRouteConstraintTests.cs55
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/MaxRouteConstraintTests.cs29
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/MinLengthRouteConstraintTests.cs55
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/MinRouteConstraintTests.cs29
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/NonFIleNameRouteConstraintTest.cs99
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/RangeRouteConstraintTests.cs65
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/RegexInlineRouteConstraintTests.cs125
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/RegexRouteConstraintTests.cs203
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/RequiredRouteConstraintTests.cs139
-rw-r--r--src/Http/Routing/test/UnitTests/Constraints/StringRouteConstraintTest.cs291
-rw-r--r--src/Http/Routing/test/UnitTests/DataSourceDependentCacheTest.cs203
-rw-r--r--src/Http/Routing/test/UnitTests/DecisionTreeBuilderTest.cs315
-rw-r--r--src/Http/Routing/test/UnitTests/DefaultEndpointDataSourceTests.cs167
-rw-r--r--src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs651
-rw-r--r--src/Http/Routing/test/UnitTests/DefaultLinkGeneratorProcessTemplateTest.cs2869
-rw-r--r--src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs1229
-rw-r--r--src/Http/Routing/test/UnitTests/DefaultLinkParserTest.cs263
-rw-r--r--src/Http/Routing/test/UnitTests/DefaultParameterPolicyFactoryTest.cs797
-rw-r--r--src/Http/Routing/test/UnitTests/EndpointFactory.cs53
-rw-r--r--src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs437
-rw-r--r--src/Http/Routing/test/UnitTests/EndpointNameAddressSchemeTest.cs285
-rw-r--r--src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs305
-rw-r--r--src/Http/Routing/test/UnitTests/InlineRouteParameterParserTests.cs1555
-rw-r--r--src/Http/Routing/test/UnitTests/Internal/DfaGraphWriterTest.cs115
-rw-r--r--src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs273
-rw-r--r--src/Http/Routing/test/UnitTests/LinkGeneratorIntegrationTest.cs1003
-rw-r--r--src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs377
-rw-r--r--src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs113
-rw-r--r--src/Http/Routing/test/UnitTests/LinkParserEndpointNameExtensionsTest.cs71
-rw-r--r--src/Http/Routing/test/UnitTests/LinkParserTestBase.cs97
-rw-r--r--src/Http/Routing/test/UnitTests/Logging/WriteContext.cs23
-rw-r--r--src/Http/Routing/test/UnitTests/MatcherPolicyTest.cs111
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/AcceptsMatcherPolicyTest.cs813
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/AsciiTest.cs207
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs167
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherBuilder.cs37
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherConformanceTest.cs81
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/CandidateSetTest.cs609
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/DataSourceDependentMatcherTest.cs457
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs281
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs3815
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/DfaMatcherConformanceTest.cs97
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs1535
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/DictionaryJumpTableTest.cs17
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/EndpointComparerTest.cs499
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/EndpointMetadataComparerTest.cs159
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/FastPathTokenizerTest.cs245
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/FullFeaturedMatcherConformanceTest.cs867
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyINodeBuilderPolicyIntegrationTest.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs653
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyTest.cs397
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTest.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs593
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyTest.cs565
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/ILEmitTrieFactoryTest.cs51
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs431
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/LinearSearchJumpTableTest.cs17
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/MatcherAssert.cs147
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.MultipleEndpoint.cs7
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.SingleEndpoint.cs625
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs73
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs141
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/NonVectorizedILEmitTrieJumpTableTest.cs9
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs39
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs145
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/RouteMatcherConformanceTest.cs55
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/SingleEntryAsciiJumpTableTest.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTest.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs95
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs39
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs139
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherConformanceTest.cs79
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/VectorizedILEmitTrieJumpTableTest.cs13
-rw-r--r--src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs53
-rw-r--r--src/Http/Routing/test/UnitTests/PathTokenizerTest.cs75
-rw-r--r--src/Http/Routing/test/UnitTests/Patterns/DefaultRoutePatternTransformerTest.cs781
-rw-r--r--src/Http/Routing/test/UnitTests/Patterns/InlineRouteParameterParserTest.cs2129
-rw-r--r--src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs1291
-rw-r--r--src/Http/Routing/test/UnitTests/Patterns/RoutePatternMatcherTest.cs1851
-rw-r--r--src/Http/Routing/test/UnitTests/Patterns/RoutePatternParserTest.cs1253
-rw-r--r--src/Http/Routing/test/UnitTests/RequestDelegateRouteBuilderExtensionsTest.cs157
-rw-r--r--src/Http/Routing/test/UnitTests/RouteBuilderTest.cs81
-rw-r--r--src/Http/Routing/test/UnitTests/RouteCollectionTest.cs1099
-rw-r--r--src/Http/Routing/test/UnitTests/RouteConstraintBuilderTest.cs347
-rw-r--r--src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs39
-rw-r--r--src/Http/Routing/test/UnitTests/RouteHandlerOptionsTests.cs105
-rw-r--r--src/Http/Routing/test/UnitTests/RouteOptionsTests.cs107
-rw-r--r--src/Http/Routing/test/UnitTests/RouteTest.cs3261
-rw-r--r--src/Http/Routing/test/UnitTests/RouteValueEqualityComparerTest.cs61
-rw-r--r--src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs853
-rw-r--r--src/Http/Routing/test/UnitTests/RouterMiddlewareTest.cs207
-rw-r--r--src/Http/Routing/test/UnitTests/RoutingEndpointConventionBuilderExtensionsTests.cs45
-rw-r--r--src/Http/Routing/test/UnitTests/Template/RoutePatternPrecedenceTests.cs63
-rw-r--r--src/Http/Routing/test/UnitTests/Template/RoutePrecedenceTestsBase.cs217
-rw-r--r--src/Http/Routing/test/UnitTests/Template/RouteTemplatePrecedenceTests.cs29
-rw-r--r--src/Http/Routing/test/UnitTests/Template/TemplateBinderTests.cs1597
-rw-r--r--src/Http/Routing/test/UnitTests/Template/TemplateMatcherTests.cs1849
-rw-r--r--src/Http/Routing/test/UnitTests/Template/TemplateParserTests.cs1623
-rw-r--r--src/Http/Routing/test/UnitTests/Template/TemplateSegmentTest.cs67
-rw-r--r--src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs251
-rw-r--r--src/Http/Routing/test/UnitTests/TestConstants.cs9
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/CapturingConstraint.cs27
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/DynamicEndpointDataSource.cs85
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/SlugifyParameterTransformer.cs13
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/TestMatcher.cs29
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/TestMatcherFactory.cs25
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/TestServiceProvider.cs13
-rw-r--r--src/Http/Routing/test/UnitTests/TestObjects/UpperCaseParameterTransform.cs11
-rw-r--r--src/Http/Routing/test/UnitTests/Tree/LinkGenerationDecisionTreeTest.cs1121
-rw-r--r--src/Http/Routing/test/UnitTests/Tree/TreeRouteBuilderTest.cs413
-rw-r--r--src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs3231
-rw-r--r--src/Http/Routing/test/UnitTests/UriBuildingContextTest.cs157
-rw-r--r--src/Http/Routing/test/testassets/Benchmarks/Program.cs91
-rw-r--r--src/Http/Routing/test/testassets/Benchmarks/StartupUsingEndpointRouting.cs35
-rw-r--r--src/Http/Routing/test/testassets/Benchmarks/StartupUsingRouter.cs43
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkConfigurationBuilder.cs47
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointDataSource.cs121
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointRouteBuilderExtensions.cs33
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs23
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs23
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloMiddleware.cs51
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloOptions.cs9
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/Program.cs109
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/SlugifyParameterTransformer.cs13
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/UseEndpointRoutingStartup.cs171
-rw-r--r--src/Http/Routing/test/testassets/RoutingSandbox/UseRouterStartup.cs47
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/EndsWithStringRouteConstraint.cs33
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/EndpointRouteBuilderExtensions.cs23
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloAppBuilderExtensions.cs23
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloMiddleware.cs51
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloOptions.cs9
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/MapFallbackStartup.cs39
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/Program.cs111
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/UseEndpointRoutingStartup.cs267
-rw-r--r--src/Http/Routing/test/testassets/RoutingWebSite/UseRouterStartup.cs63
-rw-r--r--src/Http/Routing/tools/Swaggatherer/Program.cs13
-rw-r--r--src/Http/Routing/tools/Swaggatherer/RouteEntry.cs15
-rw-r--r--src/Http/Routing/tools/Swaggatherer/SwaggathererApplication.cs355
-rw-r--r--src/Http/Routing/tools/Swaggatherer/Template.cs103
-rw-r--r--src/Http/Shared/CookieHeaderParserShared.cs341
-rw-r--r--src/Http/Shared/HttpParseResult.cs13
-rw-r--r--src/Http/Shared/HttpRuleParser.cs495
-rw-r--r--src/Http/Shared/StreamCopyOperationInternal.cs117
-rw-r--r--src/Http/WebUtilities/perf/Microbenchmarks/FormPipeReaderInternalsBenchmark.cs65
-rw-r--r--src/Http/WebUtilities/perf/Microbenchmarks/FormReaderBenchmark.cs53
-rw-r--r--src/Http/WebUtilities/perf/Microbenchmarks/HttpRequestStreamReaderReadLineBenchmark.cs83
-rw-r--r--src/Http/WebUtilities/src/AspNetCoreTempDirectory.cs39
-rw-r--r--src/Http/WebUtilities/src/Base64UrlTextEncoder.cs45
-rw-r--r--src/Http/WebUtilities/src/BufferedReadStream.cs657
-rw-r--r--src/Http/WebUtilities/src/FileBufferingReadStream.cs761
-rw-r--r--src/Http/WebUtilities/src/FileBufferingWriteStream.cs479
-rw-r--r--src/Http/WebUtilities/src/FileMultipartSection.cs95
-rw-r--r--src/Http/WebUtilities/src/FormMultipartSection.cs91
-rw-r--r--src/Http/WebUtilities/src/FormPipeReader.cs637
-rw-r--r--src/Http/WebUtilities/src/FormReader.cs527
-rw-r--r--src/Http/WebUtilities/src/HttpRequestStreamReader.cs831
-rw-r--r--src/Http/WebUtilities/src/HttpResponseStreamWriter.cs889
-rw-r--r--src/Http/WebUtilities/src/KeyValueAccumulator.cs149
-rw-r--r--src/Http/WebUtilities/src/MultipartBoundary.cs95
-rw-r--r--src/Http/WebUtilities/src/MultipartReader.cs227
-rw-r--r--src/Http/WebUtilities/src/MultipartReaderStream.cs481
-rw-r--r--src/Http/WebUtilities/src/MultipartSection.cs73
-rw-r--r--src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs101
-rw-r--r--src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs65
-rw-r--r--src/Http/WebUtilities/src/PagedByteBuffer.cs191
-rw-r--r--src/Http/WebUtilities/src/QueryHelpers.cs281
-rw-r--r--src/Http/WebUtilities/src/ReasonPhrases.cs161
-rw-r--r--src/Http/WebUtilities/src/StreamHelperExtensions.cs123
-rw-r--r--src/Http/WebUtilities/test/AspNetCoreTempDirectoryTests.cs17
-rw-r--r--src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs873
-rw-r--r--src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs611
-rw-r--r--src/Http/WebUtilities/test/FormPipeReaderTests.cs925
-rw-r--r--src/Http/WebUtilities/test/FormReaderAsyncTest.cs21
-rw-r--r--src/Http/WebUtilities/test/FormReaderTests.cs341
-rw-r--r--src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs815
-rw-r--r--src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs1155
-rw-r--r--src/Http/WebUtilities/test/MultipartReaderTests.cs549
-rw-r--r--src/Http/WebUtilities/test/NonSeekableReadStream.cs123
-rw-r--r--src/Http/WebUtilities/test/PagedByteBufferTest.cs389
-rw-r--r--src/Http/WebUtilities/test/QueryHelpersTests.cs359
-rw-r--r--src/Http/WebUtilities/test/WebEncodersTests.cs91
-rw-r--r--src/Http/samples/SampleApp/Program.cs15
820 files changed, 96129 insertions, 96949 deletions
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs b/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs
index e55e557309..9e3448c0c5 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs
@@ -5,130 +5,129 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Contains the result of an Authenticate call
+/// </summary>
+public class AuthenticateResult
{
/// <summary>
- /// Contains the result of an Authenticate call
+ /// Creates a new <see cref="AuthenticateResult"/> instance.
+ /// </summary>
+ protected AuthenticateResult() { }
+
+ /// <summary>
+ /// If a ticket was produced, authenticate was successful.
+ /// </summary>
+ [MemberNotNullWhen(true, nameof(Ticket), nameof(Principal), nameof(Properties))]
+ 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 class AuthenticateResult
+ 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>
+ /// Create a new deep copy of the result
+ /// </summary>
+ /// <returns>A copy of the result</returns>
+ public AuthenticateResult Clone()
{
- /// <summary>
- /// Creates a new <see cref="AuthenticateResult"/> instance.
- /// </summary>
- protected AuthenticateResult() { }
-
- /// <summary>
- /// If a ticket was produced, authenticate was successful.
- /// </summary>
- [MemberNotNullWhen(true, nameof(Ticket), nameof(Principal), nameof(Properties))]
- 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>
- /// Create a new deep copy of the result
- /// </summary>
- /// <returns>A copy of the result</returns>
- public AuthenticateResult Clone()
+ if (None)
{
- if (None)
- {
- return NoResult();
- }
- if (Failure != null)
- {
- return Fail(Failure, Properties?.Clone());
- }
- if (Succeeded)
- {
- return Success(Ticket!.Clone());
- }
- // This shouldn't happen
- throw new NotImplementedException();
+ return NoResult();
}
-
- /// <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 (Failure != null)
{
- if (ticket == null)
- {
- throw new ArgumentNullException(nameof(ticket));
- }
- return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
+ return Fail(Failure, Properties?.Clone());
}
-
- /// <summary>
- /// Indicates that there was no information returned for this authentication scheme.
- /// </summary>
- /// <returns>The result.</returns>
- public static AuthenticateResult NoResult()
+ if (Succeeded)
{
- return new AuthenticateResult() { None = true };
+ return Success(Ticket!.Clone());
}
+ // This shouldn't happen
+ throw new NotImplementedException();
+ }
- /// <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)
+ /// <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)
{
- return new AuthenticateResult() { Failure = failure };
+ throw new ArgumentNullException(nameof(ticket));
}
+ return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
+ }
- /// <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 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="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);
+ /// <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
index 5e6f4c8519..e6feefba0b 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs
@@ -6,218 +6,217 @@ using Microsoft.AspNetCore.Authentication.Abstractions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Extension methods to expose Authentication on HttpContext.
+/// </summary>
+public static class AuthenticationHttpContextExtensions
{
/// <summary>
- /// Extension methods to expose Authentication on HttpContext.
- /// </summary>
- public static class AuthenticationHttpContextExtensions
- {
- /// <summary>
- /// Authenticate the current request using the default authentication scheme.
- /// The default authentication scheme can be configured using <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
- /// </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>
- /// Authenticate the current request using the specified scheme.
- /// </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) =>
- GetAuthenticationService(context).AuthenticateAsync(context, scheme);
-
- /// <summary>
- /// Challenge the current request using the specified scheme.
- /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
- /// </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>
- /// Challenge the current request using the default challenge scheme.
- /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
- /// The default challenge scheme can be configured using <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
- /// </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>
- /// Challenge the current request using the default challenge scheme.
- /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
- /// The default challenge scheme can be configured using <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
- /// </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>
- /// Challenge the current request using the specified scheme.
- /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
- /// </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) =>
- GetAuthenticationService(context).ChallengeAsync(context, scheme, properties);
-
- /// <summary>
- /// Forbid the current request using the specified scheme.
- /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
- /// </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>
- /// Forbid the current request using the default forbid scheme.
- /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
- /// The default forbid scheme can be configured using <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
- /// </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>
- /// Forbid the current request using the default forbid scheme.
- /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
- /// The default forbid scheme can be configured using <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
- /// </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>
- /// Forbid the current request using the specified scheme.
- /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
- /// </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) =>
- GetAuthenticationService(context).ForbidAsync(context, scheme, properties);
-
- /// <summary>
- /// Sign in a principal for the specified scheme.
- /// </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>
- /// Sign in a principal for the default authentication scheme.
- /// The default scheme for signing in can be configured using <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>
- /// Sign in a principal for the default authentication scheme.
- /// The default scheme for signing in can be configured using <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
- /// </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>
- /// Sign in a principal for the specified scheme.
- /// </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) =>
- GetAuthenticationService(context).SignInAsync(context, scheme, principal, properties);
-
- /// <summary>
- /// Sign out a principal for the default authentication scheme.
- /// The default scheme for signing out can be configured using <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>
- /// Sign out a principal for the default authentication scheme.
- /// The default scheme for signing out can be configured using <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>
- /// Sign out a principal for the specified scheme.
- /// </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>
- /// Sign out a principal for the specified scheme.
- /// </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 SignOutAsync(this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
- GetAuthenticationService(context).SignOutAsync(context, scheme, properties);
-
- /// <summary>
- /// Authenticates the request using the specified scheme and returns the value for the 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 if present.</returns>
- public static Task<string?> GetTokenAsync(this HttpContext context, string? scheme, string tokenName) =>
- GetAuthenticationService(context).GetTokenAsync(context, scheme, tokenName);
-
- /// <summary>
- /// Authenticates the request using the default authentication scheme and returns the value for the token.
- /// The default authentication scheme can be configured using <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
- /// </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 if present.</returns>
- public static Task<string?> GetTokenAsync(this HttpContext context, string tokenName) =>
- GetAuthenticationService(context).GetTokenAsync(context, tokenName);
-
- // This project doesn't reference AuthenticationServiceCollectionExtensions.AddAuthentication so we use a string.
- private static IAuthenticationService GetAuthenticationService(HttpContext context) =>
- context.RequestServices.GetService<IAuthenticationService>() ??
- throw new InvalidOperationException(Resources.FormatException_UnableToFindServices(
- nameof(IAuthenticationService),
- nameof(IServiceCollection),
- "AddAuthentication"));
- }
+ /// Authenticate the current request using the default authentication scheme.
+ /// The default authentication scheme can be configured using <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
+ /// </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>
+ /// Authenticate the current request using the specified scheme.
+ /// </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) =>
+ GetAuthenticationService(context).AuthenticateAsync(context, scheme);
+
+ /// <summary>
+ /// Challenge the current request using the specified scheme.
+ /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
+ /// </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>
+ /// Challenge the current request using the default challenge scheme.
+ /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
+ /// The default challenge scheme can be configured using <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
+ /// </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>
+ /// Challenge the current request using the default challenge scheme.
+ /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
+ /// The default challenge scheme can be configured using <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
+ /// </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>
+ /// Challenge the current request using the specified scheme.
+ /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
+ /// </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) =>
+ GetAuthenticationService(context).ChallengeAsync(context, scheme, properties);
+
+ /// <summary>
+ /// Forbid the current request using the specified scheme.
+ /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
+ /// </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>
+ /// Forbid the current request using the default forbid scheme.
+ /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
+ /// The default forbid scheme can be configured using <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
+ /// </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>
+ /// Forbid the current request using the default forbid scheme.
+ /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
+ /// The default forbid scheme can be configured using <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
+ /// </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>
+ /// Forbid the current request using the specified scheme.
+ /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
+ /// </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) =>
+ GetAuthenticationService(context).ForbidAsync(context, scheme, properties);
+
+ /// <summary>
+ /// Sign in a principal for the specified scheme.
+ /// </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>
+ /// Sign in a principal for the default authentication scheme.
+ /// The default scheme for signing in can be configured using <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>
+ /// Sign in a principal for the default authentication scheme.
+ /// The default scheme for signing in can be configured using <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
+ /// </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>
+ /// Sign in a principal for the specified scheme.
+ /// </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) =>
+ GetAuthenticationService(context).SignInAsync(context, scheme, principal, properties);
+
+ /// <summary>
+ /// Sign out a principal for the default authentication scheme.
+ /// The default scheme for signing out can be configured using <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>
+ /// Sign out a principal for the default authentication scheme.
+ /// The default scheme for signing out can be configured using <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>
+ /// Sign out a principal for the specified scheme.
+ /// </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>
+ /// Sign out a principal for the specified scheme.
+ /// </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 SignOutAsync(this HttpContext context, string? scheme, AuthenticationProperties? properties) =>
+ GetAuthenticationService(context).SignOutAsync(context, scheme, properties);
+
+ /// <summary>
+ /// Authenticates the request using the specified scheme and returns the value for the 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 if present.</returns>
+ public static Task<string?> GetTokenAsync(this HttpContext context, string? scheme, string tokenName) =>
+ GetAuthenticationService(context).GetTokenAsync(context, scheme, tokenName);
+
+ /// <summary>
+ /// Authenticates the request using the default authentication scheme and returns the value for the token.
+ /// The default authentication scheme can be configured using <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
+ /// </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 if present.</returns>
+ public static Task<string?> GetTokenAsync(this HttpContext context, string tokenName) =>
+ GetAuthenticationService(context).GetTokenAsync(context, tokenName);
+
+ // This project doesn't reference AuthenticationServiceCollectionExtensions.AddAuthentication so we use a string.
+ private static IAuthenticationService GetAuthenticationService(HttpContext context) =>
+ context.RequestServices.GetService<IAuthenticationService>() ??
+ throw new InvalidOperationException(Resources.FormatException_UnableToFindServices(
+ nameof(IAuthenticationService),
+ nameof(IServiceCollection),
+ "AddAuthentication"));
}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs b/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs
index a1c5d08e55..a56c2528c5 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs
@@ -7,98 +7,97 @@ using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Options to configure authentication.
+/// </summary>
+public class AuthenticationOptions
{
+ private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>();
+
/// <summary>
- /// Options to configure authentication.
+ /// Returns the schemes in the order they were added (important for request handling priority)
/// </summary>
- public class AuthenticationOptions
- {
- private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>();
+ public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes;
- /// <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>
+ /// 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)
+ /// <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)
{
- 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;
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (configureBuilder == null)
+ {
+ throw new ArgumentNullException(nameof(configureBuilder));
+ }
+ if (SchemeMap.ContainsKey(name))
+ {
+ throw new InvalidOperationException("Scheme already exists: " + name);
}
- /// <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<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]THandler>(string name, string? displayName) where THandler : IAuthenticationHandler
- => AddScheme(name, b =>
- {
- b.DisplayName = displayName;
- b.HandlerType = typeof(THandler);
- });
+ 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<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] 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 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.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.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.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.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; }
+ /// <summary>
+ /// Used as the default scheme by <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
+ /// </summary>
+ public string? DefaultForbidScheme { get; set; }
- /// <summary>
- /// If true, SignIn should throw if attempted with a user is not authenticated.
- /// A user is considered authenticated if <see cref="ClaimsIdentity.IsAuthenticated"/> returns <see langword="true" /> for the <see cref="ClaimsPrincipal"/> associated with the HTTP request.
- /// </summary>
- public bool RequireAuthenticatedSignIn { get; set; } = true;
- }
+ /// <summary>
+ /// If true, SignIn should throw if attempted with a user is not authenticated.
+ /// A user is considered authenticated if <see cref="ClaimsIdentity.IsAuthenticated"/> returns <see langword="true" /> for the <see cref="ClaimsPrincipal"/> associated with the HTTP request.
+ /// </summary>
+ public bool RequireAuthenticatedSignIn { get; set; } = true;
}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs b/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
index 976adb1474..e56ce335f9 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
@@ -6,224 +6,223 @@ using System.Collections.Generic;
using System.Globalization;
using System.Text.Json.Serialization;
-namespace Microsoft.AspNetCore.Authentication
+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>
- /// Dictionary used to store state values about the authentication session.
+ /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
/// </summary>
- public class AuthenticationProperties
+ /// <param name="items">State values dictionary to use.</param>
+ [JsonConstructor]
+ 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)
{
- 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>
- [JsonConstructor]
- 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);
- }
+ Items = items ?? new Dictionary<string, string?>(StringComparer.Ordinal);
+ Parameters = parameters ?? new Dictionary<string, object?>(StringComparer.Ordinal);
+ }
- /// <summary>
- /// Return a copy.
- /// </summary>
- /// <returns>A copy.</returns>
- public AuthenticationProperties Clone()
- => new AuthenticationProperties(
- new Dictionary<string, string?>(Items, StringComparer.Ordinal),
- new Dictionary<string, object?>(Parameters, 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>
- [JsonIgnore]
- public IDictionary<string, object?> Parameters { get; }
-
- /// <summary>
- /// Gets or sets whether the authentication session is persisted across multiple requests.
- /// </summary>
- [JsonIgnore]
- public bool IsPersistent
- {
- get => GetString(IsPersistentKey) != null;
- set => SetString(IsPersistentKey, value ? string.Empty : null);
- }
+ /// <summary>
+ /// Return a copy.
+ /// </summary>
+ /// <returns>A copy.</returns>
+ public AuthenticationProperties Clone()
+ => new AuthenticationProperties(
+ new Dictionary<string, string?>(Items, StringComparer.Ordinal),
+ new Dictionary<string, object?>(Parameters, StringComparer.Ordinal));
- /// <summary>
- /// Gets or sets the full path or absolute URI to be used as an http redirect response value.
- /// </summary>
- [JsonIgnore]
- public string? RedirectUri
- {
- get => GetString(RedirectUriKey);
- set => SetString(RedirectUriKey, value);
- }
+ /// <summary>
+ /// State values about the authentication session.
+ /// </summary>
+ public IDictionary<string, string?> Items { get; }
- /// <summary>
- /// Gets or sets the time at which the authentication ticket was issued.
- /// </summary>
- [JsonIgnore]
- public DateTimeOffset? IssuedUtc
- {
- get => GetDateTimeOffset(IssuedUtcKey);
- set => SetDateTimeOffset(IssuedUtcKey, value);
- }
+ /// <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>
+ [JsonIgnore]
+ public IDictionary<string, object?> Parameters { get; }
+
+ /// <summary>
+ /// Gets or sets whether the authentication session is persisted across multiple requests.
+ /// </summary>
+ [JsonIgnore]
+ 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>
+ [JsonIgnore]
+ public string? RedirectUri
+ {
+ get => GetString(RedirectUriKey);
+ set => SetString(RedirectUriKey, value);
+ }
+
+ /// <summary>
+ /// Gets or sets the time at which the authentication ticket was issued.
+ /// </summary>
+ [JsonIgnore]
+ public DateTimeOffset? IssuedUtc
+ {
+ get => GetDateTimeOffset(IssuedUtcKey);
+ set => SetDateTimeOffset(IssuedUtcKey, value);
+ }
- /// <summary>
- /// Gets or sets the time at which the authentication ticket expires.
- /// </summary>
- [JsonIgnore]
- public DateTimeOffset? ExpiresUtc
+ /// <summary>
+ /// Gets or sets the time at which the authentication ticket expires.
+ /// </summary>
+ [JsonIgnore]
+ public DateTimeOffset? ExpiresUtc
+ {
+ get => GetDateTimeOffset(ExpiresUtcKey);
+ set => SetDateTimeOffset(ExpiresUtcKey, value);
+ }
+
+ /// <summary>
+ /// Gets or sets if refreshing the authentication session should be allowed.
+ /// </summary>
+ [JsonIgnore]
+ 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 var value) ? value : null;
+ }
+
+ /// <summary>
+ /// Set or remove a string value from the <see cref="Items"/> collection.
+ /// </summary>
+ /// <param name="key">Property key.</param>
+ /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
+ public void SetString(string key, string? value)
+ {
+ if (value != null)
{
- get => GetDateTimeOffset(ExpiresUtcKey);
- set => SetDateTimeOffset(ExpiresUtcKey, value);
+ Items[key] = value;
}
-
- /// <summary>
- /// Gets or sets if refreshing the authentication session should be allowed.
- /// </summary>
- [JsonIgnore]
- public bool? AllowRefresh
+ else
{
- get => GetBool(RefreshKey);
- set => SetBool(RefreshKey, value);
+ 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>
- /// 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)
+ /// <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 nullable <see cref="bool"/> from the <see cref="Items"/> collection.
+ /// </summary>
+ /// <param name="key">Property key.</param>
+ /// <returns>Retrieved value or <see langword="null" /> if the property is not set.</returns>
+ protected bool? GetBool(string key)
+ {
+ if (Items.TryGetValue(key, out var value) && bool.TryParse(value, out var boolValue))
{
- return Items.TryGetValue(key, out var value) ? value : null;
+ return boolValue;
}
+ return null;
+ }
- /// <summary>
- /// Set or remove a string value from the <see cref="Items"/> collection.
- /// </summary>
- /// <param name="key">Property key.</param>
- /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
- public void SetString(string key, string? value)
+ /// <summary>
+ /// Set or remove a <see cref="bool"/> value in the <see cref="Items"/> collection.
+ /// </summary>
+ /// <param name="key">Property key.</param>
+ /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
+ protected void SetBool(string key, bool? value)
+ {
+ if (value.HasValue)
{
- if (value != null)
- {
- Items[key] = value;
- }
- else
- {
- Items.Remove(key);
- }
+ Items[key] = value.GetValueOrDefault().ToString();
}
-
- /// <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 nullable <see cref="bool"/> from the <see cref="Items"/> collection.
- /// </summary>
- /// <param name="key">Property key.</param>
- /// <returns>Retrieved value or <see langword="null" /> if the property is not set.</returns>
- protected bool? GetBool(string key)
+ else
{
- if (Items.TryGetValue(key, out var value) && bool.TryParse(value, out var boolValue))
- {
- return boolValue;
- }
- return null;
+ Items.Remove(key);
}
+ }
- /// <summary>
- /// Set or remove a <see cref="bool"/> value in the <see cref="Items"/> collection.
- /// </summary>
- /// <param name="key">Property key.</param>
- /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
- protected void SetBool(string key, bool? value)
+ /// <summary>
+ /// Get a nullable <see cref="DateTimeOffset"/> value from the <see cref="Items"/> collection.
+ /// </summary>
+ /// <param name="key">Property key.</param>
+ /// <returns>Retrieved value or <see langword="null" /> if the property is not set.</returns>
+ protected DateTimeOffset? GetDateTimeOffset(string key)
+ {
+ if (Items.TryGetValue(key, out var value)
+ && DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTimeOffset))
{
- if (value.HasValue)
- {
- Items[key] = value.GetValueOrDefault().ToString();
- }
- else
- {
- Items.Remove(key);
- }
+ return dateTimeOffset;
}
+ return null;
+ }
- /// <summary>
- /// Get a nullable <see cref="DateTimeOffset"/> value from the <see cref="Items"/> collection.
- /// </summary>
- /// <param name="key">Property key.</param>
- /// <returns>Retrieved value or <see langword="null" /> if the property is not set.</returns>
- protected DateTimeOffset? GetDateTimeOffset(string key)
+ /// <summary>
+ /// Sets or removes a <see cref="DateTimeOffset" /> value in the <see cref="Items"/> collection.
+ /// </summary>
+ /// <param name="key">Property key.</param>
+ /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
+ protected void SetDateTimeOffset(string key, DateTimeOffset? value)
+ {
+ if (value.HasValue)
{
- if (Items.TryGetValue(key, out var value)
- && DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTimeOffset))
- {
- return dateTimeOffset;
- }
- return null;
+ Items[key] = value.GetValueOrDefault().ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
}
-
- /// <summary>
- /// Sets or removes a <see cref="DateTimeOffset" /> value in the <see cref="Items"/> collection.
- /// </summary>
- /// <param name="key">Property key.</param>
- /// <param name="value">Value to set or <see langword="null" /> to remove the property.</param>
- protected void SetDateTimeOffset(string key, DateTimeOffset? value)
+ else
{
- if (value.HasValue)
- {
- Items[key] = value.GetValueOrDefault().ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
- }
- else
- {
- Items.Remove(key);
- }
+ Items.Remove(key);
}
}
}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs b/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs
index 6a067d8949..605b4783b7 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs
@@ -4,54 +4,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// AuthenticationSchemes assign a name to a specific <see cref="IAuthenticationHandler"/>
+/// handlerType.
+/// </summary>
+public class AuthenticationScheme
{
/// <summary>
- /// AuthenticationSchemes assign a name to a specific <see cref="IAuthenticationHandler"/>
- /// handlerType.
+ /// Initializes a new instance of <see cref="AuthenticationScheme"/>.
/// </summary>
- public class AuthenticationScheme
+ /// <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, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type handlerType)
{
- /// <summary>
- /// Initializes a new instance of <see cref="AuthenticationScheme"/>.
- /// </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, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type handlerType)
+ if (name == null)
{
- 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;
+ 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.");
}
- /// <summary>
- /// The name of the authentication scheme.
- /// </summary>
- public string Name { get; }
+ Name = name;
+ HandlerType = handlerType;
+ DisplayName = displayName;
+ }
- /// <summary>
- /// The display name for the scheme. Null is valid and used for non user facing schemes.
- /// </summary>
- public string? DisplayName { get; }
+ /// <summary>
+ /// The name of the authentication scheme.
+ /// </summary>
+ public string Name { get; }
- /// <summary>
- /// The <see cref="IAuthenticationHandler"/> type that handles this scheme.
- /// </summary>
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
- public Type HandlerType { 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>
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+ public Type HandlerType { get; }
}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs b/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
index ad35625f5e..d4c2e94791 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
@@ -4,50 +4,49 @@
using System;
using System.Diagnostics.CodeAnalysis;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used to build <see cref="AuthenticationScheme"/>s.
+/// </summary>
+public class AuthenticationSchemeBuilder
{
/// <summary>
- /// Used to build <see cref="AuthenticationScheme"/>s.
+ /// Constructor.
/// </summary>
- public class AuthenticationSchemeBuilder
+ /// <param name="name">The name of the scheme being built.</param>
+ public AuthenticationSchemeBuilder(string name)
{
- /// <summary>
- /// Constructor.
- /// </summary>
- /// <param name="name">The name of the scheme being built.</param>
- public AuthenticationSchemeBuilder(string name)
- {
- Name = name;
- }
+ Name = name;
+ }
- /// <summary>
- /// Gets the name of the scheme being built.
- /// </summary>
- public string Name { get; }
-
- /// <summary>
- /// Gets or sets the display name for the scheme being built.
- /// </summary>
- public string? DisplayName { get; set; }
-
- /// <summary>
- /// Gets or sets the <see cref="IAuthenticationHandler"/> type responsible for this scheme.
- /// </summary>
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
- public Type? HandlerType { get; set; }
-
- /// <summary>
- /// Builds the <see cref="AuthenticationScheme"/> instance.
- /// </summary>
- /// <returns>The <see cref="AuthenticationScheme"/>.</returns>
- public AuthenticationScheme Build()
- {
- if (HandlerType is null)
- {
- throw new InvalidOperationException($"{nameof(HandlerType)} must be configured to build an {nameof(AuthenticationScheme)}.");
- }
+ /// <summary>
+ /// Gets the name of the scheme being built.
+ /// </summary>
+ public string Name { get; }
+
+ /// <summary>
+ /// Gets or sets the display name for the scheme being built.
+ /// </summary>
+ public string? DisplayName { get; set; }
- return new AuthenticationScheme(Name, DisplayName, HandlerType);
+ /// <summary>
+ /// Gets or sets the <see cref="IAuthenticationHandler"/> type responsible for this scheme.
+ /// </summary>
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+ public Type? HandlerType { get; set; }
+
+ /// <summary>
+ /// Builds the <see cref="AuthenticationScheme"/> instance.
+ /// </summary>
+ /// <returns>The <see cref="AuthenticationScheme"/>.</returns>
+ public AuthenticationScheme Build()
+ {
+ if (HandlerType is null)
+ {
+ throw new InvalidOperationException($"{nameof(HandlerType)} must be configured to build an {nameof(AuthenticationScheme)}.");
}
+
+ return new AuthenticationScheme(Name, DisplayName, HandlerType);
}
}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs b/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
index 79c4913a30..87e28c7b9d 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
@@ -4,70 +4,69 @@
using System;
using System.Security.Claims;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Contains user identity information as well as additional authentication state.
+/// </summary>
+public class AuthenticationTicket
{
/// <summary>
- /// Contains user identity information as well as additional authentication state.
+ /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
/// </summary>
- public class AuthenticationTicket
+ /// <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 scheme that was responsible for this ticket.</param>
+ public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties? properties, string authenticationScheme)
{
- /// <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 scheme that was responsible for this ticket.</param>
- public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties? properties, string authenticationScheme)
+ if (principal == null)
{
- if (principal == null)
- {
- throw new ArgumentNullException(nameof(principal));
- }
-
- AuthenticationScheme = authenticationScheme;
- Principal = principal;
- Properties = properties ?? new AuthenticationProperties();
+ throw new ArgumentNullException(nameof(principal));
}
- /// <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 scheme that was responsible for this ticket.</param>
- public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme)
- : this(principal, properties: null, authenticationScheme: authenticationScheme)
- { }
+ AuthenticationScheme = authenticationScheme;
+ Principal = principal;
+ Properties = properties ?? new AuthenticationProperties();
+ }
- /// <summary>
- /// Gets the authentication scheme that was responsible for this ticket.
- /// </summary>
- public string AuthenticationScheme { get; }
+ /// <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 scheme that was responsible for this ticket.</param>
+ public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme)
+ : this(principal, properties: null, authenticationScheme: authenticationScheme)
+ { }
- /// <summary>
- /// Gets the claims-principal with authenticated user identities.
- /// </summary>
- public ClaimsPrincipal Principal { get; }
+ /// <summary>
+ /// Gets the authentication scheme that was responsible for this ticket.
+ /// </summary>
+ public string AuthenticationScheme { get; }
- /// <summary>
- /// Additional state values for the authentication session.
- /// </summary>
- public AuthenticationProperties Properties { get; }
+ /// <summary>
+ /// Gets the claims-principal with authenticated user identities.
+ /// </summary>
+ public ClaimsPrincipal Principal { get; }
+
+ /// <summary>
+ /// Additional state values for the authentication session.
+ /// </summary>
+ public AuthenticationProperties Properties { get; }
- /// <summary>
- /// Returns a copy of the ticket.
- /// </summary>
- /// <remarks>
- /// The method clones the <see cref="Principal"/> by calling <see cref="ClaimsIdentity.Clone"/> on each of the <see cref="ClaimsPrincipal.Identities"/>.
- /// </remarks>
- /// <returns>A copy of the ticket</returns>
- public AuthenticationTicket Clone()
+ /// <summary>
+ /// Returns a copy of the ticket.
+ /// </summary>
+ /// <remarks>
+ /// The method clones the <see cref="Principal"/> by calling <see cref="ClaimsIdentity.Clone"/> on each of the <see cref="ClaimsPrincipal.Identities"/>.
+ /// </remarks>
+ /// <returns>A copy of the ticket</returns>
+ public AuthenticationTicket Clone()
+ {
+ var principal = new ClaimsPrincipal();
+ foreach (var identity in Principal.Identities)
{
- var principal = new ClaimsPrincipal();
- foreach (var identity in Principal.Identities)
- {
- principal.AddIdentity(identity.Clone());
- }
- return new AuthenticationTicket(principal, Properties.Clone(), AuthenticationScheme);
+ principal.AddIdentity(identity.Clone());
}
+ return new AuthenticationTicket(principal, Properties.Clone(), AuthenticationScheme);
}
}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs b/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs
index 2ba7a5bd35..51f7ee9e55 100644
--- a/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs
@@ -2,21 +2,20 @@
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Name/Value representing a token.
+/// </summary>
+public class AuthenticationToken
{
/// <summary>
- /// Name/Value representing a token.
+ /// Name.
/// </summary>
- public class AuthenticationToken
- {
- /// <summary>
- /// Name.
- /// </summary>
- public string Name { get; set; } = default!;
+ public string Name { get; set; } = default!;
- /// <summary>
- /// Value.
- /// </summary>
- public string Value { get; set; } = default!;
- }
+ /// <summary>
+ /// Value.
+ /// </summary>
+ public string Value { get; set; } = default!;
}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticateResultFeature.cs b/src/Http/Authentication.Abstractions/src/IAuthenticateResultFeature.cs
index 6f5e264ac0..88fdf3786d 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticateResultFeature.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticateResultFeature.cs
@@ -3,17 +3,16 @@
using Microsoft.AspNetCore.Http.Features.Authentication;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used to capture the <see cref="AuthenticateResult"/> from the authorization middleware.
+/// </summary>
+public interface IAuthenticateResultFeature
{
/// <summary>
- /// Used to capture the <see cref="AuthenticateResult"/> from the authorization middleware.
+ /// The <see cref="AuthenticateResult"/> from the authorization middleware.
+ /// Set to null if the <see cref="IHttpAuthenticationFeature.User"/> property is set after the authorization middleware.
/// </summary>
- public interface IAuthenticateResultFeature
- {
- /// <summary>
- /// The <see cref="AuthenticateResult"/> from the authorization middleware.
- /// Set to null if the <see cref="IHttpAuthenticationFeature.User"/> property is set after the authorization middleware.
- /// </summary>
- AuthenticateResult? AuthenticateResult { get; set; }
- }
+ AuthenticateResult? AuthenticateResult { get; set; }
}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs
index 9b71da47ce..391b3fe43b 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs
@@ -3,21 +3,20 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+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>
- /// Used to capture path info so redirects can be computed properly within an app.Map().
+ /// The original path base.
/// </summary>
- public interface IAuthenticationFeature
- {
- /// <summary>
- /// The original path base.
- /// </summary>
- PathString OriginalPathBase { get; set; }
+ PathString OriginalPathBase { get; set; }
- /// <summary>
- /// The original path.
- /// </summary>
- PathString OriginalPath { 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
index ad2816be0f..dc1056edc6 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs
@@ -4,36 +4,35 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Created per request to handle authentication for a particular scheme.
+/// </summary>
+public interface IAuthenticationHandler
{
/// <summary>
- /// Created per request to handle authentication for a particular scheme.
+ /// Initialize the authentication handler. The handler should initialize anything it needs from the request and scheme as part of this method.
/// </summary>
- public interface IAuthenticationHandler
- {
- /// <summary>
- /// Initialize the authentication handler. The handler should initialize anything it needs from the request and scheme as part of this method.
- /// </summary>
- /// <param name="scheme">The <see cref="AuthenticationScheme"/> scheme.</param>
- /// <param name="context">The <see cref="HttpContext"/> context.</param>
- Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
+ /// <param name="scheme">The <see cref="AuthenticationScheme"/> scheme.</param>
+ /// <param name="context">The <see cref="HttpContext"/> context.</param>
+ Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
- /// <summary>
- /// Authenticate the current request.
- /// </summary>
- /// <returns>The <see cref="AuthenticateResult"/> result.</returns>
- Task<AuthenticateResult> AuthenticateAsync();
+ /// <summary>
+ /// Authenticate the current request.
+ /// </summary>
+ /// <returns>The <see cref="AuthenticateResult"/> result.</returns>
+ Task<AuthenticateResult> AuthenticateAsync();
- /// <summary>
- /// Challenge the current request.
- /// </summary>
- /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
- Task ChallengeAsync(AuthenticationProperties? properties);
+ /// <summary>
+ /// Challenge the current request.
+ /// </summary>
+ /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
+ Task ChallengeAsync(AuthenticationProperties? properties);
- /// <summary>
- /// Forbid the current request.
- /// </summary>
- /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
- Task ForbidAsync(AuthenticationProperties? properties);
- }
+ /// <summary>
+ /// Forbid the current request.
+ /// </summary>
+ /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
+ Task ForbidAsync(AuthenticationProperties? properties);
}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs
index 127216180b..4626bbd422 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs
@@ -4,19 +4,18 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Provides the appropriate IAuthenticationHandler instance for the authenticationScheme and request.
+/// </summary>
+public interface IAuthenticationHandlerProvider
{
/// <summary>
- /// Provides the appropriate IAuthenticationHandler instance for the authenticationScheme and request.
+ /// Returns the handler instance that will be used.
/// </summary>
- public interface IAuthenticationHandlerProvider
- {
- /// <summary>
- /// Returns the handler instance that will be used.
- /// </summary>
- /// <param name="context">The <see cref="HttpContext"/>.</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);
- }
+ /// <param name="context">The <see cref="HttpContext"/>.</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);
}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs
index de0ef1a755..1f1583f6ab 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs
@@ -3,23 +3,21 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used to determine if a handler wants to participate in request processing.
+/// </summary>
+public interface IAuthenticationRequestHandler : IAuthenticationHandler
{
/// <summary>
- /// Used to determine if a handler wants to participate in request processing.
+ /// Gets a value that determines if the request should stop being processed.
+ /// <para>
+ /// This feature is supported by the Authentication middleware
+ /// which does not invoke any subsequent <see cref="IAuthenticationHandler"/> or middleware configured in the request pipeline
+ /// if the handler returns <see langword="true" />.
+ /// </para>
/// </summary>
- public interface IAuthenticationRequestHandler : IAuthenticationHandler
- {
- /// <summary>
- /// Gets a value that determines if the request should stop being processed.
- /// <para>
- /// This feature is supported by the Authentication middleware
- /// which does not invoke any subsequent <see cref="IAuthenticationHandler"/> or middleware configured in the request pipeline
- /// if the handler returns <see langword="true" />.
- /// </para>
- /// </summary>
- /// <returns><see langword="true" /> if request processing should stop.</returns>
- Task<bool> HandleRequestAsync();
- }
-
+ /// <returns><see langword="true" /> if request processing should stop.</returns>
+ Task<bool> HandleRequestAsync();
}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs
index cac9dc9736..f74146a8a0 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs
@@ -5,99 +5,99 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Responsible for managing what authenticationSchemes are supported.
+/// </summary>
+public interface IAuthenticationSchemeProvider
{
/// <summary>
- /// Responsible for managing what authenticationSchemes are supported.
+ /// Returns all currently registered <see cref="AuthenticationScheme"/>s.
/// </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();
+ /// <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 <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.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.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.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.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>
+ /// 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>
+ /// Registers a scheme for use by <see cref="IAuthenticationService"/>.
+ /// </summary>
+ /// <param name="scheme">The scheme.</param>
+ void AddScheme(AuthenticationScheme scheme);
- /// <summary>
- /// Registers a scheme for use by <see cref="IAuthenticationService"/>.
- /// </summary>
- /// <param name="scheme">The scheme.</param>
- /// <returns>true if the scheme was added successfully.</returns>
- bool TryAddScheme(AuthenticationScheme scheme)
+ /// <summary>
+ /// Registers a scheme for use by <see cref="IAuthenticationService"/>.
+ /// </summary>
+ /// <param name="scheme">The scheme.</param>
+ /// <returns>true if the scheme was added successfully.</returns>
+ bool TryAddScheme(AuthenticationScheme scheme)
+ {
+ try
+ {
+ AddScheme(scheme);
+ return true;
+ }
+ catch
{
- try
- {
- AddScheme(scheme);
- return true;
- }
- catch {
- return false;
- }
+ return false;
}
+ }
- /// <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>
+ /// 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();
- }
+ /// <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();
}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
index b83e44efd1..523b0409f1 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
@@ -5,58 +5,57 @@ using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used to provide authentication.
+/// </summary>
+public interface IAuthenticationService
{
/// <summary>
- /// Used to provide authentication.
+ /// Authenticate for the specified authentication scheme.
/// </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);
+ /// <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.
- /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
- /// </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>
+ /// Challenge the specified authentication scheme.
+ /// An authentication challenge can be issued when an unauthenticated user requests an endpoint that requires authentication.
+ /// </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.
- /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
- /// </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>
+ /// Forbids the specified authentication scheme.
+ /// Forbid is used when an authenticated user attempts to access a resource they are not permitted to access.
+ /// </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 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);
- }
+ /// <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
index da767ef7c4..3053b58127 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs
@@ -4,19 +4,18 @@
using System.Security.Claims;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used to determine if a handler supports SignIn.
+/// </summary>
+public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
{
/// <summary>
- /// Used to determine if a handler supports SignIn.
+ /// Handle sign in.
/// </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);
- }
+ /// <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
index 68f8dc36f2..34a5ef379c 100644
--- a/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs
@@ -3,19 +3,17 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used to determine if a handler supports SignOut.
+/// </summary>
+public interface IAuthenticationSignOutHandler : IAuthenticationHandler
{
/// <summary>
- /// Used to determine if a handler supports SignOut.
+ /// Signout behavior.
/// </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);
- }
-
+ /// <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
index 664d5ec006..339d7feb86 100644
--- a/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs
+++ b/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs
@@ -4,20 +4,19 @@
using System.Security.Claims;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Used by the <see cref="IAuthenticationService"/> for claims transformation.
+/// </summary>
+public interface IClaimsTransformation
{
/// <summary>
- /// Used by the <see cref="IAuthenticationService"/> for claims transformation.
+ /// 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>
- 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
+ /// <param name="principal">The <see cref="ClaimsPrincipal"/> to transform.</param>
+ /// <returns>The transformed principal.</returns>
+ Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
+}
diff --git a/src/Http/Authentication.Abstractions/src/TokenExtensions.cs b/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
index ec8504bfaf..8bdc3d644d 100644
--- a/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
+++ b/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
@@ -6,167 +6,166 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Extension methods for storing authentication tokens in <see cref="AuthenticationProperties"/>.
+/// </summary>
+public static class AuthenticationTokenExtensions
{
+ private const string TokenNamesKey = ".TokenNames";
+ private const string TokenKeyPrefix = ".Token.";
+
/// <summary>
- /// Extension methods for storing authentication tokens in <see cref="AuthenticationProperties"/>.
+ /// Stores a set of authentication tokens, after removing any old tokens.
/// </summary>
- public static class AuthenticationTokenExtensions
+ /// <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)
{
- private const string TokenNamesKey = ".TokenNames";
- private const 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)
{
- 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)
- {
- if (token.Name is null)
- {
- throw new ArgumentNullException(nameof(tokens), "Token name cannot be null.");
- }
+ throw new ArgumentNullException(nameof(properties));
+ }
+ if (tokens == null)
+ {
+ throw new ArgumentNullException(nameof(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());
- }
+ // Clear old tokens first
+ var oldTokens = properties.GetTokens();
+ foreach (var t in oldTokens)
+ {
+ properties.Items.Remove(TokenKeyPrefix + t.Name);
}
+ properties.Items.Remove(TokenNamesKey);
- /// <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)
+ var tokenNames = new List<string>();
+ foreach (var token in tokens)
{
- if (properties == null)
- {
- throw new ArgumentNullException(nameof(properties));
- }
- if (tokenName == null)
+ if (token.Name is null)
{
- throw new ArgumentNullException(nameof(tokenName));
+ throw new ArgumentNullException(nameof(tokens), "Token name cannot be null.");
}
- var tokenKey = TokenKeyPrefix + tokenName;
+ // 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());
+ }
+ }
- return properties.Items.TryGetValue(tokenKey, out var value) ? value : null;
+ /// <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;
- /// <summary>
- /// Updates the value of a token if already present.
- /// </summary>
- /// <param name="properties">The <see cref="AuthenticationProperties"/> to update.</param>
- /// <param name="tokenName">The token name.</param>
- /// <param name="tokenValue">The token value.</param>
- /// <returns><see langword="true"/> if the token was updated, otherwise <see langword="false"/>.</returns>
- public static bool UpdateTokenValue(this AuthenticationProperties properties, string tokenName, string tokenValue)
+ return properties.Items.TryGetValue(tokenKey, out var value) ? value : null;
+ }
+
+ /// <summary>
+ /// Updates the value of a token if already present.
+ /// </summary>
+ /// <param name="properties">The <see cref="AuthenticationProperties"/> to update.</param>
+ /// <param name="tokenName">The token name.</param>
+ /// <param name="tokenValue">The token value.</param>
+ /// <returns><see langword="true"/> if the token was updated, otherwise <see langword="false"/>.</returns>
+ public static bool UpdateTokenValue(this AuthenticationProperties properties, string tokenName, string tokenValue)
+ {
+ if (properties == null)
{
- if (properties == null)
- {
- throw new ArgumentNullException(nameof(properties));
- }
- if (tokenName == null)
- {
- throw new ArgumentNullException(nameof(tokenName));
- }
+ 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;
+ var tokenKey = TokenKeyPrefix + tokenName;
+ if (!properties.Items.ContainsKey(tokenKey))
+ {
+ return false;
}
+ properties.Items[tokenKey] = tokenValue;
+ return true;
+ }
- /// <summary>
- /// Returns all of the <see cref="AuthenticationToken"/> instances contained in the properties.
- /// </summary>
- /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
- /// <returns>The authentication tokens.</returns>
- public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties)
+ /// <summary>
+ /// Returns all of the <see cref="AuthenticationToken"/> instances contained in the properties.
+ /// </summary>
+ /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+ /// <returns>The authentication tokens.</returns>
+ public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties)
+ {
+ if (properties == null)
{
- if (properties == null)
- {
- throw new ArgumentNullException(nameof(properties));
- }
+ throw new ArgumentNullException(nameof(properties));
+ }
- var tokens = new List<AuthenticationToken>();
- if (properties.Items.TryGetValue(TokenNamesKey, out var value) && !string.IsNullOrEmpty(value))
+ var tokens = new List<AuthenticationToken>();
+ if (properties.Items.TryGetValue(TokenNamesKey, out var value) && !string.IsNullOrEmpty(value))
+ {
+ var tokenNames = value.Split(';');
+ foreach (var name in tokenNames)
{
- var tokenNames = value.Split(';');
- foreach (var name in tokenNames)
+ var token = properties.GetTokenValue(name);
+ if (token != null)
{
- var token = properties.GetTokenValue(name);
- if (token != null)
- {
- tokens.Add(new AuthenticationToken { Name = name, Value = token });
- }
+ tokens.Add(new AuthenticationToken { Name = name, Value = token });
}
}
-
- return tokens;
}
- /// <summary>
- /// Authenticates the request using the specified authentication scheme and returns the value for the 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 if present.</returns>
- public static Task<string?> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName)
- => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
-
- /// <summary>
- /// Authenticates the request using the specified authentication scheme and returns the value for the 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 if present.</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));
- }
+ return tokens;
+ }
+
+ /// <summary>
+ /// Authenticates the request using the specified authentication scheme and returns the value for the 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 if present.</returns>
+ public static Task<string?> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName)
+ => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
- var result = await auth.AuthenticateAsync(context, scheme);
- return result?.Properties?.GetTokenValue(tokenName);
+ /// <summary>
+ /// Authenticates the request using the specified authentication scheme and returns the value for the 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 if present.</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);
}
}
diff --git a/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs b/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
index 4af2730a1b..9384e6ce63 100644
--- a/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
+++ b/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
@@ -5,52 +5,52 @@ using System;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection.Extensions;
-namespace Microsoft.Extensions.DependencyInjection
+namespace Microsoft.Extensions.DependencyInjection;
+
+/// <summary>
+/// Extension methods for setting up authentication services in an <see cref="IServiceCollection" />.
+/// </summary>
+public static class AuthenticationCoreServiceCollectionExtensions
{
/// <summary>
- /// Extension methods for setting up authentication services in an <see cref="IServiceCollection" />.
+ /// Add core authentication services needed for <see cref="IAuthenticationService"/>.
/// </summary>
- public static class AuthenticationCoreServiceCollectionExtensions
+ /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+ /// <returns>The service collection.</returns>
+ public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
{
- /// <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)
{
- 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;
+ throw new ArgumentNullException(nameof(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));
- }
+ 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;
+ }
- if (configureOptions == null)
- {
- throw new ArgumentNullException(nameof(configureOptions));
- }
+ /// <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));
+ }
- services.AddAuthenticationCore();
- services.Configure(configureOptions);
- return 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
index 1dc87f9da1..21bb7f9206 100644
--- a/src/Http/Authentication.Core/src/AuthenticationFeature.cs
+++ b/src/Http/Authentication.Core/src/AuthenticationFeature.cs
@@ -3,21 +3,20 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Authentication
+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>
- /// Used to capture path info so redirects can be computed properly within an app.Map().
+ /// The original path base.
/// </summary>
- public class AuthenticationFeature : IAuthenticationFeature
- {
- /// <summary>
- /// The original path base.
- /// </summary>
- public PathString OriginalPathBase { get; set; }
+ public PathString OriginalPathBase { get; set; }
- /// <summary>
- /// The original path.
- /// </summary>
- public PathString OriginalPath { 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
index b2720b03c7..7eaa229506 100644
--- a/src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs
+++ b/src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs
@@ -7,57 +7,56 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Implementation of <see cref="IAuthenticationHandlerProvider"/>.
+/// </summary>
+public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
{
/// <summary>
- /// Implementation of <see cref="IAuthenticationHandlerProvider"/>.
+ /// Constructor.
/// </summary>
- public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
+ /// <param name="schemes">The <see cref="IAuthenticationHandlerProvider"/>.</param>
+ public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
{
- /// <summary>
- /// Constructor.
- /// </summary>
- /// <param name="schemes">The <see cref="IAuthenticationHandlerProvider"/>.</param>
- public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
- {
- Schemes = schemes;
- }
+ Schemes = schemes;
+ }
- /// <summary>
- /// The <see cref="IAuthenticationHandlerProvider"/>.
- /// </summary>
- public IAuthenticationSchemeProvider Schemes { get; }
+ /// <summary>
+ /// The <see cref="IAuthenticationHandlerProvider"/>.
+ /// </summary>
+ public IAuthenticationSchemeProvider Schemes { get; }
- // handler instance cache, need to initialize once per request
- private readonly Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);
+ // handler instance cache, need to initialize once per request
+ private readonly 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)
+ /// <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.TryGetValue(authenticationScheme, out var value))
{
- if (_handlerMap.TryGetValue(authenticationScheme, out var value))
- {
- return value;
- }
+ return value;
+ }
- 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;
+ 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
index cefbd900d7..4025983651 100644
--- a/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
+++ b/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
@@ -8,201 +8,200 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Implements <see cref="IAuthenticationSchemeProvider"/>.
+/// </summary>
+public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
{
/// <summary>
- /// Implements <see cref="IAuthenticationSchemeProvider"/>.
+ /// Creates an instance of <see cref="AuthenticationSchemeProvider"/>
+ /// using the specified <paramref name="options"/>,
/// </summary>
- public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
+ /// <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"/>,
- /// </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);
}
+ }
- /// <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;
+ private readonly AuthenticationOptions _options;
+ private readonly object _lock = new object();
- _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
- _requestHandlers = new List<AuthenticationScheme>();
+ private readonly IDictionary<string, AuthenticationScheme> _schemes;
+ private readonly List<AuthenticationScheme> _requestHandlers;
+ // Used as a safe return value for enumeration apis
+ private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();
+ private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();
- foreach (var builder in _options.Schemes)
- {
- var scheme = builder.Build();
- AddScheme(scheme);
- }
- }
+ 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 supports 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(_requestHandlersCopy);
- private readonly AuthenticationOptions _options;
- private readonly object _lock = new object();
-
- private readonly IDictionary<string, AuthenticationScheme> _schemes;
- private readonly List<AuthenticationScheme> _requestHandlers;
- // Used as a safe return value for enumeration apis
- private IEnumerable<AuthenticationScheme> _schemesCopy = Array.Empty<AuthenticationScheme>();
- private IEnumerable<AuthenticationScheme> _requestHandlersCopy = Array.Empty<AuthenticationScheme>();
-
- 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 supports 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(_requestHandlersCopy);
-
- /// <summary>
- /// Registers a scheme for use by <see cref="IAuthenticationService"/>.
- /// </summary>
- /// <param name="scheme">The scheme.</param>
- /// <returns>true if the scheme was added successfully.</returns>
- public virtual bool TryAddScheme(AuthenticationScheme scheme)
+ /// <summary>
+ /// Registers a scheme for use by <see cref="IAuthenticationService"/>.
+ /// </summary>
+ /// <param name="scheme">The scheme.</param>
+ /// <returns>true if the scheme was added successfully.</returns>
+ public virtual bool TryAddScheme(AuthenticationScheme scheme)
+ {
+ if (_schemes.ContainsKey(scheme.Name))
+ {
+ return false;
+ }
+ lock (_lock)
{
if (_schemes.ContainsKey(scheme.Name))
{
return false;
}
- lock (_lock)
+ if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
{
- if (_schemes.ContainsKey(scheme.Name))
- {
- return false;
- }
- if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
- {
- _requestHandlers.Add(scheme);
- _requestHandlersCopy = _requestHandlers.ToArray();
- }
- _schemes[scheme.Name] = scheme;
- _schemesCopy = _schemes.Values.ToArray();
- return true;
+ _requestHandlers.Add(scheme);
+ _requestHandlersCopy = _requestHandlers.ToArray();
}
+ _schemes[scheme.Name] = scheme;
+ _schemesCopy = _schemes.Values.ToArray();
+ return true;
}
+ }
- /// <summary>
- /// Registers a scheme for use by <see cref="IAuthenticationService"/>.
- /// </summary>
- /// <param name="scheme">The scheme.</param>
- public virtual void AddScheme(AuthenticationScheme scheme)
+ /// <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))
{
- if (_schemes.ContainsKey(scheme.Name))
+ throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+ }
+ lock (_lock)
+ {
+ if (!TryAddScheme(scheme))
{
throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
}
- lock (_lock)
- {
- if (!TryAddScheme(scheme))
- {
- throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
- }
- }
}
+ }
- /// <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)
+ /// <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))
{
- if (!_schemes.ContainsKey(name))
- {
- return;
- }
- lock (_lock)
+ return;
+ }
+ lock (_lock)
+ {
+ if (_schemes.ContainsKey(name))
{
- if (_schemes.ContainsKey(name))
+ var scheme = _schemes[name];
+ if (_requestHandlers.Remove(scheme))
{
- var scheme = _schemes[name];
- if (_requestHandlers.Remove(scheme))
- {
- _requestHandlersCopy = _requestHandlers.ToArray();
- }
- _schemes.Remove(name);
- _schemesCopy = _schemes.Values.ToArray();
+ _requestHandlersCopy = _requestHandlers.ToArray();
}
+ _schemes.Remove(name);
+ _schemesCopy = _schemes.Values.ToArray();
}
}
-
- /// <inheritdoc />
- public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
- => Task.FromResult(_schemesCopy);
}
+
+ /// <inheritdoc />
+ public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
+ => Task.FromResult(_schemesCopy);
}
diff --git a/src/Http/Authentication.Core/src/AuthenticationService.cs b/src/Http/Authentication.Core/src/AuthenticationService.cs
index 9e691585e5..8444ffcf51 100644
--- a/src/Http/Authentication.Core/src/AuthenticationService.cs
+++ b/src/Http/Authentication.Core/src/AuthenticationService.cs
@@ -9,332 +9,331 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Implements <see cref="IAuthenticationService"/>.
+/// </summary>
+public class AuthenticationService : IAuthenticationService
{
+ private HashSet<ClaimsPrincipal>? _transformCache;
+
/// <summary>
- /// Implements <see cref="IAuthenticationService"/>.
+ /// Constructor.
/// </summary>
- public class AuthenticationService : IAuthenticationService
+ /// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
+ /// <param name="handlers">The <see cref="IAuthenticationHandlerProvider"/>.</param>
+ /// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
+ /// <param name="options">The <see cref="AuthenticationOptions"/>.</param>
+ public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options)
{
- private HashSet<ClaimsPrincipal>? _transformCache;
-
- /// <summary>
- /// Constructor.
- /// </summary>
- /// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
- /// <param name="handlers">The <see cref="IAuthenticationHandlerProvider"/>.</param>
- /// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
- /// <param name="options">The <see cref="AuthenticationOptions"/>.</param>
- public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform, IOptions<AuthenticationOptions> options)
- {
- Schemes = schemes;
- Handlers = handlers;
- Transform = transform;
- Options = options.Value;
- }
+ Schemes = schemes;
+ Handlers = handlers;
+ Transform = transform;
+ Options = options.Value;
+ }
- /// <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>
- /// The <see cref="AuthenticationOptions"/>.
- /// </summary>
- public AuthenticationOptions Options { 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. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
- }
- }
+ /// <summary>
+ /// Used to lookup AuthenticationSchemes.
+ /// </summary>
+ public IAuthenticationSchemeProvider Schemes { get; }
- var handler = await Handlers.GetHandlerAsync(context, scheme);
- if (handler == null)
- {
- throw await CreateMissingHandlerException(scheme);
- }
+ /// <summary>
+ /// Used to resolve IAuthenticationHandler instances.
+ /// </summary>
+ public IAuthenticationHandlerProvider Handlers { get; }
- // Handlers should not return null, but we'll be tolerant of null values for legacy reasons.
- var result = (await handler.AuthenticateAsync()) ?? AuthenticateResult.NoResult();
+ /// <summary>
+ /// Used for claims transformation.
+ /// </summary>
+ public IClaimsTransformation Transform { get; }
- if (result.Succeeded)
- {
- var principal = result.Principal!;
- var doTransform = true;
- _transformCache ??= new HashSet<ClaimsPrincipal>();
- if (_transformCache.Contains(principal))
- {
- doTransform = false;
- }
-
- if (doTransform)
- {
- principal = await Transform.TransformAsync(principal);
- _transformCache.Add(principal);
- }
- return AuthenticateResult.Success(new AuthenticationTicket(principal, result.Properties, result.Ticket!.AuthenticationScheme));
- }
- return result;
- }
+ /// <summary>
+ /// The <see cref="AuthenticationOptions"/>.
+ /// </summary>
+ public AuthenticationOptions Options { get; }
- /// <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)
+ /// <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)
{
- var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
- scheme = defaultChallengeScheme?.Name;
- if (scheme == null)
- {
- throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
- }
- }
-
- var handler = await Handlers.GetHandlerAsync(context, scheme);
- if (handler == null)
- {
- throw await CreateMissingHandlerException(scheme);
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
+ }
- await handler.ChallengeAsync(properties);
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw await CreateMissingHandlerException(scheme);
}
- /// <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)
+ // Handlers should not return null, but we'll be tolerant of null values for legacy reasons.
+ var result = (await handler.AuthenticateAsync()) ?? AuthenticateResult.NoResult();
+
+ if (result.Succeeded)
{
- if (scheme == null)
+ var principal = result.Principal!;
+ var doTransform = true;
+ _transformCache ??= new HashSet<ClaimsPrincipal>();
+ if (_transformCache.Contains(principal))
{
- var defaultForbidScheme = await Schemes.GetDefaultForbidSchemeAsync();
- scheme = defaultForbidScheme?.Name;
- if (scheme == null)
- {
- throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
- }
+ doTransform = false;
}
- var handler = await Handlers.GetHandlerAsync(context, scheme);
- if (handler == null)
+ if (doTransform)
{
- throw await CreateMissingHandlerException(scheme);
+ principal = await Transform.TransformAsync(principal);
+ _transformCache.Add(principal);
}
-
- await handler.ForbidAsync(properties);
+ return AuthenticateResult.Success(new AuthenticationTicket(principal, result.Properties, result.Ticket!.AuthenticationScheme));
}
+ return result;
+ }
- /// <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)
+ /// <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)
{
- if (principal == null)
+ var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
+ scheme = defaultChallengeScheme?.Name;
+ if (scheme == null)
{
- throw new ArgumentNullException(nameof(principal));
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
+ }
- if (Options.RequireAuthenticatedSignIn)
- {
- if (principal.Identity == null)
- {
- throw new InvalidOperationException("SignInAsync when principal.Identity == null is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");
- }
- if (!principal.Identity.IsAuthenticated)
- {
- throw new InvalidOperationException("SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");
- }
- }
+ 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)
{
- var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
- scheme = defaultScheme?.Name;
- if (scheme == null)
- {
- throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
- }
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
+ }
- var handler = await Handlers.GetHandlerAsync(context, scheme);
- if (handler == null)
- {
- throw await CreateMissingSignInHandlerException(scheme);
- }
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw await CreateMissingHandlerException(scheme);
+ }
- var signInHandler = handler as IAuthenticationSignInHandler;
- if (signInHandler == null)
- {
- throw await CreateMismatchedSignInHandlerException(scheme, handler);
- }
+ await handler.ForbidAsync(properties);
+ }
- await signInHandler.SignInAsync(principal, 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));
}
- /// <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 (Options.RequireAuthenticatedSignIn)
{
- if (scheme == null)
+ if (principal.Identity == 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. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
- }
+ throw new InvalidOperationException("SignInAsync when principal.Identity == null is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");
}
-
- var handler = await Handlers.GetHandlerAsync(context, scheme);
- if (handler == null)
+ if (!principal.Identity.IsAuthenticated)
{
- throw await CreateMissingSignOutHandlerException(scheme);
+ throw new InvalidOperationException("SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.");
}
+ }
- var signOutHandler = handler as IAuthenticationSignOutHandler;
- if (signOutHandler == null)
+ if (scheme == null)
+ {
+ var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
+ scheme = defaultScheme?.Name;
+ if (scheme == null)
{
- throw await CreateMismatchedSignOutHandlerException(scheme, handler);
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
+ }
- await signOutHandler.SignOutAsync(properties);
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
+ {
+ throw await CreateMissingSignInHandlerException(scheme);
}
- private async Task<Exception> CreateMissingHandlerException(string scheme)
+ var signInHandler = handler as IAuthenticationSignInHandler;
+ if (signInHandler == null)
{
- var schemes = string.Join(", ", (await Schemes.GetAllSchemesAsync()).Select(sch => sch.Name));
+ throw await CreateMismatchedSignInHandlerException(scheme, handler);
+ }
- var footer = $" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{scheme}\",...)?";
+ await signInHandler.SignInAsync(principal, properties);
+ }
- if (string.IsNullOrEmpty(schemes))
+ /// <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)
{
- return new InvalidOperationException(
- $"No authentication handlers are registered." + footer);
+ throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).");
}
-
- return new InvalidOperationException(
- $"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);
}
- private async Task<string> GetAllSignInSchemeNames()
+ var handler = await Handlers.GetHandlerAsync(context, scheme);
+ if (handler == null)
{
- return string.Join(", ", (await Schemes.GetAllSchemesAsync())
- .Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
- .Select(sch => sch.Name));
+ throw await CreateMissingSignOutHandlerException(scheme);
}
- private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
+ var signOutHandler = handler as IAuthenticationSignOutHandler;
+ if (signOutHandler == null)
{
- var schemes = await GetAllSignInSchemeNames();
+ throw await CreateMismatchedSignOutHandlerException(scheme, handler);
+ }
- // CookieAuth is the only implementation of sign-in.
- var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?";
+ await signOutHandler.SignOutAsync(properties);
+ }
- if (string.IsNullOrEmpty(schemes))
- {
- return new InvalidOperationException(
- $"No sign-in authentication handlers are registered." + footer);
- }
+ 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 sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);
+ $"No authentication handlers are registered." + footer);
}
- private async Task<Exception> CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler)
- {
- var schemes = await GetAllSignInSchemeNames();
+ return new InvalidOperationException(
+ $"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);
+ }
- var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";
+ private async Task<string> GetAllSignInSchemeNames()
+ {
+ return string.Join(", ", (await Schemes.GetAllSchemesAsync())
+ .Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
+ .Select(sch => sch.Name));
+ }
- if (string.IsNullOrEmpty(schemes))
- {
- // CookieAuth is the only implementation of sign-in.
- return new InvalidOperationException(mismatchError
- + $"Did you forget to call AddAuthentication().AddCookie(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
- }
+ private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
+ {
+ var schemes = await GetAllSignInSchemeNames();
- return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}.");
- }
+ // CookieAuth is the only implementation of sign-in.
+ var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?";
- private async Task<string> GetAllSignOutSchemeNames()
+ if (string.IsNullOrEmpty(schemes))
{
- return string.Join(", ", (await Schemes.GetAllSchemesAsync())
- .Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))
- .Select(sch => sch.Name));
+ return new InvalidOperationException(
+ $"No sign-in authentication handlers are registered." + footer);
}
- private async Task<Exception> CreateMissingSignOutHandlerException(string scheme)
- {
- var schemes = await GetAllSignOutSchemeNames();
+ return new InvalidOperationException(
+ $"No sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);
+ }
- var footer = $" Did you forget to call AddAuthentication().AddCookie(\"{scheme}\",...)?";
+ private async Task<Exception> CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler)
+ {
+ var schemes = await GetAllSignInSchemeNames();
- 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);
- }
+ var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";
- return new InvalidOperationException(
- $"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);
+ if (string.IsNullOrEmpty(schemes))
+ {
+ // CookieAuth is the only implementation of sign-in.
+ return new InvalidOperationException(mismatchError
+ + $"Did you forget to call AddAuthentication().AddCookie(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
}
- private async Task<Exception> CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler)
+ 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().AddCookie(\"{scheme}\",...)?";
+
+ if (string.IsNullOrEmpty(schemes))
{
- var schemes = await GetAllSignOutSchemeNames();
+ // 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);
+ }
- var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for {nameof(SignOutAsync)}. ";
+ return new InvalidOperationException(
+ $"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);
+ }
- 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().AddCookie(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");
- }
+ private async Task<Exception> CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler)
+ {
+ var schemes = await GetAllSignOutSchemeNames();
- return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");
+ 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().AddCookie(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");
}
+
+ return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");
}
}
diff --git a/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs b/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs
index 68510f8a69..dda2690a30 100644
--- a/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs
+++ b/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs
@@ -4,21 +4,20 @@
using System.Security.Claims;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Authentication
+namespace Microsoft.AspNetCore.Authentication;
+
+/// <summary>
+/// Default claims transformation is a no-op.
+/// </summary>
+public class NoopClaimsTransformation : IClaimsTransformation
{
/// <summary>
- /// Default claims transformation is a no-op.
+ /// Returns the principal unchanged.
/// </summary>
- public class NoopClaimsTransformation : IClaimsTransformation
+ /// <param name="principal">The user.</param>
+ /// <returns>The principal unchanged.</returns>
+ public virtual Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
- /// <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);
- }
+ return Task.FromResult(principal);
}
}
diff --git a/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs b/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
index 725803873c..bbe00de0bd 100644
--- a/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
+++ b/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
@@ -8,379 +8,379 @@ using System.Linq;
using System.Text.Json;
using Xunit;
-namespace Microsoft.AspNetCore.Authentication.Core.Test
+namespace Microsoft.AspNetCore.Authentication.Core.Test;
+
+public class AuthenticationPropertiesTests
{
- public class AuthenticationPropertiesTests
+ [Fact]
+ public void Clone_Copies()
{
- [Fact]
- public void Clone_Copies()
+ var items = new Dictionary<string, string?>
{
- var items = new Dictionary<string, string?>
- {
- ["foo"] = "bar",
- };
- var value = "value";
- var parameters = new Dictionary<string, object?>
- {
- ["foo2"] = value,
- };
- var props = new AuthenticationProperties(items, parameters);
- Assert.Same(items, props.Items);
- Assert.Same(parameters, props.Parameters);
- var copy = props.Clone();
- Assert.NotSame(props.Items, copy.Items);
- Assert.NotSame(props.Parameters, copy.Parameters);
- // Objects in the dictionaries will still be the same
- Assert.Equal(props.Items, copy.Items);
- Assert.Equal(props.Parameters, copy.Parameters);
- props.Items["change"] = "good";
- props.Parameters["something"] = "bad";
- Assert.NotEqual(props.Items, copy.Items);
- Assert.NotEqual(props.Parameters, copy.Parameters);
- }
-
- [Fact]
- public void DefaultConstructor_EmptyCollections()
+ ["foo"] = "bar",
+ };
+ var value = "value";
+ var parameters = new Dictionary<string, object?>
{
- var props = new AuthenticationProperties();
- Assert.Empty(props.Items);
- Assert.Empty(props.Parameters);
- }
+ ["foo2"] = value,
+ };
+ var props = new AuthenticationProperties(items, parameters);
+ Assert.Same(items, props.Items);
+ Assert.Same(parameters, props.Parameters);
+ var copy = props.Clone();
+ Assert.NotSame(props.Items, copy.Items);
+ Assert.NotSame(props.Parameters, copy.Parameters);
+ // Objects in the dictionaries will still be the same
+ Assert.Equal(props.Items, copy.Items);
+ Assert.Equal(props.Parameters, copy.Parameters);
+ props.Items["change"] = "good";
+ props.Parameters["something"] = "bad";
+ Assert.NotEqual(props.Items, copy.Items);
+ Assert.NotEqual(props.Parameters, copy.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 DefaultConstructor_EmptyCollections()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Empty(props.Items);
+ Assert.Empty(props.Parameters);
+ }
- [Fact]
- public void FullConstructor_ReusesDictionaries()
+ [Fact]
+ public void ItemsConstructor_ReusesItemsDictionary()
+ {
+ var items = new Dictionary<string, string?>
{
- 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);
- }
+ ["foo"] = "bar",
+ };
+ var props = new AuthenticationProperties(items);
+ Assert.Same(items, props.Items);
+ Assert.Empty(props.Parameters);
+ }
- [Fact]
- public void GetSetString()
+ [Fact]
+ public void FullConstructor_ReusesDictionaries()
+ {
+ var items = new Dictionary<string, string?>
{
- 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);
-
- props.SetString("doesntexist", null);
- Assert.False(props.Items.ContainsKey("doesntexist"));
- Assert.Equal(1, props.Items.Count);
- }
-
- [Fact]
- public void GetSetParameter_String()
+ ["foo"] = "bar",
+ };
+ var parameters = new Dictionary<string, object?>
{
- 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);
- }
+ ["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 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 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);
+
+ props.SetString("doesntexist", null);
+ Assert.False(props.Items.ContainsKey("doesntexist"));
+ Assert.Equal(1, props.Items.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 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 IsPersistent_Test()
- {
- var props = new AuthenticationProperties();
- Assert.False(props.IsPersistent);
+ [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);
+ }
- props.IsPersistent = true;
- Assert.True(props.IsPersistent);
- Assert.Equal(string.Empty, props.Items.First().Value);
+ [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);
+ }
- props.Items.Clear();
- Assert.False(props.IsPersistent);
- }
+ [Fact]
+ public void IsPersistent_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.False(props.IsPersistent);
- [Fact]
- public void RedirectUri_Test()
- {
- var props = new AuthenticationProperties();
- Assert.Null(props.RedirectUri);
+ props.IsPersistent = true;
+ Assert.True(props.IsPersistent);
+ Assert.Equal(string.Empty, props.Items.First().Value);
- 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.False(props.IsPersistent);
+ }
- props.Items.Clear();
- Assert.Null(props.RedirectUri);
- }
+ [Fact]
+ public void RedirectUri_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.RedirectUri);
- [Fact]
- public void IssuedUtc_Test()
- {
- var props = new AuthenticationProperties();
- Assert.Null(props.IssuedUtc);
+ props.RedirectUri = "http://example.com";
+ Assert.Equal("http://example.com", props.RedirectUri);
+ Assert.Equal("http://example.com", props.Items.First().Value);
- 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.RedirectUri);
+ }
- props.Items.Clear();
- Assert.Null(props.IssuedUtc);
- }
+ [Fact]
+ public void IssuedUtc_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.IssuedUtc);
- [Fact]
- public void ExpiresUtc_Test()
- {
- var props = new AuthenticationProperties();
- Assert.Null(props.ExpiresUtc);
+ 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.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.IssuedUtc);
+ }
- props.Items.Clear();
- Assert.Null(props.ExpiresUtc);
- }
+ [Fact]
+ public void ExpiresUtc_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.ExpiresUtc);
- [Fact]
- public void AllowRefresh_Test()
- {
- var props = new AuthenticationProperties();
- Assert.Null(props.AllowRefresh);
+ 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.AllowRefresh = true;
- Assert.True(props.AllowRefresh);
- Assert.Equal("True", props.Items.First().Value);
+ props.Items.Clear();
+ Assert.Null(props.ExpiresUtc);
+ }
- props.AllowRefresh = false;
- Assert.False(props.AllowRefresh);
- Assert.Equal("False", props.Items.First().Value);
+ [Fact]
+ public void AllowRefresh_Test()
+ {
+ var props = new AuthenticationProperties();
+ Assert.Null(props.AllowRefresh);
- props.Items.Clear();
- Assert.Null(props.AllowRefresh);
- }
+ props.AllowRefresh = true;
+ Assert.True(props.AllowRefresh);
+ Assert.Equal("True", props.Items.First().Value);
- [Fact]
- public void SetDateTimeOffset()
- {
- var props = new MyAuthenticationProperties();
+ props.AllowRefresh = false;
+ Assert.False(props.AllowRefresh);
+ Assert.Equal("False", props.Items.First().Value);
- props.SetDateTimeOffset("foo", new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc)));
- Assert.Equal("Mon, 19 Mar 2018 12:34:56 GMT", props.Items["foo"]);
+ props.Items.Clear();
+ Assert.Null(props.AllowRefresh);
+ }
- props.SetDateTimeOffset("foo", null);
- Assert.False(props.Items.ContainsKey("foo"));
+ [Fact]
+ public void SetDateTimeOffset()
+ {
+ var props = new MyAuthenticationProperties();
- props.SetDateTimeOffset("doesnotexist", null);
- Assert.False(props.Items.ContainsKey("doesnotexist"));
- }
+ props.SetDateTimeOffset("foo", new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc)));
+ Assert.Equal("Mon, 19 Mar 2018 12:34:56 GMT", props.Items["foo"]);
- [Fact]
- public void GetDateTimeOffset()
- {
- var props = new MyAuthenticationProperties();
- var dateTimeOffset = new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc));
+ props.SetDateTimeOffset("foo", null);
+ Assert.False(props.Items.ContainsKey("foo"));
+
+ props.SetDateTimeOffset("doesnotexist", null);
+ Assert.False(props.Items.ContainsKey("doesnotexist"));
+ }
- props.Items["foo"] = dateTimeOffset.ToString("r", CultureInfo.InvariantCulture);
- Assert.Equal(dateTimeOffset, props.GetDateTimeOffset("foo"));
+ [Fact]
+ public void GetDateTimeOffset()
+ {
+ var props = new MyAuthenticationProperties();
+ var dateTimeOffset = new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc));
- props.Items.Remove("foo");
- Assert.Null(props.GetDateTimeOffset("foo"));
+ props.Items["foo"] = dateTimeOffset.ToString("r", CultureInfo.InvariantCulture);
+ Assert.Equal(dateTimeOffset, props.GetDateTimeOffset("foo"));
- props.Items["foo"] = "BAR";
- Assert.Null(props.GetDateTimeOffset("foo"));
- Assert.Equal("BAR", props.Items["foo"]);
- }
+ props.Items.Remove("foo");
+ Assert.Null(props.GetDateTimeOffset("foo"));
- [Fact]
- public void SetBool()
- {
- var props = new MyAuthenticationProperties();
+ props.Items["foo"] = "BAR";
+ Assert.Null(props.GetDateTimeOffset("foo"));
+ Assert.Equal("BAR", props.Items["foo"]);
+ }
- props.SetBool("foo", true);
- Assert.Equal(true.ToString(), props.Items["foo"]);
+ [Fact]
+ public void SetBool()
+ {
+ var props = new MyAuthenticationProperties();
- props.SetBool("foo", false);
- Assert.Equal(false.ToString(), props.Items["foo"]);
+ props.SetBool("foo", true);
+ Assert.Equal(true.ToString(), props.Items["foo"]);
- props.SetBool("foo", null);
- Assert.False(props.Items.ContainsKey("foo"));
- }
+ props.SetBool("foo", false);
+ Assert.Equal(false.ToString(), props.Items["foo"]);
- [Fact]
- public void GetBool()
- {
- var props = new MyAuthenticationProperties();
+ props.SetBool("foo", null);
+ Assert.False(props.Items.ContainsKey("foo"));
+ }
- props.Items["foo"] = true.ToString();
- Assert.True(props.GetBool("foo"));
+ [Fact]
+ public void GetBool()
+ {
+ var props = new MyAuthenticationProperties();
- props.Items["foo"] = false.ToString();
- Assert.False(props.GetBool("foo"));
+ props.Items["foo"] = true.ToString();
+ Assert.True(props.GetBool("foo"));
- props.Items["foo"] = null;
- Assert.Null(props.GetBool("foo"));
+ props.Items["foo"] = false.ToString();
+ Assert.False(props.GetBool("foo"));
- props.Items["foo"] = "BAR";
- Assert.Null(props.GetBool("foo"));
- Assert.Equal("BAR", props.Items["foo"]);
- }
+ props.Items["foo"] = null;
+ Assert.Null(props.GetBool("foo"));
+
+ props.Items["foo"] = "BAR";
+ Assert.Null(props.GetBool("foo"));
+ Assert.Equal("BAR", props.Items["foo"]);
+ }
- [Fact]
- public void Roundtrip_Serializes_With_SystemTextJson()
+ [Fact]
+ public void Roundtrip_Serializes_With_SystemTextJson()
+ {
+ var props = new AuthenticationProperties()
{
- var props = new AuthenticationProperties()
- {
- AllowRefresh = true,
- ExpiresUtc = new DateTimeOffset(2021, 03, 28, 13, 47, 00, TimeSpan.Zero),
- IssuedUtc = new DateTimeOffset(2021, 03, 28, 12, 47, 00, TimeSpan.Zero),
- IsPersistent = true,
- RedirectUri = "/foo/bar"
- };
+ AllowRefresh = true,
+ ExpiresUtc = new DateTimeOffset(2021, 03, 28, 13, 47, 00, TimeSpan.Zero),
+ IssuedUtc = new DateTimeOffset(2021, 03, 28, 12, 47, 00, TimeSpan.Zero),
+ IsPersistent = true,
+ RedirectUri = "/foo/bar"
+ };
- props.Items.Add("foo", "bar");
+ props.Items.Add("foo", "bar");
- props.Parameters.Add("baz", "quux");
+ props.Parameters.Add("baz", "quux");
- var json = JsonSerializer.Serialize(props);
+ var json = JsonSerializer.Serialize(props);
- // Verify that Parameters was not serialized
- Assert.NotNull(json);
- Assert.DoesNotContain("baz", json);
- Assert.DoesNotContain("quux", json);
+ // Verify that Parameters was not serialized
+ Assert.NotNull(json);
+ Assert.DoesNotContain("baz", json);
+ Assert.DoesNotContain("quux", json);
- var deserialized = JsonSerializer.Deserialize<AuthenticationProperties>(json);
+ var deserialized = JsonSerializer.Deserialize<AuthenticationProperties>(json);
- Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized);
- Assert.Equal(props.AllowRefresh, deserialized!.AllowRefresh);
- Assert.Equal(props.ExpiresUtc, deserialized.ExpiresUtc);
- Assert.Equal(props.IssuedUtc, deserialized.IssuedUtc);
- Assert.Equal(props.IsPersistent, deserialized.IsPersistent);
- Assert.Equal(props.RedirectUri, deserialized.RedirectUri);
+ Assert.Equal(props.AllowRefresh, deserialized!.AllowRefresh);
+ Assert.Equal(props.ExpiresUtc, deserialized.ExpiresUtc);
+ Assert.Equal(props.IssuedUtc, deserialized.IssuedUtc);
+ Assert.Equal(props.IsPersistent, deserialized.IsPersistent);
+ Assert.Equal(props.RedirectUri, deserialized.RedirectUri);
- Assert.NotNull(deserialized.Items);
- Assert.True(deserialized.Items.ContainsKey("foo"));
- Assert.Equal(props.Items["foo"], deserialized.Items["foo"]);
+ Assert.NotNull(deserialized.Items);
+ Assert.True(deserialized.Items.ContainsKey("foo"));
+ Assert.Equal(props.Items["foo"], deserialized.Items["foo"]);
- // Ensure that parameters are not round-tripped
- Assert.NotNull(deserialized.Parameters);
- Assert.Equal(0, deserialized.Parameters.Count);
- }
+ // Ensure that parameters are not round-tripped
+ Assert.NotNull(deserialized.Parameters);
+ Assert.Equal(0, deserialized.Parameters.Count);
+ }
- [Fact]
- public void Parameters_Is_Not_Deserialized_With_SystemTextJson()
- {
- var json = @"{""Parameters"":{""baz"":""quux""}}";
+ [Fact]
+ public void Parameters_Is_Not_Deserialized_With_SystemTextJson()
+ {
+ var json = @"{""Parameters"":{""baz"":""quux""}}";
- var deserialized = JsonSerializer.Deserialize<AuthenticationProperties>(json);
+ var deserialized = JsonSerializer.Deserialize<AuthenticationProperties>(json);
- Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized);
- // Ensure that parameters is not deserialized from a raw payload
- Assert.NotNull(deserialized!.Parameters);
- Assert.Equal(0, deserialized.Parameters.Count);
- }
+ // Ensure that parameters is not deserialized from a raw payload
+ Assert.NotNull(deserialized!.Parameters);
+ Assert.Equal(0, deserialized.Parameters.Count);
+ }
- [Fact]
- public void Serialization_Is_Minimised_With_SystemTextJson()
+ [Fact]
+ public void Serialization_Is_Minimised_With_SystemTextJson()
+ {
+ var props = new AuthenticationProperties()
{
- var props = new AuthenticationProperties()
- {
- AllowRefresh = true,
- ExpiresUtc = new DateTimeOffset(2021, 03, 28, 13, 47, 00, TimeSpan.Zero),
- IssuedUtc = new DateTimeOffset(2021, 03, 28, 12, 47, 00, TimeSpan.Zero),
- IsPersistent = true,
- RedirectUri = "/foo/bar"
- };
-
- props.Items.Add("foo", "bar");
-
- var options = new JsonSerializerOptions() { WriteIndented = true }; // Indented for readability if test fails
- var json = JsonSerializer.Serialize(props, options);
-
- // Verify that the payload doesn't duplicate the properties backed by Items
- Assert.Equal(@"{
+ AllowRefresh = true,
+ ExpiresUtc = new DateTimeOffset(2021, 03, 28, 13, 47, 00, TimeSpan.Zero),
+ IssuedUtc = new DateTimeOffset(2021, 03, 28, 12, 47, 00, TimeSpan.Zero),
+ IsPersistent = true,
+ RedirectUri = "/foo/bar"
+ };
+
+ props.Items.Add("foo", "bar");
+
+ var options = new JsonSerializerOptions() { WriteIndented = true }; // Indented for readability if test fails
+ var json = JsonSerializer.Serialize(props, options);
+
+ // Verify that the payload doesn't duplicate the properties backed by Items
+ Assert.Equal(@"{
""Items"": {
"".refresh"": ""True"",
"".expires"": ""Sun, 28 Mar 2021 13:47:00 GMT"",
@@ -390,29 +390,28 @@ namespace Microsoft.AspNetCore.Authentication.Core.Test
""foo"": ""bar""
}
}", json, ignoreLineEndingDifferences: true);
+ }
+
+ public class MyAuthenticationProperties : AuthenticationProperties
+ {
+ public new DateTimeOffset? GetDateTimeOffset(string key)
+ {
+ return base.GetDateTimeOffset(key);
+ }
+
+ public new void SetDateTimeOffset(string key, DateTimeOffset? value)
+ {
+ base.SetDateTimeOffset(key, value);
+ }
+
+ public new void SetBool(string key, bool? value)
+ {
+ base.SetBool(key, value);
}
- public class MyAuthenticationProperties : AuthenticationProperties
+ public new bool? GetBool(string key)
{
- public new DateTimeOffset? GetDateTimeOffset(string key)
- {
- return base.GetDateTimeOffset(key);
- }
-
- public new void SetDateTimeOffset(string key, DateTimeOffset? value)
- {
- base.SetDateTimeOffset(key, value);
- }
-
- public new void SetBool(string key, bool? value)
- {
- base.SetBool(key, value);
- }
-
- public new bool? GetBool(string key)
- {
- return base.GetBool(key);
- }
+ return base.GetBool(key);
}
}
}
diff --git a/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs b/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
index dca13ea3b0..29be714577 100644
--- a/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
+++ b/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
@@ -11,214 +11,213 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
-namespace Microsoft.AspNetCore.Authentication.Core.Test
+namespace Microsoft.AspNetCore.Authentication.Core.Test;
+
+public class AuthenticationSchemeProviderTests
{
- public class AuthenticationSchemeProviderTests
+ [Fact]
+ public async Task NoDefaultsByDefault()
{
- [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);
- }
-
+ 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 DefaultSignOutFallsbackToSignIn()
+ [Fact]
+ public async Task DefaultSchemesFallbackToDefaultScheme()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<SignInHandler>("signin", "whatever");
- o.AddScheme<Handler>("foobly", "whatever");
- o.DefaultSignInScheme = "signin";
- }).BuildServiceProvider();
+ 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);
+ }
- var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
- var scheme = await provider.GetDefaultSignOutSchemeAsync();
- Assert.NotNull(scheme);
- Assert.Equal("signin", scheme!.Name);
- }
- [Fact]
- public async Task DefaultForbidFallsbackToChallenge()
+ [Fact]
+ public async Task DefaultSignOutFallsbackToSignIn()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<Handler>("challenge", "whatever");
- o.AddScheme<Handler>("foobly", "whatever");
- o.DefaultChallengeScheme = "challenge";
- }).BuildServiceProvider();
+ 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);
+ }
- var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
- var scheme = await provider.GetDefaultForbidSchemeAsync();
- Assert.NotNull(scheme);
- Assert.Equal("challenge", 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 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()
+ [Fact]
+ public async Task SignOutWillDefaultsToSignInThatDoesNotSignOut()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<Handler>("signin", "whatever");
- o.DefaultSignInScheme = "signin";
- }).BuildServiceProvider();
+ o.AddScheme<Handler>("signin", "whatever");
+ o.DefaultSignInScheme = "signin";
+ }).BuildServiceProvider();
- var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
- Assert.NotNull(await provider.GetDefaultSignOutSchemeAsync());
- }
+ var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+ Assert.NotNull(await provider.GetDefaultSignOutSchemeAsync());
+ }
- [Fact]
- public void SchemeRegistrationIsCaseSensitive()
+ [Fact]
+ public void SchemeRegistrationIsCaseSensitive()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<Handler>("signin", "whatever");
- o.AddScheme<Handler>("signin", "whatever");
- }).BuildServiceProvider();
+ o.AddScheme<Handler>("signin", "whatever");
+ o.AddScheme<Handler>("signin", "whatever");
+ }).BuildServiceProvider();
- var error = Assert.Throws<InvalidOperationException>(() => services.GetRequiredService<IAuthenticationSchemeProvider>());
+ var error = Assert.Throws<InvalidOperationException>(() => services.GetRequiredService<IAuthenticationSchemeProvider>());
- Assert.Contains("Scheme already exists: signin", error.Message);
- }
-
- [Fact]
- public void CanSafelyTryAddSchemes()
- {
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- }).BuildServiceProvider();
-
- var o = services.GetRequiredService<IAuthenticationSchemeProvider>();
- Assert.True(o.TryAddScheme(new AuthenticationScheme("signin", "whatever", typeof(Handler))));
- Assert.True(o.TryAddScheme(new AuthenticationScheme("signin2", "whatever", typeof(Handler))));
- Assert.False(o.TryAddScheme(new AuthenticationScheme("signin", "whatever", typeof(Handler))));
- Assert.True(o.TryAddScheme(new AuthenticationScheme("signin3", "whatever", typeof(Handler))));
- Assert.False(o.TryAddScheme(new AuthenticationScheme("signin2", "whatever", typeof(Handler))));
- o.RemoveScheme("signin2");
- Assert.True(o.TryAddScheme(new AuthenticationScheme("signin2", "whatever", typeof(Handler))));
- }
+ Assert.Contains("Scheme already exists: signin", error.Message);
+ }
- [Fact]
- public async Task LookupUsesProvidedStringComparer()
+ [Fact]
+ public void CanSafelyTryAddSchemes()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
- var services = new ServiceCollection().AddOptions()
- .AddSingleton<IAuthenticationSchemeProvider, IgnoreCaseSchemeProvider>()
- .AddAuthenticationCore(o => o.AddScheme<Handler>("signin", "whatever"))
- .BuildServiceProvider();
+ }).BuildServiceProvider();
+
+ var o = services.GetRequiredService<IAuthenticationSchemeProvider>();
+ Assert.True(o.TryAddScheme(new AuthenticationScheme("signin", "whatever", typeof(Handler))));
+ Assert.True(o.TryAddScheme(new AuthenticationScheme("signin2", "whatever", typeof(Handler))));
+ Assert.False(o.TryAddScheme(new AuthenticationScheme("signin", "whatever", typeof(Handler))));
+ Assert.True(o.TryAddScheme(new AuthenticationScheme("signin3", "whatever", typeof(Handler))));
+ Assert.False(o.TryAddScheme(new AuthenticationScheme("signin2", "whatever", typeof(Handler))));
+ o.RemoveScheme("signin2");
+ Assert.True(o.TryAddScheme(new AuthenticationScheme("signin2", "whatever", typeof(Handler))));
+ }
+
+ [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 provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
- var a = await provider.GetSchemeAsync("signin");
- var b = await provider.GetSchemeAsync("SignIn");
- var c = await provider.GetSchemeAsync("SIGNIN");
+ 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);
- }
+ Assert.NotNull(a);
+ Assert.Same(a, b);
+ Assert.Same(b, c);
+ }
- private class Handler : IAuthenticationHandler
+ private class Handler : IAuthenticationHandler
+ {
+ public Task<AuthenticateResult> AuthenticateAsync()
{
- public Task<AuthenticateResult> AuthenticateAsync()
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
+ }
- public Task ChallengeAsync(AuthenticationProperties? properties)
- {
- throw new NotImplementedException();
- }
+ public Task ChallengeAsync(AuthenticationProperties? properties)
+ {
+ throw new NotImplementedException();
+ }
- public Task ForbidAsync(AuthenticationProperties? properties)
- {
- throw new NotImplementedException();
- }
+ public Task ForbidAsync(AuthenticationProperties? properties)
+ {
+ throw new NotImplementedException();
+ }
- public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
- {
- throw new NotImplementedException();
- }
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ throw new NotImplementedException();
}
+ }
- private class SignInHandler : Handler, IAuthenticationSignInHandler
+ private class SignInHandler : Handler, IAuthenticationSignInHandler
+ {
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
{
- public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
+ }
- public Task SignOutAsync(AuthenticationProperties? properties)
- {
- throw new NotImplementedException();
- }
+ public Task SignOutAsync(AuthenticationProperties? properties)
+ {
+ throw new NotImplementedException();
}
+ }
- private class SignOutHandler : Handler, IAuthenticationSignOutHandler
+ private class SignOutHandler : Handler, IAuthenticationSignOutHandler
+ {
+ public Task SignOutAsync(AuthenticationProperties? properties)
{
- public Task SignOutAsync(AuthenticationProperties? properties)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
+ }
- private class IgnoreCaseSchemeProvider : AuthenticationSchemeProvider
+ private class IgnoreCaseSchemeProvider : AuthenticationSchemeProvider
+ {
+ public IgnoreCaseSchemeProvider(IOptions<AuthenticationOptions> options)
+ : base(options, new Dictionary<string, AuthenticationScheme>(StringComparer.OrdinalIgnoreCase))
{
- 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
index 72b4bf94e2..903823e8a6 100644
--- a/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs
+++ b/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs
@@ -8,414 +8,413 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Authentication.Core.Test
+namespace Microsoft.AspNetCore.Authentication.Core.Test;
+
+public class AuthenticationServiceTests
{
- public class AuthenticationServiceTests
+ [Fact]
+ public async Task AuthenticateThrowsForSchemeMismatch()
{
- [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);
- }
+ 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 CustomHandlersAuthenticateRunsClaimsTransformationEveryTime()
+ {
+ var transform = new RunOnce();
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme<BaseHandler>("base", "whatever");
+ })
+ .AddSingleton<IClaimsTransformation>(transform)
+ .BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ // Because base handler returns a different principal per call, its run multiple times
+ await context.AuthenticateAsync("base");
+ Assert.Equal(1, transform.Ran);
+
+ await context.AuthenticateAsync("base");
+ Assert.Equal(2, transform.Ran);
+
+ await context.AuthenticateAsync("base");
+ Assert.Equal(3, transform.Ran);
+ }
+
+ [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 CanOnlySignInWithIsAuthenticated()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme<SignInHandler>("signin", "whatever");
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signin", new ClaimsPrincipal(), null));
+ await context.SignInAsync("signin", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null);
+ }
+
+ [Fact]
+ public async Task CanSignInWithoutIsAuthenticated()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+ {
+ o.AddScheme<SignInHandler>("signin", "whatever");
+ o.RequireAuthenticatedSignIn = false;
+ }).BuildServiceProvider();
+ var context = new DefaultHttpContext();
+ context.RequestServices = services;
+
+ await context.SignInAsync("signin", new ClaimsPrincipal(), null);
+ await context.SignInAsync("signin", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null);
+ }
- [Fact]
- public async Task CustomHandlersAuthenticateRunsClaimsTransformationEveryTime()
+ [Fact]
+ public async Task CanOnlySignInIfSupported()
+ {
+ var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
{
- var transform = new RunOnce();
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<BaseHandler>("base", "whatever");
- })
- .AddSingleton<IClaimsTransformation>(transform)
- .BuildServiceProvider();
- var context = new DefaultHttpContext();
- context.RequestServices = services;
+ 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(new ClaimsIdentity("whatever")), null);
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("base", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null));
+ Assert.Contains("uber", ex.Message);
+ Assert.Contains("signin", ex.Message);
+ await context.SignInAsync("signin", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null);
+ ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signout", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null));
+ Assert.Contains("uber", ex.Message);
+ Assert.Contains("signin", ex.Message);
+ }
- // Because base handler returns a different principal per call, its run multiple times
- await context.AuthenticateAsync("base");
- Assert.Equal(1, transform.Ran);
+ [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(new ClaimsIdentity("whatever"))));
+ 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(new ClaimsIdentity("whatever")));
+ }
- await context.AuthenticateAsync("base");
- Assert.Equal(2, transform.Ran);
+ [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(new ClaimsIdentity("whatever")));
+ }
+
+ [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(new ClaimsIdentity("whatever"))));
+ 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();
+ }
- await context.AuthenticateAsync("base");
- Assert.Equal(3, transform.Ran);
+ private class RunOnce : IClaimsTransformation
+ {
+ public int Ran = 0;
+ public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
+ {
+ Ran++;
+ return Task.FromResult(new ClaimsPrincipal());
}
+ }
- [Fact]
- public async Task ChallengeThrowsForSchemeMismatch()
+ private class BaseHandler : IAuthenticationHandler
+ {
+ public Task<AuthenticateResult> AuthenticateAsync()
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<BaseHandler>("base", "whatever");
- }).BuildServiceProvider();
- var context = new DefaultHttpContext();
- context.RequestServices = services;
+ return Task.FromResult(AuthenticateResult.Success(
+ new AuthenticationTicket(
+ new ClaimsPrincipal(new ClaimsIdentity("whatever")),
+ new AuthenticationProperties(),
+ "whatever")));
+ }
- await context.ChallengeAsync("base");
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.ChallengeAsync("missing"));
- Assert.Contains("base", ex.Message);
+ public Task ChallengeAsync(AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
- [Fact]
- public async Task ForbidThrowsForSchemeMismatch()
+ public Task ForbidAsync(AuthenticationProperties? properties)
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<BaseHandler>("base", "whatever");
- }).BuildServiceProvider();
- var context = new DefaultHttpContext();
- context.RequestServices = services;
+ return Task.FromResult(0);
+ }
- await context.ForbidAsync("base");
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.ForbidAsync("missing"));
- Assert.Contains("base", ex.Message);
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
}
+ }
- [Fact]
- public async Task CanOnlySignInWithIsAuthenticated()
+ private class SignInHandler : IAuthenticationSignInHandler
+ {
+ public Task<AuthenticateResult> AuthenticateAsync()
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<SignInHandler>("signin", "whatever");
- }).BuildServiceProvider();
- var context = new DefaultHttpContext();
- context.RequestServices = services;
+ return Task.FromResult(AuthenticateResult.NoResult());
+ }
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signin", new ClaimsPrincipal(), null));
- await context.SignInAsync("signin", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null);
+ public Task ChallengeAsync(AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
- [Fact]
- public async Task CanSignInWithoutIsAuthenticated()
+ public Task ForbidAsync(AuthenticationProperties? properties)
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<SignInHandler>("signin", "whatever");
- o.RequireAuthenticatedSignIn = false;
- }).BuildServiceProvider();
- var context = new DefaultHttpContext();
- context.RequestServices = services;
+ return Task.FromResult(0);
+ }
- await context.SignInAsync("signin", new ClaimsPrincipal(), null);
- await context.SignInAsync("signin", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null);
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
}
- [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(new ClaimsIdentity("whatever")), null);
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("base", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null));
- Assert.Contains("uber", ex.Message);
- Assert.Contains("signin", ex.Message);
- await context.SignInAsync("signin", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null);
- ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signout", new ClaimsPrincipal(new ClaimsIdentity("whatever")), null));
- Assert.Contains("uber", ex.Message);
- Assert.Contains("signin", ex.Message);
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
- [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");
+ public Task SignOutAsync(AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
+ }
- [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(new ClaimsIdentity("whatever"))));
- Assert.Contains("cannot be used for SignInAsync", ex.Message);
+ public class SignOutHandler : IAuthenticationSignOutHandler
+ {
+ public Task<AuthenticateResult> AuthenticateAsync()
+ {
+ return Task.FromResult(AuthenticateResult.NoResult());
}
- [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(new ClaimsIdentity("whatever")));
+ public Task ChallengeAsync(AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
- [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(new ClaimsIdentity("whatever")));
+ public Task ForbidAsync(AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
- [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(new ClaimsIdentity("whatever"))));
- Assert.Contains("cannot be used for SignInAsync", ex.Message);
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ return Task.FromResult(0);
}
- [Fact]
- public async Task ServicesWithDefaultForbidMethod_CallsForbidMethod()
+ public Task SignOutAsync(AuthenticationProperties? properties)
{
- var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
- {
- o.AddScheme<ForbidHandler>("forbid", "whatever");
- o.DefaultForbidScheme = "forbid";
- }).BuildServiceProvider();
- var context = new DefaultHttpContext();
- context.RequestServices = services;
+ return Task.FromResult(0);
+ }
+ }
- await context.ForbidAsync();
+ 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);
}
- private class RunOnce : IClaimsTransformation
+ public Task<bool> HandleRequestAsync()
{
- public int Ran = 0;
- public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
- {
- Ran++;
- return Task.FromResult(new ClaimsPrincipal());
- }
+ return Task.FromResult(false);
}
- private class BaseHandler : IAuthenticationHandler
- {
- public Task<AuthenticateResult> AuthenticateAsync()
- {
- return Task.FromResult(AuthenticateResult.Success(
- new AuthenticationTicket(
- new ClaimsPrincipal(new ClaimsIdentity("whatever")),
- new AuthenticationProperties(),
- "whatever")));
- }
-
- 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 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 Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
}
- public class SignOutHandler : IAuthenticationSignOutHandler
+ public Task SignOutAsync(AuthenticationProperties? properties)
{
- public Task<AuthenticateResult> AuthenticateAsync()
- {
- return Task.FromResult(AuthenticateResult.NoResult());
- }
+ return Task.FromResult(0);
+ }
+ }
- public Task ChallengeAsync(AuthenticationProperties? properties)
- {
- return Task.FromResult(0);
- }
+ private class ForbidHandler : IAuthenticationHandler, IAuthenticationRequestHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
+ {
+ public Task<AuthenticateResult> AuthenticateAsync()
+ {
+ throw new NotImplementedException();
+ }
- public Task ForbidAsync(AuthenticationProperties? properties)
- {
- return Task.FromResult(0);
- }
+ public Task ChallengeAsync(AuthenticationProperties? properties)
+ {
+ throw new NotImplementedException();
+ }
- public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
- {
- return Task.FromResult(0);
- }
+ public Task ForbidAsync(AuthenticationProperties? properties)
+ {
+ return Task.FromResult(0);
+ }
- public Task SignOutAsync(AuthenticationProperties? properties)
- {
- return Task.FromResult(0);
- }
+ public Task<bool> HandleRequestAsync()
+ {
+ throw new NotImplementedException();
}
- 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);
- }
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+ {
+ 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();
- }
+ 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/AuthenticationTicketTests.cs b/src/Http/Authentication.Core/test/AuthenticationTicketTests.cs
index 0c6260c421..29519ff5ea 100644
--- a/src/Http/Authentication.Core/test/AuthenticationTicketTests.cs
+++ b/src/Http/Authentication.Core/test/AuthenticationTicketTests.cs
@@ -5,43 +5,42 @@ using System.Collections.Generic;
using System.Security.Claims;
using Xunit;
-namespace Microsoft.AspNetCore.Authentication.Core.Test
+namespace Microsoft.AspNetCore.Authentication.Core.Test;
+
+public class AuthenticationTicketTests
{
- public class AuthenticationTicketTests
+ [Fact]
+ public void Clone_Copies()
{
- [Fact]
- public void Clone_Copies()
+ var items = new Dictionary<string, string?>
+ {
+ ["foo"] = "bar",
+ };
+ var value = "value";
+ var parameters = new Dictionary<string, object?>
{
- var items = new Dictionary<string, string?>
- {
- ["foo"] = "bar",
- };
- var value = "value";
- var parameters = new Dictionary<string, object?>
- {
- ["foo2"] = value,
- };
- var props = new AuthenticationProperties(items, parameters);
- var identity = new ClaimsIdentity();
- var principal = new ClaimsPrincipal(identity);
- var ticket = new AuthenticationTicket(principal, props, "scheme");
+ ["foo2"] = value,
+ };
+ var props = new AuthenticationProperties(items, parameters);
+ var identity = new ClaimsIdentity();
+ var principal = new ClaimsPrincipal(identity);
+ var ticket = new AuthenticationTicket(principal, props, "scheme");
- Assert.Same(items, ticket.Properties.Items);
- Assert.Same(parameters, ticket.Properties.Parameters);
- var copy = ticket.Clone();
- Assert.NotSame(ticket.Principal, copy.Principal);
- Assert.NotSame(ticket.Properties.Items, copy.Properties.Items);
- Assert.NotSame(ticket.Properties.Parameters, copy.Properties.Parameters);
- // Objects in the dictionaries will still be the same
- Assert.Equal(ticket.Properties.Items, copy.Properties.Items);
- Assert.Equal(ticket.Properties.Parameters, copy.Properties.Parameters);
- props.Items["change"] = "good";
- props.Parameters["something"] = "bad";
- Assert.NotEqual(ticket.Properties.Items, copy.Properties.Items);
- Assert.NotEqual(ticket.Properties.Parameters, copy.Properties.Parameters);
- identity.AddClaim(new Claim("name", "value"));
- Assert.True(ticket.Principal.HasClaim("name", "value"));
- Assert.False(copy.Principal.HasClaim("name", "value"));
- }
+ Assert.Same(items, ticket.Properties.Items);
+ Assert.Same(parameters, ticket.Properties.Parameters);
+ var copy = ticket.Clone();
+ Assert.NotSame(ticket.Principal, copy.Principal);
+ Assert.NotSame(ticket.Properties.Items, copy.Properties.Items);
+ Assert.NotSame(ticket.Properties.Parameters, copy.Properties.Parameters);
+ // Objects in the dictionaries will still be the same
+ Assert.Equal(ticket.Properties.Items, copy.Properties.Items);
+ Assert.Equal(ticket.Properties.Parameters, copy.Properties.Parameters);
+ props.Items["change"] = "good";
+ props.Parameters["something"] = "bad";
+ Assert.NotEqual(ticket.Properties.Items, copy.Properties.Items);
+ Assert.NotEqual(ticket.Properties.Parameters, copy.Properties.Parameters);
+ identity.AddClaim(new Claim("name", "value"));
+ Assert.True(ticket.Principal.HasClaim("name", "value"));
+ Assert.False(copy.Principal.HasClaim("name", "value"));
}
}
diff --git a/src/Http/Authentication.Core/test/TokenExtensionTests.cs b/src/Http/Authentication.Core/test/TokenExtensionTests.cs
index aac27e22bb..bb2acc8645 100644
--- a/src/Http/Authentication.Core/test/TokenExtensionTests.cs
+++ b/src/Http/Authentication.Core/test/TokenExtensionTests.cs
@@ -10,54 +10,153 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Authentication.Core.Test
+namespace Microsoft.AspNetCore.Authentication.Core.Test;
+
+public class TokenExtensionTests
{
- public class TokenExtensionTests
+ [Fact]
+ public void CanStoreMultipleTokens()
{
- [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);
+ 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());
+ }
- 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 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);
+ [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());
+ }
- props.StoreTokens(tokens);
+ [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());
+ }
- props.StoreTokens(new[] { new AuthenticationToken { Name = "Zero", Value = "0" } });
+ [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());
+ }
- 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 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"));
+ }
- [Fact]
- public void CanUpdateTokens()
+ private class SimpleAuth : IAuthenticationHandler
+ {
+ public Task<AuthenticateResult> AuthenticateAsync()
{
var props = new AuthenticationProperties();
var tokens = new List<AuthenticationToken>();
@@ -68,133 +167,33 @@ namespace Microsoft.AspNetCore.Authentication.Core.Test
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());
+ return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), props, "simple")));
}
- [Fact]
- public void CanUpdateTokenValues()
+ public Task ChallengeAsync(AuthenticationProperties? properties)
{
- 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());
+ throw new NotImplementedException();
}
- [Fact]
- public void UpdateTokenValueReturnsFalseForUnknownToken()
+ public Task ForbidAsync(AuthenticationProperties? properties)
{
- 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());
+ throw new NotImplementedException();
}
- [Fact]
- public async Task GetTokenWorksWithDefaultAuthenticateScheme()
+ public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
- 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"));
+ return Task.FromResult(0);
}
- [Fact]
- public async Task GetTokenWorksWithExplicitScheme()
+ public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
- 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"));
+ throw new NotImplementedException();
}
- private class SimpleAuth : IAuthenticationHandler
+ public Task SignOutAsync(AuthenticationProperties properties)
{
- 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();
- }
+ throw new NotImplementedException();
}
-
}
+
}
diff --git a/src/Http/Headers/src/BaseHeaderParser.cs b/src/Http/Headers/src/BaseHeaderParser.cs
index fd5ea9bd10..bf35270963 100644
--- a/src/Http/Headers/src/BaseHeaderParser.cs
+++ b/src/Http/Headers/src/BaseHeaderParser.cs
@@ -3,69 +3,68 @@
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal abstract class BaseHeaderParser<T> : HttpHeaderParser<T>
{
- internal abstract class BaseHeaderParser<T> : HttpHeaderParser<T>
+ protected BaseHeaderParser(bool supportsMultipleValues)
+ : base(supportsMultipleValues)
{
- protected BaseHeaderParser(bool supportsMultipleValues)
- : base(supportsMultipleValues)
- {
- }
+ }
- protected abstract int GetParsedValueLength(StringSegment value, int startIndex, out T? parsedValue);
+ 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;
+ public sealed override bool TryParseValue(StringSegment value, ref int index, out T? parsedValue)
+ {
+ parsedValue = default;
- // 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;
- }
+ // 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);
+ 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 (separatorFound && !SupportsMultipleValues)
+ {
+ return false; // leading separators not allowed if we don't support multiple values.
+ }
- if (current == value.Length)
+ if (current == value.Length)
+ {
+ if (SupportsMultipleValues)
{
- if (SupportsMultipleValues)
- {
- index = current;
- }
- return SupportsMultipleValues;
+ index = current;
}
+ return SupportsMultipleValues;
+ }
- var length = GetParsedValueLength(value, current, out var result);
-
- if (length == 0)
- {
- return false;
- }
+ var length = GetParsedValueLength(value, current, out var result);
- current = current + length;
- current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues,
- out separatorFound);
+ if (length == 0)
+ {
+ return false;
+ }
- // 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;
- }
+ current = current + length;
+ current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues,
+ out separatorFound);
- index = current;
- parsedValue = result;
- return true;
+ // 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
index 3082fa5524..37ff5185dc 100644
--- a/src/Http/Headers/src/CacheControlHeaderValue.cs
+++ b/src/Http/Headers/src/CacheControlHeaderValue.cs
@@ -9,824 +9,823 @@ using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents the <c>Cache-Control</c> HTTP header.
+/// </summary>
+public class CacheControlHeaderValue
{
/// <summary>
- /// Represents the <c>Cache-Control</c> HTTP header.
+ /// A constant for the <c>public</c> cache-control directive.
+ /// </summary>
+ public static readonly string PublicString = "public";
+
+ /// <summary>
+ /// A constant for the <c>private</c> cache-control directive.
+ /// </summary>
+ public static readonly string PrivateString = "private";
+
+ /// <summary>
+ /// A constant for the <c>max-age</c> cache-control directive.
+ /// </summary>
+ public static readonly string MaxAgeString = "max-age";
+
+ /// <summary>
+ /// A constant for the <c>s-maxage</c> cache-control directive.
+ /// </summary>
+ public static readonly string SharedMaxAgeString = "s-maxage";
+
+ /// <summary>
+ /// A constant for the <c>no-cache</c> cache-control directive.
+ /// </summary>
+ public static readonly string NoCacheString = "no-cache";
+
+ /// <summary>
+ /// A constant for the <c>no-store</c> cache-control directive.
+ /// </summary>
+ public static readonly string NoStoreString = "no-store";
+
+ /// <summary>
+ /// A constant for the <c>max-stale</c> cache-control directive.
+ /// </summary>
+ public static readonly string MaxStaleString = "max-stale";
+
+ /// <summary>
+ /// A constant for the <c>min-fresh</c> cache-control directive.
+ /// </summary>
+ public static readonly string MinFreshString = "min-fresh";
+
+ /// <summary>
+ /// A constant for the <c>no-transform</c> cache-control directive.
/// </summary>
- public class CacheControlHeaderValue
+ public static readonly string NoTransformString = "no-transform";
+
+ /// <summary>
+ /// A constant for the <c>only-if-cached</c> cache-control directive.
+ /// </summary>
+ public static readonly string OnlyIfCachedString = "only-if-cached";
+
+ /// <summary>
+ /// A constant for the <c>must-revalidate</c> cache-control directive.
+ /// </summary>
+ public static readonly string MustRevalidateString = "must-revalidate";
+
+ /// <summary>
+ /// A constant for the <c>proxy-revalidate</c> cache-control directive.
+ /// </summary>
+ 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;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="CacheControlHeaderValue"/>.
+ /// </summary>
+ public CacheControlHeaderValue()
{
- /// <summary>
- /// A constant for the <c>public</c> cache-control directive.
- /// </summary>
- public static readonly string PublicString = "public";
-
- /// <summary>
- /// A constant for the <c>private</c> cache-control directive.
- /// </summary>
- public static readonly string PrivateString = "private";
-
- /// <summary>
- /// A constant for the <c>max-age</c> cache-control directive.
- /// </summary>
- public static readonly string MaxAgeString = "max-age";
-
- /// <summary>
- /// A constant for the <c>s-maxage</c> cache-control directive.
- /// </summary>
- public static readonly string SharedMaxAgeString = "s-maxage";
-
- /// <summary>
- /// A constant for the <c>no-cache</c> cache-control directive.
- /// </summary>
- public static readonly string NoCacheString = "no-cache";
-
- /// <summary>
- /// A constant for the <c>no-store</c> cache-control directive.
- /// </summary>
- public static readonly string NoStoreString = "no-store";
-
- /// <summary>
- /// A constant for the <c>max-stale</c> cache-control directive.
- /// </summary>
- public static readonly string MaxStaleString = "max-stale";
-
- /// <summary>
- /// A constant for the <c>min-fresh</c> cache-control directive.
- /// </summary>
- public static readonly string MinFreshString = "min-fresh";
-
- /// <summary>
- /// A constant for the <c>no-transform</c> cache-control directive.
- /// </summary>
- public static readonly string NoTransformString = "no-transform";
-
- /// <summary>
- /// A constant for the <c>only-if-cached</c> cache-control directive.
- /// </summary>
- public static readonly string OnlyIfCachedString = "only-if-cached";
-
- /// <summary>
- /// A constant for the <c>must-revalidate</c> cache-control directive.
- /// </summary>
- public static readonly string MustRevalidateString = "must-revalidate";
-
- /// <summary>
- /// A constant for the <c>proxy-revalidate</c> cache-control directive.
- /// </summary>
- 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;
-
- /// <summary>
- /// Initializes a new instance of <see cref="CacheControlHeaderValue"/>.
- /// </summary>
- public CacheControlHeaderValue()
- {
- // This type is unique in that there is no single required parameter.
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>no-cache</c> directive.
- /// <para>
- /// Configuring no-cache indicates that the client must re-validate cached responses with the original server
- /// before using it.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.4</remarks>
- public bool NoCache
- {
- get { return _noCache; }
- set { _noCache = value; }
- }
-
- /// <summary>
- /// Gets a collection of field names in the "no-cache" directive in a cache-control header field on an HTTP response.
- /// </summary>
- public ICollection<StringSegment> NoCacheHeaders
- {
- get
- {
- if (_noCacheHeaders == null)
- {
- _noCacheHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
- }
- return _noCacheHeaders;
- }
- }
+ // This type is unique in that there is no single required parameter.
+ }
+
+ /// <summary>
+ /// Gets or sets a value for the <c>no-cache</c> directive.
+ /// <para>
+ /// Configuring no-cache indicates that the client must re-validate cached responses with the original server
+ /// before using it.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.4</remarks>
+ public bool NoCache
+ {
+ get { return _noCache; }
+ set { _noCache = value; }
+ }
- /// <summary>
- /// Gets or sets a value for the <c>no-store</c> directive.
- /// <para>
- /// Configuring no-store indicates that the response may not be stored in any cache.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.5</remarks>
- public bool NoStore
- {
- get { return _noStore; }
- set { _noStore = value; }
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>max-age</c> directive.
- /// <para>
- /// max-age specifies the maximum amount of time the response is considered fresh.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.1</remarks>
- public TimeSpan? MaxAge
- {
- get { return _maxAge; }
- set { _maxAge = value; }
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>s-maxage</c> directive.
- /// <para>
- /// Overrides <see cref="MaxAge">max-age</see>, but only for shared caches (such as proxies).
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.9</remarks>
- public TimeSpan? SharedMaxAge
- {
- get { return _sharedMaxAge; }
- set { _sharedMaxAge = value; }
- }
-
- /// <summary>
- /// Gets or sets a value that determines if the <c>max-stale</c> is included.
- /// <para>
- /// <c>max-stale</c> that the client will accept stale responses. The maximum tolerance for staleness
- /// is specified by <see cref="MaxStaleLimit"/>.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.2</remarks>
- public bool MaxStale
- {
- get { return _maxStale; }
- set { _maxStale = value; }
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>max-stale</c> directive.
- /// <para>
- /// Indicates the maximum duration an HTTP client is willing to accept a response that has exceeded its expiration time.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.2</remarks>
- public TimeSpan? MaxStaleLimit
- {
- get { return _maxStaleLimit; }
- set { _maxStaleLimit = value; }
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>min-fresh</c> directive.
- /// <para>
- /// Indicates the freshness lifetime that an HTTP client is willing to accept a response.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.3</remarks>
- public TimeSpan? MinFresh
- {
- get { return _minFresh; }
- set { _minFresh = value; }
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>no-transform</c> request directive.
- /// <para>
- /// Forbids intermediate caches or proxies from editing the response payload.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.6</remarks>
- public bool NoTransform
- {
- get { return _noTransform; }
- set { _noTransform = value; }
- }
-
- /// <summary>
- /// Gets or sets a value for the <c>only-if-cached</c> request directive.
- /// <para>
- /// Indicates that the client only wishes to obtain a stored response
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.7</remarks>
- public bool OnlyIfCached
- {
- get { return _onlyIfCached; }
- set { _onlyIfCached = value; }
- }
-
- /// <summary>
- /// Gets or sets a value that determines if the <c>public</c> response directive is included.
- /// <para>
- /// Indicates that the response may be stored by any cache.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.5</remarks>
- public bool Public
- {
- get { return _public; }
- set { _public = value; }
- }
-
- /// <summary>
- /// Gets or sets a value that determines if the <c>private</c> response directive is included.
- /// <para>
- /// Indicates that the response may not be stored by a shared cache.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.6</remarks>
- public bool Private
- {
- get { return _private; }
- set { _private = value; }
- }
-
- /// <summary>
- /// Gets a collection of field names in the "private" directive in a cache-control header field on an HTTP response.
- /// </summary>
- public ICollection<StringSegment> PrivateHeaders
- {
- get
+ /// <summary>
+ /// Gets a collection of field names in the "no-cache" directive in a cache-control header field on an HTTP response.
+ /// </summary>
+ public ICollection<StringSegment> NoCacheHeaders
+ {
+ get
+ {
+ if (_noCacheHeaders == null)
{
- if (_privateHeaders == null)
- {
- _privateHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
- }
- return _privateHeaders;
+ _noCacheHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
}
+ return _noCacheHeaders;
}
+ }
- /// <summary>
- /// Gets or sets a value that determines if the <c>must-revalidate</c> response directive is included.
- /// <para>
- /// Indicates that caches must revalidate the use of stale caches with the origin server before their use.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.1</remarks>
- public bool MustRevalidate
- {
- get { return _mustRevalidate; }
- set { _mustRevalidate = value; }
- }
+ /// <summary>
+ /// Gets or sets a value for the <c>no-store</c> directive.
+ /// <para>
+ /// Configuring no-store indicates that the response may not be stored in any cache.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.5</remarks>
+ public bool NoStore
+ {
+ get { return _noStore; }
+ set { _noStore = value; }
+ }
- /// <summary>
- /// Gets or sets a value that determines if the <c>proxy-validate</c> response directive is included.
- /// <para>
- /// Indicates that shared caches must revalidate the use of stale caches with the origin server before their use.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.1</remarks>
- public bool ProxyRevalidate
- {
- get { return _proxyRevalidate; }
- set { _proxyRevalidate = value; }
- }
+ /// <summary>
+ /// Gets or sets a value for the <c>max-age</c> directive.
+ /// <para>
+ /// max-age specifies the maximum amount of time the response is considered fresh.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.1</remarks>
+ public TimeSpan? MaxAge
+ {
+ get { return _maxAge; }
+ set { _maxAge = value; }
+ }
- /// <summary>
- /// Gets cache-extension tokens, each with an optional assigned value.
- /// </summary>
- public IList<NameValueHeaderValue> Extensions
+ /// <summary>
+ /// Gets or sets a value for the <c>s-maxage</c> directive.
+ /// <para>
+ /// Overrides <see cref="MaxAge">max-age</see>, but only for shared caches (such as proxies).
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.9</remarks>
+ public TimeSpan? SharedMaxAge
+ {
+ get { return _sharedMaxAge; }
+ set { _sharedMaxAge = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value that determines if the <c>max-stale</c> is included.
+ /// <para>
+ /// <c>max-stale</c> that the client will accept stale responses. The maximum tolerance for staleness
+ /// is specified by <see cref="MaxStaleLimit"/>.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.2</remarks>
+ public bool MaxStale
+ {
+ get { return _maxStale; }
+ set { _maxStale = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value for the <c>max-stale</c> directive.
+ /// <para>
+ /// Indicates the maximum duration an HTTP client is willing to accept a response that has exceeded its expiration time.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.2</remarks>
+ public TimeSpan? MaxStaleLimit
+ {
+ get { return _maxStaleLimit; }
+ set { _maxStaleLimit = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value for the <c>min-fresh</c> directive.
+ /// <para>
+ /// Indicates the freshness lifetime that an HTTP client is willing to accept a response.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.3</remarks>
+ public TimeSpan? MinFresh
+ {
+ get { return _minFresh; }
+ set { _minFresh = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value for the <c>no-transform</c> request directive.
+ /// <para>
+ /// Forbids intermediate caches or proxies from editing the response payload.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.6</remarks>
+ public bool NoTransform
+ {
+ get { return _noTransform; }
+ set { _noTransform = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value for the <c>only-if-cached</c> request directive.
+ /// <para>
+ /// Indicates that the client only wishes to obtain a stored response
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.1.7</remarks>
+ public bool OnlyIfCached
+ {
+ get { return _onlyIfCached; }
+ set { _onlyIfCached = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value that determines if the <c>public</c> response directive is included.
+ /// <para>
+ /// Indicates that the response may be stored by any cache.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.5</remarks>
+ public bool Public
+ {
+ get { return _public; }
+ set { _public = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value that determines if the <c>private</c> response directive is included.
+ /// <para>
+ /// Indicates that the response may not be stored by a shared cache.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.6</remarks>
+ public bool Private
+ {
+ get { return _private; }
+ set { _private = value; }
+ }
+
+ /// <summary>
+ /// Gets a collection of field names in the "private" directive in a cache-control header field on an HTTP response.
+ /// </summary>
+ public ICollection<StringSegment> PrivateHeaders
+ {
+ get
{
- get
+ if (_privateHeaders == null)
{
- if (_extensions == null)
- {
- _extensions = new ObjectCollection<NameValueHeaderValue>();
- }
- return _extensions;
+ _privateHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
}
+ return _privateHeaders;
}
+ }
- /// <inheritdoc />
- public override string ToString()
- {
- var sb = new StringBuilder();
+ /// <summary>
+ /// Gets or sets a value that determines if the <c>must-revalidate</c> response directive is included.
+ /// <para>
+ /// Indicates that caches must revalidate the use of stale caches with the origin server before their use.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.1</remarks>
+ public bool MustRevalidate
+ {
+ get { return _mustRevalidate; }
+ set { _mustRevalidate = value; }
+ }
- AppendValueIfRequired(sb, _noStore, NoStoreString);
- AppendValueIfRequired(sb, _noTransform, NoTransformString);
- AppendValueIfRequired(sb, _onlyIfCached, OnlyIfCachedString);
- AppendValueIfRequired(sb, _public, PublicString);
- AppendValueIfRequired(sb, _mustRevalidate, MustRevalidateString);
- AppendValueIfRequired(sb, _proxyRevalidate, ProxyRevalidateString);
+ /// <summary>
+ /// Gets or sets a value that determines if the <c>proxy-validate</c> response directive is included.
+ /// <para>
+ /// Indicates that shared caches must revalidate the use of stale caches with the origin server before their use.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc7234#section-5.2.2.1</remarks>
+ public bool ProxyRevalidate
+ {
+ get { return _proxyRevalidate; }
+ set { _proxyRevalidate = value; }
+ }
- if (_noCache)
+ /// <summary>
+ /// Gets cache-extension tokens, each with an optional assigned value.
+ /// </summary>
+ public IList<NameValueHeaderValue> Extensions
+ {
+ get
+ {
+ if (_extensions == null)
{
- AppendValueWithSeparatorIfRequired(sb, NoCacheString);
- if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
- {
- sb.Append("=\"");
- AppendValues(sb, _noCacheHeaders);
- sb.Append('\"');
- }
+ _extensions = new ObjectCollection<NameValueHeaderValue>();
}
+ return _extensions;
+ }
+ }
- if (_maxAge.HasValue)
- {
- AppendValueWithSeparatorIfRequired(sb, MaxAgeString);
- sb.Append('=');
- sb.Append(((int)_maxAge.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
- }
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
- if (_sharedMaxAge.HasValue)
- {
- AppendValueWithSeparatorIfRequired(sb, SharedMaxAgeString);
- sb.Append('=');
- sb.Append(((int)_sharedMaxAge.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
- }
+ 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 (_maxStale)
+ if (_noCache)
+ {
+ AppendValueWithSeparatorIfRequired(sb, NoCacheString);
+ if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
{
- AppendValueWithSeparatorIfRequired(sb, MaxStaleString);
- if (_maxStaleLimit.HasValue)
- {
- sb.Append('=');
- sb.Append(((int)_maxStaleLimit.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
- }
+ sb.Append("=\"");
+ AppendValues(sb, _noCacheHeaders);
+ sb.Append('\"');
}
+ }
- if (_minFresh.HasValue)
- {
- AppendValueWithSeparatorIfRequired(sb, MinFreshString);
- sb.Append('=');
- sb.Append(((int)_minFresh.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
- }
+ if (_maxAge.HasValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, MaxAgeString);
+ sb.Append('=');
+ sb.Append(((int)_maxAge.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
- if (_private)
+ if (_sharedMaxAge.HasValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, SharedMaxAgeString);
+ sb.Append('=');
+ sb.Append(((int)_sharedMaxAge.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
+
+ if (_maxStale)
+ {
+ AppendValueWithSeparatorIfRequired(sb, MaxStaleString);
+ if (_maxStaleLimit.HasValue)
{
- AppendValueWithSeparatorIfRequired(sb, PrivateString);
- if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
- {
- sb.Append("=\"");
- AppendValues(sb, _privateHeaders);
- sb.Append('\"');
- }
+ sb.Append('=');
+ sb.Append(((int)_maxStaleLimit.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
}
-
- NameValueHeaderValue.ToString(_extensions, ',', false, sb);
-
- return sb.ToString();
}
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ if (_minFresh.HasValue)
{
- var other = obj as CacheControlHeaderValue;
+ AppendValueWithSeparatorIfRequired(sb, MinFreshString);
+ sb.Append('=');
+ sb.Append(((int)_minFresh.GetValueOrDefault().TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+ }
- if (other == null)
+ if (_private)
+ {
+ AppendValueWithSeparatorIfRequired(sb, PrivateString);
+ if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
{
- return false;
+ sb.Append("=\"");
+ AppendValues(sb, _privateHeaders);
+ sb.Append('\"');
}
+ }
- 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;
- }
+ NameValueHeaderValue.ToString(_extensions, ',', false, sb);
- if (!HeaderUtilities.AreEqualCollections(_noCacheHeaders, other._noCacheHeaders,
- StringSegmentComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return sb.ToString();
+ }
- if (!HeaderUtilities.AreEqualCollections(_privateHeaders, other._privateHeaders,
- StringSegmentComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as CacheControlHeaderValue;
- if (!HeaderUtilities.AreEqualCollections(_extensions, other._extensions))
- {
- return false;
- }
+ if (other == null)
+ {
+ return false;
+ }
- return true;
+ 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;
}
- /// <inheritdoc />
- public override int GetHashCode()
+ if (!HeaderUtilities.AreEqualCollections(_noCacheHeaders, other._noCacheHeaders,
+ StringSegmentComparer.OrdinalIgnoreCase))
{
- // 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);
+ return false;
+ }
- // 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.GetValueOrDefault().GetHashCode() ^ 1 : 0) ^
- (_sharedMaxAge.HasValue ? _sharedMaxAge.GetValueOrDefault().GetHashCode() ^ 2 : 0) ^
- (_maxStaleLimit.HasValue ? _maxStaleLimit.GetValueOrDefault().GetHashCode() ^ 4 : 0) ^
- (_minFresh.HasValue ? _minFresh.GetValueOrDefault().GetHashCode() ^ 8 : 0);
+ if (!HeaderUtilities.AreEqualCollections(_privateHeaders, other._privateHeaders,
+ StringSegmentComparer.OrdinalIgnoreCase))
+ {
+ return false;
+ }
- if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
- {
- foreach (var noCacheHeader in _noCacheHeaders)
- {
- result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
- }
- }
+ if (!HeaderUtilities.AreEqualCollections(_extensions, other._extensions))
+ {
+ return false;
+ }
- if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
- {
- foreach (var privateHeader in _privateHeaders)
- {
- result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
- }
- }
+ return true;
+ }
- if ((_extensions != null) && (_extensions.Count > 0))
+ /// <inheritdoc />
+ 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.GetValueOrDefault().GetHashCode() ^ 1 : 0) ^
+ (_sharedMaxAge.HasValue ? _sharedMaxAge.GetValueOrDefault().GetHashCode() ^ 2 : 0) ^
+ (_maxStaleLimit.HasValue ? _maxStaleLimit.GetValueOrDefault().GetHashCode() ^ 4 : 0) ^
+ (_minFresh.HasValue ? _minFresh.GetValueOrDefault().GetHashCode() ^ 8 : 0);
+
+ if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
+ {
+ foreach (var noCacheHeader in _noCacheHeaders)
{
- foreach (var extension in _extensions)
- {
- result = result ^ extension.GetHashCode();
- }
+ result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
}
-
- return result;
}
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="CacheControlHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static CacheControlHeaderValue Parse(StringSegment input)
+ if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
{
- var 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)
+ foreach (var privateHeader in _privateHeaders)
{
- throw new FormatException("No cache directives found.");
+ result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
}
- return result;
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="CacheControlHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out CacheControlHeaderValue? parsedValue)
+ if ((_extensions != null) && (_extensions.Count > 0))
{
- var 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)
+ foreach (var extension in _extensions)
{
- return true;
+ result = result ^ extension.GetHashCode();
}
- parsedValue = null;
- return false;
}
- private static int GetCacheControlLength(StringSegment input, int startIndex, out CacheControlHeaderValue? parsedValue)
+ return result;
+ }
+
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="CacheControlHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static CacheControlHeaderValue Parse(StringSegment input)
+ {
+ var 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)
{
- Contract.Requires(startIndex >= 0);
+ throw new FormatException("No cache directives found.");
+ }
+ return result;
+ }
- parsedValue = null;
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="CacheControlHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out CacheControlHeaderValue? parsedValue)
+ {
+ var 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);
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ 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;
+ var nameValueList = new List<NameValueHeaderValue>();
+ while (current < input.Length)
+ {
+ if (!NameValueHeaderValue.MultipleValueParser.TryParseValue(input, ref current, out var nameValue))
{
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;
- var nameValueList = new List<NameValueHeaderValue>();
- while (current < input.Length)
+ if (nameValue != null)
{
- if (!NameValueHeaderValue.MultipleValueParser.TryParseValue(input, ref current, out var nameValue))
- {
- return 0;
- }
-
- if (nameValue != null)
- {
- nameValueList.Add(nameValue);
- }
+ 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.
+ // 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();
+ // 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;
- }
+ if (!TrySetCacheControlValues(result, nameValueList))
+ {
+ return 0;
+ }
- parsedValue = result;
+ parsedValue = result;
- // If we get here we successfully parsed the whole string.
- return input.Length - startIndex;
- }
+ // If we get here we successfully parsed the whole string.
+ return input.Length - startIndex;
+ }
- private static bool TrySetCacheControlValues(
- CacheControlHeaderValue cc,
- List<NameValueHeaderValue> nameValueList)
+ private static bool TrySetCacheControlValues(
+ CacheControlHeaderValue cc,
+ List<NameValueHeaderValue> nameValueList)
+ {
+ for (var i = 0; i < nameValueList.Count; i++)
{
- 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;
+ 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 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 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 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 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 12:
- if (StringSegment.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
+ case 9:
+ if (StringSegment.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
+ if (success)
{
- success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
+ cc._maxStale = true;
}
- else
- {
- goto default;
- }
- break;
+ }
+ else if (StringSegment.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTimeSpan(nameValue, ref cc._minFresh);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
- case 14:
- if (StringSegment.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
- {
- success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
- }
- else
- {
- goto default;
- }
- break;
+ case 12:
+ if (StringSegment.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
- case 15:
- if (StringSegment.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
- {
- success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
- }
- else
- {
- goto default;
- }
- break;
+ case 14:
+ if (StringSegment.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
- case 16:
- if (StringSegment.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
- {
- success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
- }
- else
- {
- goto default;
- }
- break;
+ case 15:
+ if (StringSegment.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
- default:
- cc.Extensions.Add(nameValue); // success is always true
- break;
- }
+ case 16:
+ if (StringSegment.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
+ {
+ success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
+ }
+ else
+ {
+ goto default;
+ }
+ break;
- if (!success)
- {
- return false;
- }
+ default:
+ cc.Extensions.Add(nameValue); // success is always true
+ break;
}
- return true;
- }
-
- private static bool TrySetTokenOnlyValue(NameValueHeaderValue nameValue, ref bool boolField)
- {
- if (nameValue.Value != null)
+ 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)
+ {
+ if (nameValue.Value == null)
+ {
boolField = true;
return true;
}
- private static bool TrySetOptionalTokenList(
- NameValueHeaderValue nameValue,
- ref bool boolField,
- ref ICollection<StringSegment>? destination)
+ // 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)
{
- if (nameValue.Value == null)
+ current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
+ out separatorFound);
+
+ if (current == maxLength)
{
- boolField = true;
- return true;
+ break;
}
- // 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] != '\"'))
+ 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;
}
- // 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)
+ if (destination == null)
{
- 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;
- }
+ destination = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
+ }
- if (destination == null)
- {
- destination = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
- }
+ destination.Add(valueString.Subsegment(current, tokenLength));
- destination.Add(valueString.Subsegment(current, tokenLength));
+ current = 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;
+ }
- // 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)
+ {
+ if (nameValue.Value == null)
+ {
return false;
}
- private static bool TrySetTimeSpan(NameValueHeaderValue nameValue, ref TimeSpan? timeSpan)
+ int seconds;
+ if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds))
{
- if (nameValue.Value == null)
- {
- return false;
- }
+ return false;
+ }
- int seconds;
- if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds))
- {
- return false;
- }
+ timeSpan = new TimeSpan(0, 0, seconds);
- timeSpan = new TimeSpan(0, 0, seconds);
+ return true;
+ }
- return true;
+ private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
+ {
+ if (appendValue)
+ {
+ AppendValueWithSeparatorIfRequired(sb, value);
}
+ }
- private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
+ private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
+ {
+ if (sb.Length > 0)
{
- if (appendValue)
- {
- AppendValueWithSeparatorIfRequired(sb, value);
- }
+ sb.Append(", ");
}
+ sb.Append(value);
+ }
- private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
+ private static void AppendValues(StringBuilder sb, IEnumerable<StringSegment> values)
+ {
+ var first = true;
+ foreach (var value in values)
{
- if (sb.Length > 0)
+ if (first)
{
- sb.Append(", ");
+ first = false;
}
- sb.Append(value);
- }
-
- private static void AppendValues(StringBuilder sb, IEnumerable<StringSegment> values)
- {
- var first = true;
- foreach (var value in values)
+ else
{
- if (first)
- {
- first = false;
- }
- else
- {
- sb.Append(", ");
- }
-
- sb.Append(value.AsSpan());
+ sb.Append(", ");
}
- }
- private static void CheckIsValidToken(StringSegment item)
- {
- HeaderUtilities.CheckValidToken(item, nameof(item));
+ sb.Append(value.AsSpan());
}
}
+
+ 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
index 1cf3b08242..0ce6151204 100644
--- a/src/Http/Headers/src/ContentDispositionHeaderValue.cs
+++ b/src/Http/Headers/src/ContentDispositionHeaderValue.cs
@@ -13,819 +13,818 @@ using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents the value of a <c>Content-Disposition</c> header.
+/// </summary>
+/// <remarks>
+/// Note this is for use both in HTTP (https://tools.ietf.org/html/rfc6266) and MIME (https://tools.ietf.org/html/rfc2183)
+/// </remarks>
+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 const int MaxStackAllocSizeBytes = 256;
+ private static readonly char[] QuestionMark = new char[] { '?' };
+ private static readonly char[] SingleQuote = new char[] { '\'' };
+ private static readonly char[] EscapeChars = new char[] { '\\', '"' };
+ private static ReadOnlySpan<byte> MimePrefix => new byte[] { (byte)'"', (byte)'=', (byte)'?', (byte)'u', (byte)'t', (byte)'f', (byte)'-', (byte)'8', (byte)'?', (byte)'B', (byte)'?' };
+ private static ReadOnlySpan<byte> MimeSuffix => new byte[] { (byte)'?', (byte)'=', (byte)'"' };
+
+ 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.
+ }
+
/// <summary>
- /// Represents the value of a <c>Content-Disposition</c> header.
+ /// Initializes a new instance of <see cref="ContentDispositionHeaderValue"/>.
/// </summary>
- /// <remarks>
- /// Note this is for use both in HTTP (https://tools.ietf.org/html/rfc6266) and MIME (https://tools.ietf.org/html/rfc2183)
- /// </remarks>
- 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 const int MaxStackAllocSizeBytes = 256;
- private static readonly char[] QuestionMark = new char[] { '?' };
- private static readonly char[] SingleQuote = new char[] { '\'' };
- private static readonly char[] EscapeChars = new char[] { '\\', '"' };
- private static ReadOnlySpan<byte> MimePrefix => new byte[] { (byte)'"', (byte)'=', (byte)'?', (byte)'u', (byte)'t', (byte)'f', (byte)'-', (byte)'8', (byte)'?', (byte)'B', (byte)'?' };
- private static ReadOnlySpan<byte> MimeSuffix => new byte[] { (byte)'?', (byte)'=', (byte)'"' };
-
- 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.
- }
-
- /// <summary>
- /// Initializes a new instance of <see cref="ContentDispositionHeaderValue"/>.
- /// </summary>
- /// <param name="dispositionType">A <see cref="StringSegment"/> that represents a content disposition type.</param>
- public ContentDispositionHeaderValue(StringSegment dispositionType)
- {
- CheckDispositionTypeFormat(dispositionType, "dispositionType");
- _dispositionType = dispositionType;
- }
-
- /// <summary>
- /// Gets or sets a content disposition type.
- /// </summary>
- public StringSegment DispositionType
- {
- get { return _dispositionType; }
- set
- {
- CheckDispositionTypeFormat(value, "value");
- _dispositionType = value;
- }
+ /// <param name="dispositionType">A <see cref="StringSegment"/> that represents a content disposition type.</param>
+ public ContentDispositionHeaderValue(StringSegment dispositionType)
+ {
+ CheckDispositionTypeFormat(dispositionType, "dispositionType");
+ _dispositionType = dispositionType;
+ }
+
+ /// <summary>
+ /// Gets or sets a content disposition type.
+ /// </summary>
+ public StringSegment DispositionType
+ {
+ get { return _dispositionType; }
+ set
+ {
+ CheckDispositionTypeFormat(value, "value");
+ _dispositionType = value;
}
+ }
- /// <summary>
- /// Gets a collection of parameters included the <c>Content-Disposition</c> header.
- /// </summary>
- public IList<NameValueHeaderValue> Parameters
+ /// <summary>
+ /// Gets a collection of parameters included the <c>Content-Disposition</c> header.
+ /// </summary>
+ public IList<NameValueHeaderValue> Parameters
+ {
+ get
{
- get
+ if (_parameters == null)
{
- if (_parameters == null)
- {
- _parameters = new ObjectCollection<NameValueHeaderValue>();
- }
- return _parameters;
+ _parameters = new ObjectCollection<NameValueHeaderValue>();
}
+ return _parameters;
}
+ }
- // Helpers to access specific parameters in the list
+ // Helpers to access specific parameters in the list
- /// <summary>
- /// Gets or sets the name of the content body part.
- /// </summary>
- public StringSegment Name
- {
- get { return GetName(NameString); }
- set { SetName(NameString, value); }
- }
+ /// <summary>
+ /// Gets or sets the name of the content body part.
+ /// </summary>
+ public StringSegment Name
+ {
+ get { return GetName(NameString); }
+ set { SetName(NameString, value); }
+ }
- /// <summary>
- /// Gets or sets a value that suggests how to construct a filename for storing the message payload
- /// to be used if the entity is detached and stored in a separate file.
- /// </summary>
- public StringSegment FileName
- {
- get { return GetName(FileNameString); }
- set { SetName(FileNameString, value); }
- }
+ /// <summary>
+ /// Gets or sets a value that suggests how to construct a filename for storing the message payload
+ /// to be used if the entity is detached and stored in a separate file.
+ /// </summary>
+ public StringSegment FileName
+ {
+ get { return GetName(FileNameString); }
+ set { SetName(FileNameString, value); }
+ }
- /// <summary>
- /// Gets or sets a value that suggests how to construct filenames for storing message payloads
- /// to be used if the entities are detached and stored in a separate files.
- /// </summary>
- public StringSegment FileNameStar
- {
- get { return GetName(FileNameStarString); }
- set { SetName(FileNameStarString, value); }
- }
+ /// <summary>
+ /// Gets or sets a value that suggests how to construct filenames for storing message payloads
+ /// to be used if the entities are detached and stored in a separate files.
+ /// </summary>
+ public StringSegment FileNameStar
+ {
+ get { return GetName(FileNameStarString); }
+ set { SetName(FileNameStarString, value); }
+ }
- /// <summary>
- /// Gets or sets the <see cref="DateTimeOffset"/> at which the file was created.
- /// </summary>
- public DateTimeOffset? CreationDate
- {
- get { return GetDate(CreationDateString); }
- set { SetDate(CreationDateString, value); }
- }
+ /// <summary>
+ /// Gets or sets the <see cref="DateTimeOffset"/> at which the file was created.
+ /// </summary>
+ public DateTimeOffset? CreationDate
+ {
+ get { return GetDate(CreationDateString); }
+ set { SetDate(CreationDateString, value); }
+ }
- /// <summary>
- /// Gets or sets the <see cref="DateTimeOffset"/> at which the file was last modified.
- /// </summary>
- public DateTimeOffset? ModificationDate
- {
- get { return GetDate(ModificationDateString); }
- set { SetDate(ModificationDateString, value); }
- }
+ /// <summary>
+ /// Gets or sets the <see cref="DateTimeOffset"/> at which the file was last modified.
+ /// </summary>
+ public DateTimeOffset? ModificationDate
+ {
+ get { return GetDate(ModificationDateString); }
+ set { SetDate(ModificationDateString, value); }
+ }
- /// <summary>
- /// Gets or sets the <see cref="DateTimeOffset"/> at which the file was last read.
- /// </summary>
- public DateTimeOffset? ReadDate
- {
- get { return GetDate(ReadDateString); }
- set { SetDate(ReadDateString, value); }
- }
+ /// <summary>
+ /// Gets or sets the <see cref="DateTimeOffset"/> at which the file was last read.
+ /// </summary>
+ public DateTimeOffset? ReadDate
+ {
+ get { return GetDate(ReadDateString); }
+ set { SetDate(ReadDateString, value); }
+ }
- /// <summary>
- /// Gets or sets the approximate size, in bytes, of the file.
- /// </summary>
- public long? Size
+ /// <summary>
+ /// Gets or sets the approximate size, in bytes, of the file.
+ /// </summary>
+ public long? Size
+ {
+ get
{
- get
+ var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
+ if (sizeParameter != null)
{
- var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
- if (sizeParameter != null)
+ var sizeString = sizeParameter.Value;
+ if (HeaderUtilities.TryParseNonNegativeInt64(sizeString, out var value))
{
- var sizeString = sizeParameter.Value;
- if (HeaderUtilities.TryParseNonNegativeInt64(sizeString, out var value))
- {
- return value;
- }
+ return value;
}
- return null;
}
- set
+ return null;
+ }
+ set
+ {
+ var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
+ if (value == null)
{
- 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.GetValueOrDefault().ToString(CultureInfo.InvariantCulture);
- }
- else
+ // Remove parameter
+ if (sizeParameter != null)
{
- var sizeString = value.GetValueOrDefault().ToString(CultureInfo.InvariantCulture);
- Parameters.Add(new NameValueHeaderValue(SizeString, sizeString));
+ _parameters!.Remove(sizeParameter);
}
}
- }
-
- /// <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))
+ else if (value < 0)
{
- FileName = Sanitize(fileName);
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ else if (sizeParameter != null)
+ {
+ sizeParameter.Value = value.GetValueOrDefault().ToString(CultureInfo.InvariantCulture);
}
else
{
- FileName = fileName;
+ var sizeString = value.GetValueOrDefault().ToString(CultureInfo.InvariantCulture);
+ Parameters.Add(new NameValueHeaderValue(SizeString, sizeString));
}
- FileNameStar = fileName;
}
+ }
- /// <summary>
- /// Sets the FileName parameter using encodings appropriate for MIME headers.
- /// The FileNameStar parameter is removed.
- /// </summary>
- /// <param name="fileName"></param>
- public void SetMimeFileName(StringSegment fileName)
+ /// <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))
{
- FileNameStar = null;
- FileName = fileName;
+ FileName = Sanitize(fileName);
}
-
- /// <inheritdoc />
- public override string ToString()
+ else
{
- return _dispositionType + NameValueHeaderValue.ToString(_parameters, ';', true);
+ FileName = fileName;
}
+ FileNameStar = fileName;
+ }
- /// <inheritdoc />
- public override bool Equals(object? obj)
- {
- var other = obj as ContentDispositionHeaderValue;
+ /// <summary>
+ /// Sets the FileName parameter using encodings appropriate for MIME headers.
+ /// The FileNameStar parameter is removed.
+ /// </summary>
+ /// <param name="fileName"></param>
+ public void SetMimeFileName(StringSegment fileName)
+ {
+ FileNameStar = null;
+ FileName = fileName;
+ }
- if (other == null)
- {
- return false;
- }
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ return _dispositionType + NameValueHeaderValue.ToString(_parameters, ';', true);
+ }
- return _dispositionType.Equals(other._dispositionType, StringComparison.OrdinalIgnoreCase) &&
- HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
- }
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as ContentDispositionHeaderValue;
- /// <inheritdoc />
- public override int GetHashCode()
+ if (other == null)
{
- // The dispositionType string is case-insensitive.
- return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_dispositionType) ^ NameValueHeaderValue.GetHashCode(_parameters);
+ return false;
}
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="ContentDispositionHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static ContentDispositionHeaderValue Parse(StringSegment input)
+ return _dispositionType.Equals(other._dispositionType, StringComparison.OrdinalIgnoreCase) &&
+ HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ // The dispositionType string is case-insensitive.
+ return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_dispositionType) ^ NameValueHeaderValue.GetHashCode(_parameters);
+ }
+
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="ContentDispositionHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static ContentDispositionHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return Parser.ParseValue(input, ref index)!;
+ }
+
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="ContentDispositionHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="ContentDispositionHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] 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))
{
- var index = 0;
- return Parser.ParseValue(input, ref index)!;
+ return 0;
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="ContentDispositionHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="ContentDispositionHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out ContentDispositionHeaderValue? parsedValue)
+ // Caller must remove leading whitespaces. If not, we'll return 0.
+ var dispositionTypeLength = GetDispositionTypeExpressionLength(input, startIndex, out var dispositionType);
+
+ if (dispositionTypeLength == 0)
{
- var index = 0;
- return Parser.TryParseValue(input, ref index, out parsedValue!);
+ return 0;
}
- private static int GetDispositionTypeLength(StringSegment input, int startIndex, out ContentDispositionHeaderValue? parsedValue)
+ 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] == ';'))
{
- Contract.Requires(startIndex >= 0);
+ current++; // skip delimiter.
+ int parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';',
+ contentDispositionHeader.Parameters);
- parsedValue = null;
+ parsedValue = contentDispositionHeader;
+ return current + parameterLength - startIndex;
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ // We have a ContentDisposition header without parameters.
+ parsedValue = contentDispositionHeader;
+ return current - startIndex;
+ }
- // Caller must remove leading whitespaces. If not, we'll return 0.
- var dispositionTypeLength = GetDispositionTypeExpressionLength(input, startIndex, out var dispositionType);
+ private static int GetDispositionTypeExpressionLength(StringSegment input, int startIndex, out StringSegment dispositionType)
+ {
+ Contract.Requires((input.Length > 0) && (startIndex < input.Length));
- if (dispositionTypeLength == 0)
- {
- return 0;
- }
+ // This method just parses the disposition type string, it does not parse parameters.
+ dispositionType = null;
- var current = startIndex + dispositionTypeLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- var contentDispositionHeader = new ContentDispositionHeaderValue();
- contentDispositionHeader._dispositionType = dispositionType;
+ // Parse the disposition type, i.e. <dispositiontype> in content-disposition string
+ // "<dispositiontype>; param1=value1; param2=value2"
+ var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);
- // 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);
+ if (typeLength == 0)
+ {
+ return 0;
+ }
- parsedValue = contentDispositionHeader;
- return current + parameterLength - startIndex;
- }
+ dispositionType = input.Subsegment(startIndex, typeLength);
+ return typeLength;
+ }
- // We have a ContentDisposition header without parameters.
- parsedValue = contentDispositionHeader;
- return current - startIndex;
+ private static void CheckDispositionTypeFormat(StringSegment dispositionType, string parameterName)
+ {
+ if (StringSegment.IsNullOrEmpty(dispositionType))
+ {
+ throw new ArgumentException("An empty string is not allowed.", parameterName);
}
- private static int GetDispositionTypeExpressionLength(StringSegment input, int startIndex, out StringSegment dispositionType)
+ // 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))
{
- Contract.Requires((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;
+ throw new FormatException(string.Format(CultureInfo.InvariantCulture,
+ "Invalid disposition type '{0}'.", dispositionType));
}
+ }
- private static void CheckDispositionTypeFormat(StringSegment dispositionType, string parameterName)
+ // 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)
{
- if (StringSegment.IsNullOrEmpty(dispositionType))
+ var dateString = dateParameter.Value;
+ // Should have quotes, remove them.
+ if (IsQuoted(dateString))
{
- throw new ArgumentException("An empty string is not allowed.", parameterName);
+ dateString = dateString.Subsegment(1, dateString.Length - 2);
}
-
- // 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))
+ DateTimeOffset date;
+ if (HttpRuleParser.TryStringToDate(dateString, out date))
{
- throw new FormatException(string.Format(CultureInfo.InvariantCulture,
- "Invalid disposition type '{0}'.", dispositionType));
+ return date;
}
}
+ return null;
+ }
- // 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)
+ // 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)
{
- var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
+ // Remove 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;
- }
+ _parameters!.Remove(dateParameter);
}
- return null;
}
-
- // Add the given parameter to the list. Remove if date is null.
- private void SetDate(string parameter, DateTimeOffset? date)
+ else
{
- var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
- if (date == null)
+ // Must always be quoted
+ var dateString = HeaderUtilities.FormatDate(date.GetValueOrDefault(), quoted: true);
+ if (dateParameter != null)
{
- // Remove parameter
- if (dateParameter != null)
- {
- _parameters!.Remove(dateParameter);
- }
+ dateParameter.Value = dateString;
}
else
{
- // Must always be quoted
- var dateString = HeaderUtilities.FormatDate(date.GetValueOrDefault(), quoted: true);
- if (dateParameter != null)
- {
- dateParameter.Value = dateString;
- }
- else
- {
- Parameters.Add(new NameValueHeaderValue(parameter, dateString));
- }
+ 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)
+ // 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)
{
- var nameParameter = NameValueHeaderValue.Find(_parameters, parameter);
- if (nameParameter != null)
+ string? result;
+ // filename*=utf-8'lang'%7FMyString
+ if (parameter.EndsWith("*", StringComparison.Ordinal))
{
- 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))
+ if (TryDecode5987(nameParameter.Value, out result))
{
return result;
}
- // May not have been encoded
- return HeaderUtilities.RemoveQuotes(nameParameter.Value);
+ return null; // Unrecognized encoding
}
- 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
+ // filename="=?utf-8?B?BDFSDFasdfasdc==?="
+ if (TryDecodeMime(nameParameter.Value, out result))
{
- 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));
- }
+ return result;
}
+ // May not have been encoded
+ return HeaderUtilities.RemoveQuotes(nameParameter.Value);
}
+ return null;
+ }
- // Returns input for decoding failures, as the content might not be encoded
- private StringSegment EncodeAndQuoteMime(StringSegment input)
+ // 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))
{
- var result = input;
- var needsQuotes = false;
- // Remove bounding quotes, they'll get re-added later
- if (IsQuoted(result))
+ // Remove parameter
+ if (nameParameter != null)
{
- result = result.Subsegment(1, result.Length - 2);
- needsQuotes = true;
+ _parameters!.Remove(nameParameter);
}
-
- if (RequiresEncoding(result))
+ }
+ else
+ {
+ var processedValue = StringSegment.Empty;
+ if (parameter.EndsWith("*", StringComparison.Ordinal))
{
- // EncodeMimeWithQuotes will Base64 encode any quotes in the input, and surround the payload in quotes
- // so there is no need to add quotes
- needsQuotes = false;
- result = EncodeMimeWithQuotes(result); // "=?utf-8?B?asdfasdfaesdf?="
+ processedValue = Encode5987(value);
}
- else if (!needsQuotes && HttpRuleParser.GetTokenLength(result, 0) != result.Length)
+ else
{
- needsQuotes = true;
+ processedValue = EncodeAndQuoteMime(value);
}
- if (needsQuotes)
+ if (nameParameter != null)
{
- if (result.IndexOfAny(EscapeChars) != -1)
- {
- // '\' and '"' must be escaped in a quoted string
- result = result.ToString().Replace(@"\", @"\\").Replace(@"""", @"\""");
- }
- // Re-add quotes "value"
- result = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", result);
+ nameParameter.Value = processedValue;
}
- return result;
- }
-
- // Replaces characters not suitable for HTTP headers with '_' rather than MIME encoding them.
- private StringSegment Sanitize(StringSegment input)
- {
- var result = input;
-
- if (RequiresEncoding(result))
+ else
{
- var builder = new StringBuilder(result.Length);
- for (int i = 0; i < result.Length; i++)
- {
- var c = result[i];
- if ((int)c > 0x7f || (int)c < 0x20)
- {
- c = '_'; // Replace out-of-range characters
- }
- builder.Append(c);
- }
- result = builder.ToString();
+ Parameters.Add(new NameValueHeaderValue(parameter, processedValue));
}
-
- return result;
}
+ }
- // Returns true if the value starts and ends with a quote
- private static bool IsQuoted(StringSegment value)
+ // 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))
{
- Contract.Assert(value != null);
+ result = result.Subsegment(1, result.Length - 2);
+ needsQuotes = true;
+ }
- return value.Length > 1 && value.StartsWith("\"", StringComparison.Ordinal)
- && value.EndsWith("\"", StringComparison.Ordinal);
+ if (RequiresEncoding(result))
+ {
+ // EncodeMimeWithQuotes will Base64 encode any quotes in the input, and surround the payload in quotes
+ // so there is no need to add quotes
+ needsQuotes = false;
+ result = EncodeMimeWithQuotes(result); // "=?utf-8?B?asdfasdfaesdf?="
+ }
+ else if (!needsQuotes && HttpRuleParser.GetTokenLength(result, 0) != result.Length)
+ {
+ needsQuotes = true;
}
- // tspecials are required to be in a quoted string. Only non-ascii needs to be encoded.
- private static bool RequiresEncoding(StringSegment input)
+ if (needsQuotes)
{
- Contract.Assert(input != null);
+ if (result.IndexOfAny(EscapeChars) != -1)
+ {
+ // '\' 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 Sanitize(StringSegment input)
+ {
+ var result = input;
- for (int i = 0; i < input.Length; i++)
+ if (RequiresEncoding(result))
+ {
+ var builder = new StringBuilder(result.Length);
+ for (int i = 0; i < result.Length; i++)
{
- if ((int)input[i] > 0x7f || (int)input[i] < 0x20)
+ var c = result[i];
+ if ((int)c > 0x7f || (int)c < 0x20)
{
- return true;
+ c = '_'; // Replace out-of-range characters
}
+ builder.Append(c);
}
- return false;
+ result = builder.ToString();
}
- // Encode using MIME encoding
- // And adds surrounding quotes, Encoded data must always be quoted, the equals signs are invalid in tokens
- [SkipLocalsInit]
- private string EncodeMimeWithQuotes(StringSegment input)
- {
- var requiredLength = MimePrefix.Length +
- Base64.GetMaxEncodedToUtf8Length(Encoding.UTF8.GetByteCount(input.AsSpan())) +
- MimeSuffix.Length;
- byte[]? bufferFromPool = null;
- Span<byte> buffer = requiredLength <= MaxStackAllocSizeBytes
- ? stackalloc byte[MaxStackAllocSizeBytes]
- : bufferFromPool = ArrayPool<byte>.Shared.Rent(requiredLength);
- buffer = buffer[..requiredLength];
-
- MimePrefix.CopyTo(buffer);
- var bufferContent = buffer.Slice(MimePrefix.Length);
- var contentLength = Encoding.UTF8.GetBytes(input.AsSpan(), bufferContent);
+ return result;
+ }
- Base64.EncodeToUtf8InPlace(bufferContent, contentLength, out var base64ContentLength);
+ // Returns true if the value starts and ends with a quote
+ private static bool IsQuoted(StringSegment value)
+ {
+ Contract.Assert(value != null);
- MimeSuffix.CopyTo(bufferContent.Slice(base64ContentLength));
+ return value.Length > 1 && value.StartsWith("\"", StringComparison.Ordinal)
+ && value.EndsWith("\"", StringComparison.Ordinal);
+ }
- var result = Encoding.UTF8.GetString(buffer.Slice(0, MimePrefix.Length + base64ContentLength + MimeSuffix.Length));
+ // tspecials are required to be in a quoted string. Only non-ascii needs to be encoded.
+ private static bool RequiresEncoding(StringSegment input)
+ {
+ Contract.Assert(input != null);
- if (bufferFromPool is not null)
+ for (int i = 0; i < input.Length; i++)
+ {
+ if ((int)input[i] > 0x7f || (int)input[i] < 0x20)
{
- ArrayPool<byte>.Shared.Return(bufferFromPool);
+ return true;
}
-
- return result;
}
+ return false;
+ }
+
+ // Encode using MIME encoding
+ // And adds surrounding quotes, Encoded data must always be quoted, the equals signs are invalid in tokens
+ [SkipLocalsInit]
+ private string EncodeMimeWithQuotes(StringSegment input)
+ {
+ var requiredLength = MimePrefix.Length +
+ Base64.GetMaxEncodedToUtf8Length(Encoding.UTF8.GetByteCount(input.AsSpan())) +
+ MimeSuffix.Length;
+ byte[]? bufferFromPool = null;
+ Span<byte> buffer = requiredLength <= MaxStackAllocSizeBytes
+ ? stackalloc byte[MaxStackAllocSizeBytes]
+ : bufferFromPool = ArrayPool<byte>.Shared.Rent(requiredLength);
+ buffer = buffer[..requiredLength];
+
+ MimePrefix.CopyTo(buffer);
+ var bufferContent = buffer.Slice(MimePrefix.Length);
+ var contentLength = Encoding.UTF8.GetBytes(input.AsSpan(), bufferContent);
+
+ Base64.EncodeToUtf8InPlace(bufferContent, contentLength, out var base64ContentLength);
+
+ MimeSuffix.CopyTo(bufferContent.Slice(base64ContentLength));
- // Attempt to decode MIME encoded strings
- private bool TryDecodeMime(StringSegment input, [NotNullWhen(true)] out string? output)
+ var result = Encoding.UTF8.GetString(buffer.Slice(0, MimePrefix.Length + base64ContentLength + MimeSuffix.Length));
+
+ if (bufferFromPool is not null)
{
- Contract.Assert(input != null);
+ ArrayPool<byte>.Shared.Return(bufferFromPool);
+ }
- output = null;
- var processedInput = input;
- // Require quotes, min of "=?e?b??="
- if (!IsQuoted(processedInput) || processedInput.Length < 10)
- {
- return false;
- }
+ return result;
+ }
- 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;
- }
+ // Attempt to decode MIME encoded strings
+ private bool TryDecodeMime(StringSegment input, [NotNullWhen(true)] out string? output)
+ {
+ Contract.Assert(input != null);
- 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
- }
+ output = null;
+ var processedInput = input;
+ // Require quotes, min of "=?e?b??="
+ if (!IsQuoted(processedInput) || processedInput.Length < 10)
+ {
return false;
}
- // Encode a string using RFC 5987 encoding
- // encoding'lang'PercentEncodedSpecials
- [SkipLocalsInit]
- private static string Encode5987(StringSegment input)
+ var parts = processedInput.Split(QuestionMark).ToArray();
+ // "=, encodingName, encodingType, encodedData, ="
+ if (parts.Length != 5 || parts[0] != "\"=" || parts[4] != "=\""
+ || !parts[2].Equals("b", StringComparison.OrdinalIgnoreCase))
{
- var builder = new StringBuilder("UTF-8\'\'");
+ // 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;
+ }
- var maxInputBytes = Encoding.UTF8.GetMaxByteCount(input.Length);
- byte[]? bufferFromPool = null;
- Span<byte> inputBytes = maxInputBytes <= MaxStackAllocSizeBytes
- ? stackalloc byte[MaxStackAllocSizeBytes]
- : bufferFromPool = ArrayPool<byte>.Shared.Rent(maxInputBytes);
+ // Encode a string using RFC 5987 encoding
+ // encoding'lang'PercentEncodedSpecials
+ [SkipLocalsInit]
+ private static string Encode5987(StringSegment input)
+ {
+ var builder = new StringBuilder("UTF-8\'\'");
- var bytesWritten = Encoding.UTF8.GetBytes(input, inputBytes);
- inputBytes = inputBytes[..bytesWritten];
+ var maxInputBytes = Encoding.UTF8.GetMaxByteCount(input.Length);
+ byte[]? bufferFromPool = null;
+ Span<byte> inputBytes = maxInputBytes <= MaxStackAllocSizeBytes
+ ? stackalloc byte[MaxStackAllocSizeBytes]
+ : bufferFromPool = ArrayPool<byte>.Shared.Rent(maxInputBytes);
- int totalBytesConsumed = 0;
- while (totalBytesConsumed < inputBytes.Length)
- {
- if (inputBytes[totalBytesConsumed] <= 0x7F)
- {
- // This is an ASCII char. Let's handle it ourselves.
+ var bytesWritten = Encoding.UTF8.GetBytes(input, inputBytes);
+ inputBytes = inputBytes[..bytesWritten];
- char c = (char)inputBytes[totalBytesConsumed];
- if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
- {
- HexEscape(builder, c);
- }
- else
- {
- builder.Append(c);
- }
+ int totalBytesConsumed = 0;
+ while (totalBytesConsumed < inputBytes.Length)
+ {
+ if (inputBytes[totalBytesConsumed] <= 0x7F)
+ {
+ // This is an ASCII char. Let's handle it ourselves.
- totalBytesConsumed++;
+ char c = (char)inputBytes[totalBytesConsumed];
+ if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
+ {
+ HexEscape(builder, c);
}
else
{
- // Non-ASCII, let's rely on Rune to decode it.
+ builder.Append(c);
+ }
- Rune.DecodeFromUtf8(inputBytes.Slice(totalBytesConsumed), out Rune r, out int bytesConsumedForRune);
- Contract.Assert(!r.IsAscii, "We shouldn't have gotten here if the Rune is ASCII.");
+ totalBytesConsumed++;
+ }
+ else
+ {
+ // Non-ASCII, let's rely on Rune to decode it.
- for (int i = 0; i < bytesConsumedForRune; i++)
- {
- HexEscape(builder, (char)inputBytes[totalBytesConsumed + i]);
- }
+ Rune.DecodeFromUtf8(inputBytes.Slice(totalBytesConsumed), out Rune r, out int bytesConsumedForRune);
+ Contract.Assert(!r.IsAscii, "We shouldn't have gotten here if the Rune is ASCII.");
- totalBytesConsumed += bytesConsumedForRune;
+ for (int i = 0; i < bytesConsumedForRune; i++)
+ {
+ HexEscape(builder, (char)inputBytes[totalBytesConsumed + i]);
}
- }
- if (bufferFromPool is not null)
- {
- ArrayPool<byte>.Shared.Return(bufferFromPool);
+ totalBytesConsumed += bytesConsumedForRune;
}
+ }
- return builder.ToString();
+ if (bufferFromPool is not null)
+ {
+ ArrayPool<byte>.Shared.Return(bufferFromPool);
}
- private static readonly char[] HexUpperChars = {
+ 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)
+ 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 static bool TryDecode5987(StringSegment input, [NotNullWhen(true)] out string? output)
+ {
+ output = null;
+
+ var parts = input.Split(SingleQuote).ToArray();
+ if (parts.Length != 3)
{
- builder.Append('%');
- builder.Append(HexUpperChars[(c & 0xf0) >> 4]);
- builder.Append(HexUpperChars[c & 0xf]);
+ return false;
}
- // Attempt to decode using RFC 5987 encoding.
- // encoding'language'my%20string
- private static bool TryDecode5987(StringSegment input, [NotNullWhen(true)] out string? output)
+ var decoded = new StringBuilder();
+ byte[]? unescapedBytes = null;
+ try
{
- output = null;
+ var encoding = Encoding.GetEncoding(parts[0].ToString());
- var parts = input.Split(SingleQuote).ToArray();
- if (parts.Length != 3)
+ var dataString = parts[2];
+ unescapedBytes = ArrayPool<byte>.Shared.Rent(dataString.Length);
+ var unescapedBytesCount = 0;
+ for (var index = 0; index < dataString.Length; index++)
{
- 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
{
- 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
- }
+ // 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 ++
}
-
- if (unescapedBytesCount > 0)
+ else
{
- // Decode any previously cached bytes
- decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
+ if (unescapedBytesCount > 0)
+ {
+ // Decode any previously cached bytes
+ decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
+ unescapedBytesCount = 0;
+ }
+ decoded.Append(dataString[index]); // Normal safe character
}
}
- catch (ArgumentException)
+
+ if (unescapedBytesCount > 0)
{
- return false; // Unknown encoding or bad characters
+ // Decode any previously cached bytes
+ decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
}
- finally
+ }
+ catch (ArgumentException)
+ {
+ return false; // Unknown encoding or bad characters
+ }
+ finally
+ {
+ if (unescapedBytes != null)
{
- if (unescapedBytes != null)
- {
- ArrayPool<byte>.Shared.Return(unescapedBytes);
- }
+ ArrayPool<byte>.Shared.Return(unescapedBytes);
}
+ }
- output = decoded.ToString();
+ 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 IsHexEncoding(StringSegment pattern, int index)
+ private static bool IsEscapedAscii(char digit, char next)
+ {
+ if (!(((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))))
{
- 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 (!(((next >= '0') && (next <= '9'))
+ || ((next >= 'A') && (next <= 'F'))
+ || ((next >= 'a') && (next <= 'f'))))
{
- if (!(((digit >= '0') && (digit <= '9'))
- || ((digit >= 'A') && (digit <= 'F'))
- || ((digit >= 'a') && (digit <= 'f'))))
- {
- return false;
- }
+ return false;
+ }
- if (!(((next >= '0') && (next <= '9'))
- || ((next >= 'A') && (next <= 'F'))
- || ((next >= 'a') && (next <= 'f'))))
- {
- return false;
- }
+ return true;
+ }
- return true;
+ private static byte HexUnescape(StringSegment pattern, ref int index)
+ {
+ if ((index < 0) || (index >= pattern.Length))
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
}
-
- private static byte HexUnescape(StringSegment pattern, ref int index)
+ if ((pattern[index] == '%')
+ && (pattern.Length - index >= 3))
{
- 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++];
+ 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)
+ internal static byte UnEscapeAscii(char digit, char next)
+ {
+ if (!(((digit >= '0') && (digit <= '9'))
+ || ((digit >= 'A') && (digit <= 'F'))
+ || ((digit >= 'a') && (digit <= 'f'))))
{
- if (!(((digit >= '0') && (digit <= '9'))
- || ((digit >= 'A') && (digit <= 'F'))
- || ((digit >= 'a') && (digit <= 'f'))))
- {
- throw new ArgumentOutOfRangeException(nameof(digit));
- }
-
- var res = (digit <= '9')
- ? ((int)digit - (int)'0')
- : (((digit <= 'F')
- ? ((int)digit - (int)'A')
- : ((int)digit - (int)'a'))
- + 10);
+ throw new ArgumentOutOfRangeException(nameof(digit));
+ }
- if (!(((next >= '0') && (next <= '9'))
- || ((next >= 'A') && (next <= 'F'))
- || ((next >= 'a') && (next <= 'f'))))
- {
- throw new ArgumentOutOfRangeException(nameof(next));
- }
+ var res = (digit <= '9')
+ ? ((int)digit - (int)'0')
+ : (((digit <= 'F')
+ ? ((int)digit - (int)'A')
+ : ((int)digit - (int)'a'))
+ + 10);
- return (byte)((res << 4) + ((next <= '9')
- ? ((int)next - (int)'0')
- : (((next <= 'F')
- ? ((int)next - (int)'A')
- : ((int)next - (int)'a'))
- + 10)));
+ if (!(((next >= '0') && (next <= '9'))
+ || ((next >= 'A') && (next <= 'F'))
+ || ((next >= 'a') && (next <= 'f'))))
+ {
+ throw new ArgumentOutOfRangeException(nameof(next));
}
+
+ return (byte)((res << 4) + ((next <= '9')
+ ? ((int)next - (int)'0')
+ : (((next <= 'F')
+ ? ((int)next - (int)'A')
+ : ((int)next - (int)'a'))
+ + 10)));
}
}
diff --git a/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs b/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs
index c713b7b005..82afd6b8c7 100644
--- a/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs
+++ b/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs
@@ -4,43 +4,42 @@
using System;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+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>
- /// Various extension methods for <see cref="ContentDispositionHeaderValue"/> for identifying the type of the disposition header
+ /// Checks if the content disposition header is a file disposition
/// </summary>
- public static class ContentDispositionHeaderValueIdentityExtensions
+ /// <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)
{
- /// <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)
{
- if (header == null)
- {
- throw new ArgumentNullException(nameof(header));
- }
-
- return header.DispositionType.Equals("form-data")
- && (!StringSegment.IsNullOrEmpty(header.FileName) || !StringSegment.IsNullOrEmpty(header.FileNameStar));
+ throw new ArgumentNullException(nameof(header));
}
- /// <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));
+ }
- 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
index 4a58c36d60..cada5ec343 100644
--- a/src/Http/Headers/src/ContentRangeHeaderValue.cs
+++ b/src/Http/Headers/src/ContentRangeHeaderValue.cs
@@ -8,443 +8,442 @@ using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents a <c>Content-Range</c> response HTTP header.
+/// </summary>
+public class ContentRangeHeaderValue
{
+ private static readonly HttpHeaderParser<ContentRangeHeaderValue> Parser
+ = new GenericHeaderParser<ContentRangeHeaderValue>(false, GetContentRangeLength);
+
+ private StringSegment _unit;
+
+ private ContentRangeHeaderValue()
+ {
+ // Used by the parser to create a new instance of this type.
+ }
+
/// <summary>
- /// Represents a <c>Content-Range</c> response HTTP header.
+ /// Initializes a new instance of <see cref="ContentRangeHeaderValue"/>.
/// </summary>
- public class ContentRangeHeaderValue
+ /// <param name="from">The start of the range.</param>
+ /// <param name="to">The end of the range.</param>
+ /// <param name="length">The total size of the document in bytes.</param>
+ public ContentRangeHeaderValue(long from, long to, long length)
{
- private static readonly HttpHeaderParser<ContentRangeHeaderValue> Parser
- = new GenericHeaderParser<ContentRangeHeaderValue>(false, GetContentRangeLength);
-
- private StringSegment _unit;
+ // Scenario: "Content-Range: bytes 12-34/5678"
- private ContentRangeHeaderValue()
+ if (length < 0)
{
- // Used by the parser to create a new instance of this type.
+ throw new ArgumentOutOfRangeException(nameof(length));
}
-
- /// <summary>
- /// Initializes a new instance of <see cref="ContentRangeHeaderValue"/>.
- /// </summary>
- /// <param name="from">The start of the range.</param>
- /// <param name="to">The end of the range.</param>
- /// <param name="length">The total size of the document in bytes.</param>
- public ContentRangeHeaderValue(long from, long to, long length)
+ if ((to < 0) || (to > 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;
+ throw new ArgumentOutOfRangeException(nameof(to));
}
-
- /// <summary>
- /// Initializes a new instance of <see cref="ContentRangeHeaderValue"/>.
- /// </summary>
- /// <param name="length">The total size of the document in bytes.</param>
- public ContentRangeHeaderValue(long length)
+ if ((from < 0) || (from > to))
{
- // Scenario: "Content-Range: bytes */1234"
-
- if (length < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(length));
- }
-
- Length = length;
- _unit = HeaderUtilities.BytesUnit;
+ throw new ArgumentOutOfRangeException(nameof(from));
}
- /// <summary>
- /// Initializes a new instance of <see cref="ContentRangeHeaderValue"/>.
- /// </summary>
- /// <param name="from">The start of the range.</param>
- /// <param name="to">The end of the range.</param>
- 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;
+ Length = length;
+ _unit = HeaderUtilities.BytesUnit;
+ }
- From = from;
- To = to;
- _unit = HeaderUtilities.BytesUnit;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ContentRangeHeaderValue"/>.
+ /// </summary>
+ /// <param name="length">The total size of the document in bytes.</param>
+ public ContentRangeHeaderValue(long length)
+ {
+ // Scenario: "Content-Range: bytes */1234"
- /// <summary>
- /// Gets or sets the unit in which ranges are specified.
- /// </summary>
- /// <value>Defaults to <c>bytes</c>.</value>
- public StringSegment Unit
+ if (length < 0)
{
- get { return _unit; }
- set
- {
- HeaderUtilities.CheckValidToken(value, nameof(value));
- _unit = value;
- }
+ throw new ArgumentOutOfRangeException(nameof(length));
}
- /// <summary>
- /// Gets the start of the range.
- /// </summary>
- public long? From { get; private set; }
-
- /// <summary>
- /// Gets the end of the range.
- /// </summary>
- public long? To { get; private set; }
+ Length = length;
+ _unit = HeaderUtilities.BytesUnit;
+ }
- /// <summary>
- /// Gets the total size of the document.
- /// </summary>
- [NotNullIfNotNull(nameof(Length))]
- public long? Length { get; private set; }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ContentRangeHeaderValue"/>.
+ /// </summary>
+ /// <param name="from">The start of the range.</param>
+ /// <param name="to">The end of the range.</param>
+ public ContentRangeHeaderValue(long from, long to)
+ {
+ // Scenario: "Content-Range: bytes 12-34/*"
- /// <summary>
- /// Gets a value that determines if <see cref="Length"/> has been specified.
- /// </summary>
- [MemberNotNullWhen(true, nameof(Length))]
- public bool HasLength // e.g. "Content-Range: bytes 12-34/*"
+ if (to < 0)
{
- get { return Length != null; }
+ throw new ArgumentOutOfRangeException(nameof(to));
}
-
- /// <summary>
- /// Gets a value that determines if <see cref="From"/> and <see cref="To"/> have been specified.
- /// </summary>
- [MemberNotNullWhen(true, nameof(From), nameof(To))]
- public bool HasRange // e.g. "Content-Range: bytes */1234"
+ if ((from < 0) || (from > to))
{
- get { return From != null && To != null; }
+ throw new ArgumentOutOfRangeException(nameof(@from));
}
- /// <inheritdoc/>
- public override bool Equals(object? obj)
+ From = from;
+ To = to;
+ _unit = HeaderUtilities.BytesUnit;
+ }
+
+ /// <summary>
+ /// Gets or sets the unit in which ranges are specified.
+ /// </summary>
+ /// <value>Defaults to <c>bytes</c>.</value>
+ public StringSegment Unit
+ {
+ get { return _unit; }
+ set
{
- var other = obj as ContentRangeHeaderValue;
+ HeaderUtilities.CheckValidToken(value, nameof(value));
+ _unit = value;
+ }
+ }
- if (other == null)
- {
- return false;
- }
+ /// <summary>
+ /// Gets the start of the range.
+ /// </summary>
+ public long? From { get; private set; }
- return ((From == other.From) && (To == other.To) && (Length == other.Length) &&
- StringSegment.Equals(Unit, other.Unit, StringComparison.OrdinalIgnoreCase));
- }
+ /// <summary>
+ /// Gets the end of the range.
+ /// </summary>
+ public long? To { get; private set; }
- /// <inheritdoc/>
- public override int GetHashCode()
- {
- var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(Unit);
+ /// <summary>
+ /// Gets the total size of the document.
+ /// </summary>
+ [NotNullIfNotNull(nameof(Length))]
+ public long? Length { get; private set; }
- if (HasRange)
- {
- result = result ^ From.GetHashCode() ^ To.GetHashCode();
- }
+ /// <summary>
+ /// Gets a value that determines if <see cref="Length"/> has been specified.
+ /// </summary>
+ [MemberNotNullWhen(true, nameof(Length))]
+ public bool HasLength // e.g. "Content-Range: bytes 12-34/*"
+ {
+ get { return Length != null; }
+ }
- if (HasLength)
- {
- result = result ^ Length.GetHashCode();
- }
+ /// <summary>
+ /// Gets a value that determines if <see cref="From"/> and <see cref="To"/> have been specified.
+ /// </summary>
+ [MemberNotNullWhen(true, nameof(From), nameof(To))]
+ public bool HasRange // e.g. "Content-Range: bytes */1234"
+ {
+ get { return From != null && To != null; }
+ }
- return result;
- }
+ /// <inheritdoc/>
+ public override bool Equals(object? obj)
+ {
+ var other = obj as ContentRangeHeaderValue;
- /// <inheritdoc/>
- public override string ToString()
+ if (other == null)
{
- var sb = new StringBuilder();
- sb.Append(Unit.AsSpan());
- sb.Append(' ');
+ return false;
+ }
- if (HasRange)
- {
- sb.Append(From.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo));
- sb.Append('-');
- sb.Append(To.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo));
- }
- else
- {
- sb.Append('*');
- }
+ return ((From == other.From) && (To == other.To) && (Length == other.Length) &&
+ StringSegment.Equals(Unit, other.Unit, StringComparison.OrdinalIgnoreCase));
+ }
- sb.Append('/');
- if (HasLength)
- {
- sb.Append(Length.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo));
- }
- else
- {
- sb.Append('*');
- }
+ /// <inheritdoc/>
+ public override int GetHashCode()
+ {
+ var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(Unit);
- return sb.ToString();
+ if (HasRange)
+ {
+ result = result ^ From.GetHashCode() ^ To.GetHashCode();
}
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="ContentRangeHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static ContentRangeHeaderValue Parse(StringSegment input)
+ if (HasLength)
{
- var index = 0;
- return Parser.ParseValue(input, ref index)!;
+ result = result ^ Length.GetHashCode();
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="ContentRangeHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="ContentRangeHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out ContentRangeHeaderValue parsedValue)
+ return result;
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(Unit.AsSpan());
+ sb.Append(' ');
+
+ if (HasRange)
+ {
+ sb.Append(From.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo));
+ sb.Append('-');
+ sb.Append(To.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo));
+ }
+ else
{
- var index = 0;
- return Parser.TryParseValue(input, ref index, out parsedValue!);
+ sb.Append('*');
}
- private static int GetContentRangeLength(StringSegment input, int startIndex, out ContentRangeHeaderValue? parsedValue)
+ sb.Append('/');
+ if (HasLength)
+ {
+ sb.Append(Length.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo));
+ }
+ else
{
- Contract.Requires(startIndex >= 0);
+ sb.Append('*');
+ }
- parsedValue = null;
+ return sb.ToString();
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="ContentRangeHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static ContentRangeHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return Parser.ParseValue(input, ref index)!;
+ }
- // Parse the unit string: <unit> in '<unit> <from>-<to>/<length>'
- var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="ContentRangeHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="ContentRangeHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out ContentRangeHeaderValue parsedValue)
+ {
+ var index = 0;
+ return Parser.TryParseValue(input, ref index, out parsedValue!);
+ }
- if (unitLength == 0)
- {
- return 0;
- }
+ private static int GetContentRangeLength(StringSegment input, int startIndex, out ContentRangeHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- var unit = input.Subsegment(startIndex, unitLength);
- var current = startIndex + unitLength;
- var separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);
+ parsedValue = null;
- if (separatorLength == 0)
- {
- return 0;
- }
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
- current = current + separatorLength;
+ // Parse the unit string: <unit> in '<unit> <from>-<to>/<length>'
+ var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
- if (current == input.Length)
- {
- return 0;
- }
+ if (unitLength == 0)
+ {
+ 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;
- }
+ var unit = input.Subsegment(startIndex, unitLength);
+ var current = startIndex + unitLength;
+ var separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);
- // After the range is read we expect the length separator '/'
- if ((current == input.Length) || (input[current] != '/'))
- {
- return 0;
- }
+ if (separatorLength == 0)
+ {
+ return 0;
+ }
- current++; // Skip '/' separator
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ current = current + separatorLength;
- if (current == input.Length)
- {
- return 0;
- }
+ 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;
- }
+ // 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;
+ }
- if (!TryCreateContentRange(input, unit, fromStartIndex, fromLength, toStartIndex, toLength,
- lengthStartIndex, lengthLength, out parsedValue))
- {
- 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);
- return current - startIndex;
+ if (current == input.Length)
+ {
+ return 0;
}
- private static bool TryGetLengthLength(StringSegment input, ref int current, out int lengthLength)
+ // 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))
{
- lengthLength = 0;
+ return 0;
+ }
- if (input[current] == '*')
- {
- current++;
- }
- else
- {
- // Parse length value: <length> in '<unit> <from>-<to>/<length>'
- lengthLength = HttpRuleParser.GetNumberLength(input, current, false);
+ if (!TryCreateContentRange(input, unit, fromStartIndex, fromLength, toStartIndex, toLength,
+ lengthStartIndex, lengthLength, out parsedValue))
+ {
+ return 0;
+ }
- if ((lengthLength == 0) || (lengthLength > HttpRuleParser.MaxInt64Digits))
- {
- return false;
- }
+ return current - startIndex;
+ }
- current = current + lengthLength;
- }
+ private static bool TryGetLengthLength(StringSegment input, ref int current, out int lengthLength)
+ {
+ lengthLength = 0;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- return true;
+ if (input[current] == '*')
+ {
+ current++;
}
-
- private static bool TryGetRangeLength(StringSegment input, ref int current, out int fromLength, out int toStartIndex, out int toLength)
+ else
{
- fromLength = 0;
- toStartIndex = 0;
- toLength = 0;
+ // Parse length value: <length> in '<unit> <from>-<to>/<length>'
+ lengthLength = HttpRuleParser.GetNumberLength(input, current, false);
- // Check if we have a value like 'bytes */133'. If yes, skip the range part and continue parsing the
- // length separator '/'.
- if (input[current] == '*')
+ if ((lengthLength == 0) || (lengthLength > HttpRuleParser.MaxInt64Digits))
{
- 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;
+ return false;
}
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- return true;
+ current = current + lengthLength;
}
- private static bool TryCreateContentRange(
- StringSegment input,
- StringSegment unit,
- int fromStartIndex,
- int fromLength,
- int toStartIndex,
- int toLength,
- int lengthStartIndex,
- int lengthLength,
- [NotNullWhen(true)]out ContentRangeHeaderValue? parsedValue)
+ 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
{
- parsedValue = null;
+ // Parse first range value: <from> in '<unit> <from>-<to>/<length>'
+ fromLength = HttpRuleParser.GetNumberLength(input, current, false);
- long from = 0;
- if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(fromStartIndex, fromLength), out from))
+ if ((fromLength == 0) || (fromLength > HttpRuleParser.MaxInt64Digits))
{
return false;
}
- long to = 0;
- if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
- {
- return false;
- }
+ current = current + fromLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- // 'from' must not be greater than 'to'
- if ((fromLength > 0) && (toLength > 0) && (from > to))
+ // 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;
}
- long length = 0;
- if ((lengthLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(lengthStartIndex, lengthLength),
- out length))
+ current++; // skip the '-' character
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+ if (current == input.Length)
{
return false;
}
- // 'from' and 'to' must be less than 'length'
- if ((toLength > 0) && (lengthLength > 0) && (to >= length))
+ // 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;
}
- var result = new ContentRangeHeaderValue();
- result._unit = unit;
+ current = current + toLength;
+ }
- if (fromLength > 0)
- {
- result.From = from;
- result.To = to;
- }
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ return true;
+ }
- if (lengthLength > 0)
- {
- result.Length = length;
- }
+ private static bool TryCreateContentRange(
+ StringSegment input,
+ StringSegment unit,
+ int fromStartIndex,
+ int fromLength,
+ int toStartIndex,
+ int toLength,
+ int lengthStartIndex,
+ int lengthLength,
+ [NotNullWhen(true)] 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;
+ }
- parsedValue = result;
- return true;
+ 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
index 00f473b31e..ca65e83d58 100644
--- a/src/Http/Headers/src/CookieHeaderParser.cs
+++ b/src/Http/Headers/src/CookieHeaderParser.cs
@@ -3,33 +3,32 @@
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal sealed class CookieHeaderParser : HttpHeaderParser<CookieHeaderValue>
{
- internal sealed class CookieHeaderParser : HttpHeaderParser<CookieHeaderValue>
+ internal CookieHeaderParser(bool supportsMultipleValues)
+ : base(supportsMultipleValues)
+ {
+ }
+
+ public override bool TryParseValue(StringSegment value, ref int index, out CookieHeaderValue? cookieValue)
{
- internal CookieHeaderParser(bool supportsMultipleValues)
- : base(supportsMultipleValues)
+ cookieValue = null;
+
+ if (!CookieHeaderParserShared.TryParseValue(value, ref index, SupportsMultipleValues, out var parsedName, out var parsedValue))
{
+ return false;
}
- public override bool TryParseValue(StringSegment value, ref int index, out CookieHeaderValue? cookieValue)
+ if (parsedName == null || parsedValue == null)
{
- cookieValue = null;
-
- if (!CookieHeaderParserShared.TryParseValue(value, ref index, SupportsMultipleValues, out var parsedName, out var parsedValue))
- {
- return false;
- }
-
- if (parsedName == null || parsedValue == null)
- {
- // Successfully parsed, but no values.
- return true;
- }
-
- cookieValue = new CookieHeaderValue(parsedName.Value, parsedValue.Value);
-
+ // Successfully parsed, but no values.
return true;
}
+
+ cookieValue = new CookieHeaderValue(parsedName.Value, parsedValue.Value);
+
+ return true;
}
}
diff --git a/src/Http/Headers/src/CookieHeaderValue.cs b/src/Http/Headers/src/CookieHeaderValue.cs
index d89321a0f8..0c207b8274 100644
--- a/src/Http/Headers/src/CookieHeaderValue.cs
+++ b/src/Http/Headers/src/CookieHeaderValue.cs
@@ -8,209 +8,208 @@ using System.Diagnostics.Contracts;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+// http://tools.ietf.org/html/rfc6265
+/// <summary>
+/// Represents the HTTP request <c>Cookie</c> header.
+/// </summary>
+public class CookieHeaderValue
{
- // http://tools.ietf.org/html/rfc6265
+ 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.
+ }
+
/// <summary>
- /// Represents the HTTP request <c>Cookie</c> header.
+ /// Initializes a new instance of <see cref="CookieHeaderValue"/>.
/// </summary>
- public class CookieHeaderValue
+ /// <param name="name">The cookie name.</param>
+ public CookieHeaderValue(StringSegment name)
+ : this(name, StringSegment.Empty)
{
- 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()
+ if (name == null)
{
- // Used by the parser to create a new instance of this type.
+ throw new ArgumentNullException(nameof(name));
}
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="CookieHeaderValue"/>.
- /// </summary>
- /// <param name="name">The cookie name.</param>
- public CookieHeaderValue(StringSegment name)
- : this(name, StringSegment.Empty)
+ /// <summary>
+ /// Initializes a new instance of <see cref="CookieHeaderValue"/>.
+ /// </summary>
+ /// <param name="name">The cookie name.</param>
+ /// <param name="value">The cookie value.</param>
+ public CookieHeaderValue(StringSegment name, StringSegment value)
+ {
+ if (name == null)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Initializes a new instance of <see cref="CookieHeaderValue"/>.
- /// </summary>
- /// <param name="name">The cookie name.</param>
- /// <param name="value">The cookie value.</param>
- public CookieHeaderValue(StringSegment name, StringSegment value)
+ if (value == null)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- Name = name;
- Value = value;
+ throw new ArgumentNullException(nameof(value));
}
- /// <summary>
- /// Gets or sets the cookie name.
- /// </summary>
- public StringSegment Name
+ Name = name;
+ Value = value;
+ }
+
+ /// <summary>
+ /// Gets or sets the cookie name.
+ /// </summary>
+ public StringSegment Name
+ {
+ get { return _name; }
+ set
{
- get { return _name; }
- set
- {
- CheckNameFormat(value, nameof(value));
- _name = value;
- }
+ CheckNameFormat(value, nameof(value));
+ _name = value;
}
+ }
- /// <summary>
- /// Gets or sets the cookie value.
- /// </summary>
- public StringSegment Value
+ /// <summary>
+ /// Gets or sets the cookie value.
+ /// </summary>
+ public StringSegment Value
+ {
+ get { return _value; }
+ set
{
- get { return _value; }
- set
- {
- CheckValueFormat(value, nameof(value));
- _value = value;
- }
+ CheckValueFormat(value, nameof(value));
+ _value = value;
}
+ }
- /// <inheritdoc />
- // name="val ue";
- public override string ToString()
- {
- var header = new StringBuilder();
+ /// <inheritdoc />
+ // name="val ue";
+ public override string ToString()
+ {
+ var header = new StringBuilder();
- header.Append(_name.AsSpan());
- header.Append('=');
- header.Append(_value.AsSpan());
+ header.Append(_name.AsSpan());
+ header.Append('=');
+ header.Append(_value.AsSpan());
- return header.ToString();
- }
+ return header.ToString();
+ }
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="CookieHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static CookieHeaderValue Parse(StringSegment input)
- {
- var index = 0;
- return SingleValueParser.ParseValue(input, ref index)!;
- }
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="CookieHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static CookieHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return SingleValueParser.ParseValue(input, ref index)!;
+ }
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="CookieHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="CookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out CookieHeaderValue? parsedValue)
- {
- var index = 0;
- return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
- }
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="CookieHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="CookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out CookieHeaderValue? parsedValue)
+ {
+ var index = 0;
+ return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ }
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="CookieHeaderValue"/> values.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<CookieHeaderValue> ParseList(IList<string>? inputs)
- {
- return MultipleValueParser.ParseValues(inputs);
- }
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="CookieHeaderValue"/> values.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<CookieHeaderValue> ParseList(IList<string>? inputs)
+ {
+ return MultipleValueParser.ParseValues(inputs);
+ }
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="CookieHeaderValue"/> values using string parsing rules.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<CookieHeaderValue> ParseStrictList(IList<string>? inputs)
- {
- return MultipleValueParser.ParseStrictValues(inputs);
- }
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="CookieHeaderValue"/> values using string parsing rules.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<CookieHeaderValue> ParseStrictList(IList<string>? inputs)
+ {
+ return MultipleValueParser.ParseStrictValues(inputs);
+ }
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="CookieHeaderValue"/>.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="CookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseList(IList<string>? inputs, [NotNullWhen(true)] out IList<CookieHeaderValue>? parsedValues)
- {
- return MultipleValueParser.TryParseValues(inputs, out parsedValues);
- }
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="CookieHeaderValue"/>.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="CookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseList(IList<string>? inputs, [NotNullWhen(true)] out IList<CookieHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ }
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="CookieHeaderValue"/> using string parsing rules.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseStrictList(IList<string>? inputs, [NotNullWhen(true)] out IList<CookieHeaderValue>? parsedValues)
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="CookieHeaderValue"/> using string parsing rules.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseStrictList(IList<string>? inputs, [NotNullWhen(true)] out IList<CookieHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+ }
+
+ internal static void CheckNameFormat(StringSegment name, string parameterName)
+ {
+ if (name == null)
{
- return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+ throw new ArgumentNullException(nameof(name));
}
- internal static void CheckNameFormat(StringSegment name, string parameterName)
+ if (HttpRuleParser.GetTokenLength(name, 0) != name.Length)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (HttpRuleParser.GetTokenLength(name, 0) != name.Length)
- {
- throw new ArgumentException("Invalid cookie name: " + name, parameterName);
- }
+ throw new ArgumentException("Invalid cookie name: " + name, parameterName);
}
+ }
- internal static void CheckValueFormat(StringSegment value, string parameterName)
+ internal static void CheckValueFormat(StringSegment value, string parameterName)
+ {
+ if (value == null)
{
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- var offset = 0;
- var result = CookieHeaderParserShared.GetCookieValue(value, ref offset);
- if (result.Length != value.Length)
- {
- throw new ArgumentException("Invalid cookie value: " + value, parameterName);
- }
+ throw new ArgumentNullException(nameof(value));
}
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ var offset = 0;
+ var result = CookieHeaderParserShared.GetCookieValue(value, ref offset);
+ if (result.Length != value.Length)
{
- 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);
+ throw new ArgumentException("Invalid cookie value: " + value, parameterName);
}
+ }
+
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as CookieHeaderValue;
- /// <inheritdoc />
- public override int GetHashCode()
+ if (other == null)
{
- return _name.GetHashCode() ^ _value.GetHashCode();
+ return false;
}
+
+ return StringSegment.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
+ && StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ return _name.GetHashCode() ^ _value.GetHashCode();
}
}
diff --git a/src/Http/Headers/src/EntityTagHeaderValue.cs b/src/Http/Headers/src/EntityTagHeaderValue.cs
index 51555c6a40..2523dc756e 100644
--- a/src/Http/Headers/src/EntityTagHeaderValue.cs
+++ b/src/Http/Headers/src/EntityTagHeaderValue.cs
@@ -7,273 +7,272 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents an entity-tag (<c>etag</c>) header value.
+/// </summary>
+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 StringSegment _tag;
+ private bool _isWeak;
+
+ private EntityTagHeaderValue()
+ {
+ // Used by the parser to create a new instance of this type.
+ }
+
/// <summary>
- /// Represents an entity-tag (<c>etag</c>) header value.
+ /// Initializes a new instance of the <see cref="EntityTagHeaderValue"/>.
/// </summary>
- public class EntityTagHeaderValue
+ /// <param name="tag">A <see cref="StringSegment"/> that contains an <see cref="EntityTagHeaderValue"/>.</param>
+ public EntityTagHeaderValue(StringSegment tag)
+ : this(tag, isWeak: false)
{
- // 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 StringSegment _tag;
- private bool _isWeak;
-
- private EntityTagHeaderValue()
- {
- // Used by the parser to create a new instance of this type.
- }
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="EntityTagHeaderValue"/>.
- /// </summary>
- /// <param name="tag">A <see cref="StringSegment"/> that contains an <see cref="EntityTagHeaderValue"/>.</param>
- public EntityTagHeaderValue(StringSegment tag)
- : this(tag, isWeak: false)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EntityTagHeaderValue"/>.
+ /// </summary>
+ /// <param name="tag">A <see cref="StringSegment"/> that contains an <see cref="EntityTagHeaderValue"/>.</param>
+ /// <param name="isWeak">A value that indicates if this entity-tag header is a weak validator.</param>
+ public EntityTagHeaderValue(StringSegment tag, bool isWeak)
+ {
+ if (StringSegment.IsNullOrEmpty(tag))
{
+ throw new ArgumentException("An empty string is not allowed.", nameof(tag));
}
- /// <summary>
- /// Initializes a new instance of the <see cref="EntityTagHeaderValue"/>.
- /// </summary>
- /// <param name="tag">A <see cref="StringSegment"/> that contains an <see cref="EntityTagHeaderValue"/>.</param>
- /// <param name="isWeak">A value that indicates if this entity-tag header is a weak validator.</param>
- public EntityTagHeaderValue(StringSegment tag, bool isWeak)
+ if (!isWeak && StringSegment.Equals(tag, "*", StringComparison.Ordinal))
{
- if (StringSegment.IsNullOrEmpty(tag))
- {
- throw new ArgumentException("An empty string is not allowed.", nameof(tag));
- }
-
- if (!isWeak && StringSegment.Equals(tag, "*", StringComparison.Ordinal))
- {
- // * is valid, but W/* isn't.
- _tag = tag;
- }
- else if ((HttpRuleParser.GetQuotedStringLength(tag, 0, out var 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, they can set 'isWeak' to true.
- throw new FormatException("Invalid ETag name");
- }
-
+ // * is valid, but W/* isn't.
_tag = tag;
- _isWeak = isWeak;
+ }
+ else if ((HttpRuleParser.GetQuotedStringLength(tag, 0, out var 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, they can set 'isWeak' to true.
+ throw new FormatException("Invalid ETag name");
}
- /// <summary>
- /// Gets the "any" etag.
- /// </summary>
- public static EntityTagHeaderValue Any { get; } = new EntityTagHeaderValue("*", isWeak: false);
-
- /// <summary>
- /// Gets the quoted tag.
- /// </summary>
- public StringSegment Tag => _tag;
+ _tag = tag;
+ _isWeak = isWeak;
+ }
- /// <summary>
- /// Gets a value that determines if the entity-tag header is a weak validator.
- /// </summary>
- public bool IsWeak => _isWeak;
+ /// <summary>
+ /// Gets the "any" etag.
+ /// </summary>
+ public static EntityTagHeaderValue Any { get; } = new EntityTagHeaderValue("*", isWeak: false);
- /// <inheritdoc />
- public override string ToString()
- {
- if (_isWeak)
- {
- return "W/" + _tag.ToString();
- }
- return _tag.ToString();
- }
+ /// <summary>
+ /// Gets the quoted tag.
+ /// </summary>
+ public StringSegment Tag => _tag;
- /// <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)
- {
- // Since the tag is a quoted-string we treat it case-sensitive.
- return obj is EntityTagHeaderValue other && _isWeak == other._isWeak && StringSegment.Equals(_tag, other._tag, StringComparison.Ordinal);
- }
+ /// <summary>
+ /// Gets a value that determines if the entity-tag header is a weak validator.
+ /// </summary>
+ public bool IsWeak => _isWeak;
- /// <inheritdoc />
- public override int GetHashCode()
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ if (_isWeak)
{
- // Since the tag is a quoted-string we treat it case-sensitive.
- return _tag.GetHashCode() ^ _isWeak.GetHashCode();
+ return "W/" + _tag.ToString();
}
+ return _tag.ToString();
+ }
- /// <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;
- }
+ /// <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)
+ {
+ // Since the tag is a quoted-string we treat it case-sensitive.
+ return obj is EntityTagHeaderValue other && _isWeak == other._isWeak && StringSegment.Equals(_tag, other._tag, StringComparison.Ordinal);
+ }
- if (useStrongComparison)
- {
- return !IsWeak && !other.IsWeak && StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
- }
- else
- {
- return StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
- }
- }
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ // Since the tag is a quoted-string we treat it case-sensitive.
+ return _tag.GetHashCode() ^ _isWeak.GetHashCode();
+ }
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="EntityTagHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static EntityTagHeaderValue Parse(StringSegment input)
+ /// <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)
{
- var index = 0;
- return SingleValueParser.ParseValue(input, ref index)!;
+ return false;
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="EntityTagHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="EntityTagHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out EntityTagHeaderValue parsedValue)
+ if (useStrongComparison)
{
- var index = 0;
- return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ return !IsWeak && !other.IsWeak && StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
}
-
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="EntityTagHeaderValue"/> values.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<EntityTagHeaderValue> ParseList(IList<string>? inputs)
+ else
{
- return MultipleValueParser.ParseValues(inputs);
+ return StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
}
+ }
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="EntityTagHeaderValue"/> values using string parsing rules.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<EntityTagHeaderValue> ParseStrictList(IList<string>? inputs)
- {
- return MultipleValueParser.ParseStrictValues(inputs);
- }
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="EntityTagHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static EntityTagHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return SingleValueParser.ParseValue(input, ref index)!;
+ }
+
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="EntityTagHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="EntityTagHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out EntityTagHeaderValue parsedValue)
+ {
+ var index = 0;
+ return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ }
+
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="EntityTagHeaderValue"/> values.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<EntityTagHeaderValue> ParseList(IList<string>? inputs)
+ {
+ return MultipleValueParser.ParseValues(inputs);
+ }
+
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="EntityTagHeaderValue"/> values using string parsing rules.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<EntityTagHeaderValue> ParseStrictList(IList<string>? inputs)
+ {
+ return MultipleValueParser.ParseStrictValues(inputs);
+ }
+
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="EntityTagHeaderValue"/>.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="EntityTagHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseList(IList<string>? inputs, [NotNullWhen(true)] out IList<EntityTagHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ }
+
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="EntityTagHeaderValue"/> using string parsing rules.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="EntityTagHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseStrictList(IList<string>? inputs, [NotNullWhen(true)] 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);
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="EntityTagHeaderValue"/>.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="EntityTagHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseList(IList<string>? inputs, [NotNullWhen(true)] out IList<EntityTagHeaderValue>? parsedValues)
+ parsedValue = null;
+
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
- return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ return 0;
}
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="EntityTagHeaderValue"/> using string parsing rules.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="EntityTagHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseStrictList(IList<string>? inputs, [NotNullWhen(true)] out IList<EntityTagHeaderValue>? parsedValues)
+ // Caller must remove leading whitespaces. If not, we'll return 0.
+ var isWeak = false;
+ var current = startIndex;
+
+ var firstChar = input[startIndex];
+ if (firstChar == '*')
{
- return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+ // We have '*' value, indicating "any" ETag.
+ parsedValue = Any;
+ current++;
}
-
- internal static int GetEntityTagLength(StringSegment input, int startIndex, out EntityTagHeaderValue? parsedValue)
+ else
{
- Contract.Requires(startIndex >= 0);
-
- parsedValue = null;
+ // 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);
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ var tagStartIndex = current;
+ var tagLength = 0;
+ if (HttpRuleParser.GetQuotedStringLength(input, current, out tagLength) != HttpParseResult.Parsed)
{
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 == '*')
+ parsedValue = new EntityTagHeaderValue();
+ if (tagLength == input.Length)
{
- // We have '*' value, indicating "any" ETag.
- parsedValue = Any;
- current++;
+ // 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
{
- // 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;
+ parsedValue._tag = input.Subsegment(tagStartIndex, tagLength);
+ parsedValue._isWeak = isWeak;
}
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- return current - startIndex;
+ 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
index ac3cdd44c5..3eb3b9cb56 100644
--- a/src/Http/Headers/src/GenericHeaderParser.cs
+++ b/src/Http/Headers/src/GenericHeaderParser.cs
@@ -4,28 +4,27 @@
using System;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T>
{
- internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T>
- {
- internal delegate int GetParsedValueLengthDelegate(StringSegment value, int startIndex, out T? parsedValue);
+ internal delegate int GetParsedValueLengthDelegate(StringSegment value, int startIndex, out T? parsedValue);
- private readonly GetParsedValueLengthDelegate _getParsedValueLength;
+ private readonly GetParsedValueLengthDelegate _getParsedValueLength;
- internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength)
- : base(supportsMultipleValues)
+ internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength)
+ : base(supportsMultipleValues)
+ {
+ if (getParsedValueLength == null)
{
- if (getParsedValueLength == null)
- {
- throw new ArgumentNullException(nameof(getParsedValueLength));
- }
-
- _getParsedValueLength = getParsedValueLength;
+ throw new ArgumentNullException(nameof(getParsedValueLength));
}
- protected override int GetParsedValueLength(StringSegment value, int startIndex, out T? parsedValue)
- {
- return _getParsedValueLength(value, startIndex, out parsedValue);
- }
+ _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
index 7d92ae0004..1d7611d519 100644
--- a/src/Http/Headers/src/HeaderNames.cs
+++ b/src/Http/Headers/src/HeaderNames.cs
@@ -1,303 +1,302 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Defines constants for well-known HTTP headers.
+/// </summary>
+// MODIFICATION POLICY: This list is not intended to be exhaustive, it primarily contains values used by the framework itself.
+// Please do not open PRs without first opening an issue to discuss a specific item.
+public static class HeaderNames
{
- /// <summary>
- /// Defines constants for well-known HTTP headers.
- /// </summary>
- // MODIFICATION POLICY: This list is not intended to be exhaustive, it primarily contains values used by the framework itself.
- // Please do not open PRs without first opening an issue to discuss a specific item.
- public static class HeaderNames
- {
- // Use readonly statics rather than constants so ReferenceEquals works
+ // Use readonly statics rather than constants so ReferenceEquals works
- /// <summary>Gets the <c>Accept</c> HTTP header name.</summary>
- public static readonly string Accept = "Accept";
+ /// <summary>Gets the <c>Accept</c> HTTP header name.</summary>
+ public static readonly string Accept = "Accept";
- /// <summary>Gets the <c>Accept-Charset</c> HTTP header name.</summary>
- public static readonly string AcceptCharset = "Accept-Charset";
+ /// <summary>Gets the <c>Accept-Charset</c> HTTP header name.</summary>
+ public static readonly string AcceptCharset = "Accept-Charset";
- /// <summary>Gets the <c>Accept-Encoding</c> HTTP header name.</summary>
- public static readonly string AcceptEncoding = "Accept-Encoding";
+ /// <summary>Gets the <c>Accept-Encoding</c> HTTP header name.</summary>
+ public static readonly string AcceptEncoding = "Accept-Encoding";
- /// <summary>Gets the <c>Accept-Language</c> HTTP header name.</summary>
- public static readonly string AcceptLanguage = "Accept-Language";
+ /// <summary>Gets the <c>Accept-Language</c> HTTP header name.</summary>
+ public static readonly string AcceptLanguage = "Accept-Language";
- /// <summary>Gets the <c>Accept-Ranges</c> HTTP header name.</summary>
- public static readonly string AcceptRanges = "Accept-Ranges";
+ /// <summary>Gets the <c>Accept-Ranges</c> HTTP header name.</summary>
+ public static readonly string AcceptRanges = "Accept-Ranges";
- /// <summary>Gets the <c>Access-Control-Allow-Credentials</c> HTTP header name.</summary>
- public static readonly string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
+ /// <summary>Gets the <c>Access-Control-Allow-Credentials</c> HTTP header name.</summary>
+ public static readonly string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
- /// <summary>Gets the <c>Access-Control-Allow-Headers</c> HTTP header name.</summary>
- public static readonly string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
+ /// <summary>Gets the <c>Access-Control-Allow-Headers</c> HTTP header name.</summary>
+ public static readonly string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
- /// <summary>Gets the <c>Access-Control-Allow-Methods</c> HTTP header name.</summary>
- public static readonly string AccessControlAllowMethods = "Access-Control-Allow-Methods";
+ /// <summary>Gets the <c>Access-Control-Allow-Methods</c> HTTP header name.</summary>
+ public static readonly string AccessControlAllowMethods = "Access-Control-Allow-Methods";
- /// <summary>Gets the <c>Access-Control-Allow-Origin</c> HTTP header name.</summary>
- public static readonly string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
+ /// <summary>Gets the <c>Access-Control-Allow-Origin</c> HTTP header name.</summary>
+ public static readonly string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
- /// <summary>Gets the <c>Access-Control-Expose-Headers</c> HTTP header name.</summary>
- public static readonly string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
+ /// <summary>Gets the <c>Access-Control-Expose-Headers</c> HTTP header name.</summary>
+ public static readonly string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
- /// <summary>Gets the <c>Access-Control-Max-Age</c> HTTP header name.</summary>
- public static readonly string AccessControlMaxAge = "Access-Control-Max-Age";
+ /// <summary>Gets the <c>Access-Control-Max-Age</c> HTTP header name.</summary>
+ public static readonly string AccessControlMaxAge = "Access-Control-Max-Age";
- /// <summary>Gets the <c>Access-Control-Request-Headers</c> HTTP header name.</summary>
- public static readonly string AccessControlRequestHeaders = "Access-Control-Request-Headers";
+ /// <summary>Gets the <c>Access-Control-Request-Headers</c> HTTP header name.</summary>
+ public static readonly string AccessControlRequestHeaders = "Access-Control-Request-Headers";
- /// <summary>Gets the <c>Access-Control-Request-Method</c> HTTP header name.</summary>
- public static readonly string AccessControlRequestMethod = "Access-Control-Request-Method";
+ /// <summary>Gets the <c>Access-Control-Request-Method</c> HTTP header name.</summary>
+ public static readonly string AccessControlRequestMethod = "Access-Control-Request-Method";
- /// <summary>Gets the <c>Age</c> HTTP header name.</summary>
- public static readonly string Age = "Age";
+ /// <summary>Gets the <c>Age</c> HTTP header name.</summary>
+ public static readonly string Age = "Age";
- /// <summary>Gets the <c>Allow</c> HTTP header name.</summary>
- public static readonly string Allow = "Allow";
+ /// <summary>Gets the <c>Allow</c> HTTP header name.</summary>
+ public static readonly string Allow = "Allow";
- /// <summary>Gets the <c>Alt-Svc</c> HTTP header name.</summary>
- public static readonly string AltSvc = "Alt-Svc";
+ /// <summary>Gets the <c>Alt-Svc</c> HTTP header name.</summary>
+ public static readonly string AltSvc = "Alt-Svc";
- /// <summary>Gets the <c>:authority</c> HTTP header name.</summary>
- public static readonly string Authority = ":authority";
+ /// <summary>Gets the <c>:authority</c> HTTP header name.</summary>
+ public static readonly string Authority = ":authority";
- /// <summary>Gets the <c>Authorization</c> HTTP header name.</summary>
- public static readonly string Authorization = "Authorization";
+ /// <summary>Gets the <c>Authorization</c> HTTP header name.</summary>
+ public static readonly string Authorization = "Authorization";
- /// <summary>Gets the <c>baggage</c> HTTP header name.</summary>
- public static readonly string Baggage = "baggage";
+ /// <summary>Gets the <c>baggage</c> HTTP header name.</summary>
+ public static readonly string Baggage = "baggage";
- /// <summary>Gets the <c>Cache-Control</c> HTTP header name.</summary>
- public static readonly string CacheControl = "Cache-Control";
+ /// <summary>Gets the <c>Cache-Control</c> HTTP header name.</summary>
+ public static readonly string CacheControl = "Cache-Control";
- /// <summary>Gets the <c>Connection</c> HTTP header name.</summary>
- public static readonly string Connection = "Connection";
+ /// <summary>Gets the <c>Connection</c> HTTP header name.</summary>
+ public static readonly string Connection = "Connection";
- /// <summary>Gets the <c>Content-Disposition</c> HTTP header name.</summary>
- public static readonly string ContentDisposition = "Content-Disposition";
+ /// <summary>Gets the <c>Content-Disposition</c> HTTP header name.</summary>
+ public static readonly string ContentDisposition = "Content-Disposition";
- /// <summary>Gets the <c>Content-Encoding</c> HTTP header name.</summary>
- public static readonly string ContentEncoding = "Content-Encoding";
+ /// <summary>Gets the <c>Content-Encoding</c> HTTP header name.</summary>
+ public static readonly string ContentEncoding = "Content-Encoding";
- /// <summary>Gets the <c>Content-Language</c> HTTP header name.</summary>
- public static readonly string ContentLanguage = "Content-Language";
+ /// <summary>Gets the <c>Content-Language</c> HTTP header name.</summary>
+ public static readonly string ContentLanguage = "Content-Language";
- /// <summary>Gets the <c>Content-Length</c> HTTP header name.</summary>
- public static readonly string ContentLength = "Content-Length";
+ /// <summary>Gets the <c>Content-Length</c> HTTP header name.</summary>
+ public static readonly string ContentLength = "Content-Length";
- /// <summary>Gets the <c>Content-Location</c> HTTP header name.</summary>
- public static readonly string ContentLocation = "Content-Location";
+ /// <summary>Gets the <c>Content-Location</c> HTTP header name.</summary>
+ public static readonly string ContentLocation = "Content-Location";
- /// <summary>Gets the <c>Content-MD5</c> HTTP header name.</summary>
- public static readonly string ContentMD5 = "Content-MD5";
+ /// <summary>Gets the <c>Content-MD5</c> HTTP header name.</summary>
+ public static readonly string ContentMD5 = "Content-MD5";
- /// <summary>Gets the <c>Content-Range</c> HTTP header name.</summary>
- public static readonly string ContentRange = "Content-Range";
+ /// <summary>Gets the <c>Content-Range</c> HTTP header name.</summary>
+ public static readonly string ContentRange = "Content-Range";
- /// <summary>Gets the <c>Content-Security-Policy</c> HTTP header name.</summary>
- public static readonly string ContentSecurityPolicy = "Content-Security-Policy";
+ /// <summary>Gets the <c>Content-Security-Policy</c> HTTP header name.</summary>
+ public static readonly string ContentSecurityPolicy = "Content-Security-Policy";
- /// <summary>Gets the <c>Content-Security-Policy-Report-Only</c> HTTP header name.</summary>
- public static readonly string ContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only";
+ /// <summary>Gets the <c>Content-Security-Policy-Report-Only</c> HTTP header name.</summary>
+ public static readonly string ContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only";
- /// <summary>Gets the <c>Content-Type</c> HTTP header name.</summary>
- public static readonly string ContentType = "Content-Type";
+ /// <summary>Gets the <c>Content-Type</c> HTTP header name.</summary>
+ public static readonly string ContentType = "Content-Type";
- /// <summary>Gets the <c>Correlation-Context</c> HTTP header name.</summary>
- public static readonly string CorrelationContext = "Correlation-Context";
+ /// <summary>Gets the <c>Correlation-Context</c> HTTP header name.</summary>
+ public static readonly string CorrelationContext = "Correlation-Context";
- /// <summary>Gets the <c>Cookie</c> HTTP header name.</summary>
- public static readonly string Cookie = "Cookie";
+ /// <summary>Gets the <c>Cookie</c> HTTP header name.</summary>
+ public static readonly string Cookie = "Cookie";
- /// <summary>Gets the <c>Date</c> HTTP header name.</summary>
- public static readonly string Date = "Date";
+ /// <summary>Gets the <c>Date</c> HTTP header name.</summary>
+ public static readonly string Date = "Date";
- /// <summary>Gets the <c>DNT</c> HTTP header name.</summary>
- public static readonly string DNT = "DNT";
+ /// <summary>Gets the <c>DNT</c> HTTP header name.</summary>
+ public static readonly string DNT = "DNT";
- /// <summary>Gets the <c>ETag</c> HTTP header name.</summary>
- public static readonly string ETag = "ETag";
+ /// <summary>Gets the <c>ETag</c> HTTP header name.</summary>
+ public static readonly string ETag = "ETag";
- /// <summary>Gets the <c>Expires</c> HTTP header name.</summary>
- public static readonly string Expires = "Expires";
+ /// <summary>Gets the <c>Expires</c> HTTP header name.</summary>
+ public static readonly string Expires = "Expires";
- /// <summary>Gets the <c>Expect</c> HTTP header name.</summary>
- public static readonly string Expect = "Expect";
+ /// <summary>Gets the <c>Expect</c> HTTP header name.</summary>
+ public static readonly string Expect = "Expect";
- /// <summary>Gets the <c>From</c> HTTP header name.</summary>
- public static readonly string From = "From";
+ /// <summary>Gets the <c>From</c> HTTP header name.</summary>
+ public static readonly string From = "From";
- /// <summary>Gets the <c>Grpc-Accept-Encoding</c> HTTP header name.</summary>
- public static readonly string GrpcAcceptEncoding = "Grpc-Accept-Encoding";
+ /// <summary>Gets the <c>Grpc-Accept-Encoding</c> HTTP header name.</summary>
+ public static readonly string GrpcAcceptEncoding = "Grpc-Accept-Encoding";
- /// <summary>Gets the <c>Grpc-Encoding</c> HTTP header name.</summary>
- public static readonly string GrpcEncoding = "Grpc-Encoding";
+ /// <summary>Gets the <c>Grpc-Encoding</c> HTTP header name.</summary>
+ public static readonly string GrpcEncoding = "Grpc-Encoding";
- /// <summary>Gets the <c>Grpc-Message</c> HTTP header name.</summary>
- public static readonly string GrpcMessage = "Grpc-Message";
+ /// <summary>Gets the <c>Grpc-Message</c> HTTP header name.</summary>
+ public static readonly string GrpcMessage = "Grpc-Message";
- /// <summary>Gets the <c>Grpc-Status</c> HTTP header name.</summary>
- public static readonly string GrpcStatus = "Grpc-Status";
+ /// <summary>Gets the <c>Grpc-Status</c> HTTP header name.</summary>
+ public static readonly string GrpcStatus = "Grpc-Status";
- /// <summary>Gets the <c>Grpc-Timeout</c> HTTP header name.</summary>
- public static readonly string GrpcTimeout = "Grpc-Timeout";
+ /// <summary>Gets the <c>Grpc-Timeout</c> HTTP header name.</summary>
+ public static readonly string GrpcTimeout = "Grpc-Timeout";
- /// <summary>Gets the <c>Host</c> HTTP header name.</summary>
- public static readonly string Host = "Host";
+ /// <summary>Gets the <c>Host</c> HTTP header name.</summary>
+ public static readonly string Host = "Host";
- /// <summary>Gets the <c>Keep-Alive</c> HTTP header name.</summary>
- public static readonly string KeepAlive = "Keep-Alive";
+ /// <summary>Gets the <c>Keep-Alive</c> HTTP header name.</summary>
+ public static readonly string KeepAlive = "Keep-Alive";
- /// <summary>Gets the <c>If-Match</c> HTTP header name.</summary>
- public static readonly string IfMatch = "If-Match";
+ /// <summary>Gets the <c>If-Match</c> HTTP header name.</summary>
+ public static readonly string IfMatch = "If-Match";
- /// <summary>Gets the <c>If-Modified-Since</c> HTTP header name.</summary>
- public static readonly string IfModifiedSince = "If-Modified-Since";
+ /// <summary>Gets the <c>If-Modified-Since</c> HTTP header name.</summary>
+ public static readonly string IfModifiedSince = "If-Modified-Since";
- /// <summary>Gets the <c>If-None-Match</c> HTTP header name.</summary>
- public static readonly string IfNoneMatch = "If-None-Match";
+ /// <summary>Gets the <c>If-None-Match</c> HTTP header name.</summary>
+ public static readonly string IfNoneMatch = "If-None-Match";
- /// <summary>Gets the <c>If-Range</c> HTTP header name.</summary>
- public static readonly string IfRange = "If-Range";
+ /// <summary>Gets the <c>If-Range</c> HTTP header name.</summary>
+ public static readonly string IfRange = "If-Range";
- /// <summary>Gets the <c>If-Unmodified-Since</c> HTTP header name.</summary>
- public static readonly string IfUnmodifiedSince = "If-Unmodified-Since";
+ /// <summary>Gets the <c>If-Unmodified-Since</c> HTTP header name.</summary>
+ public static readonly string IfUnmodifiedSince = "If-Unmodified-Since";
- /// <summary>Gets the <c>Last-Modified</c> HTTP header name.</summary>
- public static readonly string LastModified = "Last-Modified";
+ /// <summary>Gets the <c>Last-Modified</c> HTTP header name.</summary>
+ public static readonly string LastModified = "Last-Modified";
- /// <summary>Gets the <c>Link</c> HTTP header name.</summary>
- public static readonly string Link = "Link";
+ /// <summary>Gets the <c>Link</c> HTTP header name.</summary>
+ public static readonly string Link = "Link";
- /// <summary>Gets the <c>Location</c> HTTP header name.</summary>
- public static readonly string Location = "Location";
+ /// <summary>Gets the <c>Location</c> HTTP header name.</summary>
+ public static readonly string Location = "Location";
- /// <summary>Gets the <c>Max-Forwards</c> HTTP header name.</summary>
- public static readonly string MaxForwards = "Max-Forwards";
+ /// <summary>Gets the <c>Max-Forwards</c> HTTP header name.</summary>
+ public static readonly string MaxForwards = "Max-Forwards";
- /// <summary>Gets the <c>:method</c> HTTP header name.</summary>
- public static readonly string Method = ":method";
+ /// <summary>Gets the <c>:method</c> HTTP header name.</summary>
+ public static readonly string Method = ":method";
- /// <summary>Gets the <c>Origin</c> HTTP header name.</summary>
- public static readonly string Origin = "Origin";
+ /// <summary>Gets the <c>Origin</c> HTTP header name.</summary>
+ public static readonly string Origin = "Origin";
- /// <summary>Gets the <c>:path</c> HTTP header name.</summary>
- public static readonly string Path = ":path";
+ /// <summary>Gets the <c>:path</c> HTTP header name.</summary>
+ public static readonly string Path = ":path";
- /// <summary>Gets the <c>Pragma</c> HTTP header name.</summary>
- public static readonly string Pragma = "Pragma";
+ /// <summary>Gets the <c>Pragma</c> HTTP header name.</summary>
+ public static readonly string Pragma = "Pragma";
- /// <summary>Gets the <c>Proxy-Authenticate</c> HTTP header name.</summary>
- public static readonly string ProxyAuthenticate = "Proxy-Authenticate";
+ /// <summary>Gets the <c>Proxy-Authenticate</c> HTTP header name.</summary>
+ public static readonly string ProxyAuthenticate = "Proxy-Authenticate";
- /// <summary>Gets the <c>Proxy-Authorization</c> HTTP header name.</summary>
- public static readonly string ProxyAuthorization = "Proxy-Authorization";
+ /// <summary>Gets the <c>Proxy-Authorization</c> HTTP header name.</summary>
+ public static readonly string ProxyAuthorization = "Proxy-Authorization";
- /// <summary>Gets the <c>Proxy-Connection</c> HTTP header name.</summary>
- public static readonly string ProxyConnection = "Proxy-Connection";
+ /// <summary>Gets the <c>Proxy-Connection</c> HTTP header name.</summary>
+ public static readonly string ProxyConnection = "Proxy-Connection";
- /// <summary>Gets the <c>Range</c> HTTP header name.</summary>
- public static readonly string Range = "Range";
+ /// <summary>Gets the <c>Range</c> HTTP header name.</summary>
+ public static readonly string Range = "Range";
- /// <summary>Gets the <c>Referer</c> HTTP header name.</summary>
- public static readonly string Referer = "Referer";
+ /// <summary>Gets the <c>Referer</c> HTTP header name.</summary>
+ public static readonly string Referer = "Referer";
- /// <summary>Gets the <c>Retry-After</c> HTTP header name.</summary>
- public static readonly string RetryAfter = "Retry-After";
+ /// <summary>Gets the <c>Retry-After</c> HTTP header name.</summary>
+ public static readonly string RetryAfter = "Retry-After";
- /// <summary>Gets the <c>Request-Id</c> HTTP header name.</summary>
- public static readonly string RequestId = "Request-Id";
+ /// <summary>Gets the <c>Request-Id</c> HTTP header name.</summary>
+ public static readonly string RequestId = "Request-Id";
- /// <summary>Gets the <c>:scheme</c> HTTP header name.</summary>
- public static readonly string Scheme = ":scheme";
+ /// <summary>Gets the <c>:scheme</c> HTTP header name.</summary>
+ public static readonly string Scheme = ":scheme";
- /// <summary>Gets the <c>Sec-WebSocket-Accept</c> HTTP header name.</summary>
- public static readonly string SecWebSocketAccept = "Sec-WebSocket-Accept";
+ /// <summary>Gets the <c>Sec-WebSocket-Accept</c> HTTP header name.</summary>
+ public static readonly string SecWebSocketAccept = "Sec-WebSocket-Accept";
- /// <summary>Gets the <c>Sec-WebSocket-Key</c> HTTP header name.</summary>
- public static readonly string SecWebSocketKey = "Sec-WebSocket-Key";
+ /// <summary>Gets the <c>Sec-WebSocket-Key</c> HTTP header name.</summary>
+ public static readonly string SecWebSocketKey = "Sec-WebSocket-Key";
- /// <summary>Gets the <c>Sec-WebSocket-Protocol</c> HTTP header name.</summary>
- public static readonly string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
+ /// <summary>Gets the <c>Sec-WebSocket-Protocol</c> HTTP header name.</summary>
+ public static readonly string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
- /// <summary>Gets the <c>Sec-WebSocket-Version</c> HTTP header name.</summary>
- public static readonly string SecWebSocketVersion = "Sec-WebSocket-Version";
+ /// <summary>Gets the <c>Sec-WebSocket-Version</c> HTTP header name.</summary>
+ public static readonly string SecWebSocketVersion = "Sec-WebSocket-Version";
- /// <summary>Gets the <c>Sec-WebSocket-Extensions</c> HTTP header name.</summary>
- public static readonly string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
+ /// <summary>Gets the <c>Sec-WebSocket-Extensions</c> HTTP header name.</summary>
+ public static readonly string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
- /// <summary>Gets the <c>Server</c> HTTP header name.</summary>
- public static readonly string Server = "Server";
+ /// <summary>Gets the <c>Server</c> HTTP header name.</summary>
+ public static readonly string Server = "Server";
- /// <summary>Gets the <c>Set-Cookie</c> HTTP header name.</summary>
- public static readonly string SetCookie = "Set-Cookie";
+ /// <summary>Gets the <c>Set-Cookie</c> HTTP header name.</summary>
+ public static readonly string SetCookie = "Set-Cookie";
- /// <summary>Gets the <c>:status</c> HTTP header name.</summary>
- public static readonly string Status = ":status";
+ /// <summary>Gets the <c>:status</c> HTTP header name.</summary>
+ public static readonly string Status = ":status";
- /// <summary>Gets the <c>Strict-Transport-Security</c> HTTP header name.</summary>
- public static readonly string StrictTransportSecurity = "Strict-Transport-Security";
+ /// <summary>Gets the <c>Strict-Transport-Security</c> HTTP header name.</summary>
+ public static readonly string StrictTransportSecurity = "Strict-Transport-Security";
- /// <summary>Gets the <c>TE</c> HTTP header name.</summary>
- public static readonly string TE = "TE";
+ /// <summary>Gets the <c>TE</c> HTTP header name.</summary>
+ public static readonly string TE = "TE";
- /// <summary>Gets the <c>Trailer</c> HTTP header name.</summary>
- public static readonly string Trailer = "Trailer";
+ /// <summary>Gets the <c>Trailer</c> HTTP header name.</summary>
+ public static readonly string Trailer = "Trailer";
- /// <summary>Gets the <c>Transfer-Encoding</c> HTTP header name.</summary>
- public static readonly string TransferEncoding = "Transfer-Encoding";
+ /// <summary>Gets the <c>Transfer-Encoding</c> HTTP header name.</summary>
+ public static readonly string TransferEncoding = "Transfer-Encoding";
- /// <summary>Gets the <c>Translate</c> HTTP header name.</summary>
- public static readonly string Translate = "Translate";
+ /// <summary>Gets the <c>Translate</c> HTTP header name.</summary>
+ public static readonly string Translate = "Translate";
- /// <summary>Gets the <c>traceparent</c> HTTP header name.</summary>
- public static readonly string TraceParent = "traceparent";
+ /// <summary>Gets the <c>traceparent</c> HTTP header name.</summary>
+ public static readonly string TraceParent = "traceparent";
- /// <summary>Gets the <c>tracestate</c> HTTP header name.</summary>
- public static readonly string TraceState = "tracestate";
+ /// <summary>Gets the <c>tracestate</c> HTTP header name.</summary>
+ public static readonly string TraceState = "tracestate";
- /// <summary>Gets the <c>Upgrade</c> HTTP header name.</summary>
- public static readonly string Upgrade = "Upgrade";
+ /// <summary>Gets the <c>Upgrade</c> HTTP header name.</summary>
+ public static readonly string Upgrade = "Upgrade";
- /// <summary>Gets the <c>Upgrade-Insecure-Requests</c> HTTP header name.</summary>
- public static readonly string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
+ /// <summary>Gets the <c>Upgrade-Insecure-Requests</c> HTTP header name.</summary>
+ public static readonly string UpgradeInsecureRequests = "Upgrade-Insecure-Requests";
- /// <summary>Gets the <c>User-Agent</c> HTTP header name.</summary>
- public static readonly string UserAgent = "User-Agent";
+ /// <summary>Gets the <c>User-Agent</c> HTTP header name.</summary>
+ public static readonly string UserAgent = "User-Agent";
- /// <summary>Gets the <c>Vary</c> HTTP header name.</summary>
- public static readonly string Vary = "Vary";
+ /// <summary>Gets the <c>Vary</c> HTTP header name.</summary>
+ public static readonly string Vary = "Vary";
- /// <summary>Gets the <c>Via</c> HTTP header name.</summary>
- public static readonly string Via = "Via";
+ /// <summary>Gets the <c>Via</c> HTTP header name.</summary>
+ public static readonly string Via = "Via";
- /// <summary>Gets the <c>Warning</c> HTTP header name.</summary>
- public static readonly string Warning = "Warning";
+ /// <summary>Gets the <c>Warning</c> HTTP header name.</summary>
+ public static readonly string Warning = "Warning";
- /// <summary>Gets the <c>Sec-WebSocket-Protocol</c> HTTP header name.</summary>
- public static readonly string WebSocketSubProtocols = "Sec-WebSocket-Protocol";
+ /// <summary>Gets the <c>Sec-WebSocket-Protocol</c> HTTP header name.</summary>
+ public static readonly string WebSocketSubProtocols = "Sec-WebSocket-Protocol";
- /// <summary>Gets the <c>WWW-Authenticate</c> HTTP header name.</summary>
- public static readonly string WWWAuthenticate = "WWW-Authenticate";
+ /// <summary>Gets the <c>WWW-Authenticate</c> HTTP header name.</summary>
+ public static readonly string WWWAuthenticate = "WWW-Authenticate";
- /// <summary>Gets the <c>X-Content-Type-Options</c> HTTP header name.</summary>
- public static readonly string XContentTypeOptions = "X-Content-Type-Options";
+ /// <summary>Gets the <c>X-Content-Type-Options</c> HTTP header name.</summary>
+ public static readonly string XContentTypeOptions = "X-Content-Type-Options";
- /// <summary>Gets the <c>X-Frame-Options</c> HTTP header name.</summary>
- public static readonly string XFrameOptions = "X-Frame-Options";
+ /// <summary>Gets the <c>X-Frame-Options</c> HTTP header name.</summary>
+ public static readonly string XFrameOptions = "X-Frame-Options";
- /// <summary>Gets the <c>X-Powered-By</c> HTTP header name.</summary>
- public static readonly string XPoweredBy = "X-Powered-By";
+ /// <summary>Gets the <c>X-Powered-By</c> HTTP header name.</summary>
+ public static readonly string XPoweredBy = "X-Powered-By";
- /// <summary>Gets the <c>X-Requested-With</c> HTTP header name.</summary>
- public static readonly string XRequestedWith = "X-Requested-With";
+ /// <summary>Gets the <c>X-Requested-With</c> HTTP header name.</summary>
+ public static readonly string XRequestedWith = "X-Requested-With";
- /// <summary>Gets the <c>X-UA-Compatible</c> HTTP header name.</summary>
- public static readonly string XUACompatible = "X-UA-Compatible";
+ /// <summary>Gets the <c>X-UA-Compatible</c> HTTP header name.</summary>
+ public static readonly string XUACompatible = "X-UA-Compatible";
- /// <summary>Gets the <c>X-XSS-Protection</c> HTTP header name.</summary>
- public static readonly string XXSSProtection = "X-XSS-Protection";
- }
+ /// <summary>Gets the <c>X-XSS-Protection</c> HTTP header name.</summary>
+ public static readonly string XXSSProtection = "X-XSS-Protection";
}
diff --git a/src/Http/Headers/src/HeaderQuality.cs b/src/Http/Headers/src/HeaderQuality.cs
index 497733e9ae..60e55193ba 100644
--- a/src/Http/Headers/src/HeaderQuality.cs
+++ b/src/Http/Headers/src/HeaderQuality.cs
@@ -1,21 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Provides HTTP header quality factors.
+/// </summary>
+public static class HeaderQuality
{
/// <summary>
- /// Provides HTTP header quality factors.
+ /// Quality factor to indicate a perfect match.
/// </summary>
- public static class HeaderQuality
- {
- /// <summary>
- /// Quality factor to indicate a perfect match.
- /// </summary>
- public const double Match = 1.0;
+ public const double Match = 1.0;
- /// <summary>
- /// Quality factor to indicate no match.
- /// </summary>
- public const double NoMatch = 0.0;
- }
+ /// <summary>
+ /// Quality factor to indicate no match.
+ /// </summary>
+ public const double NoMatch = 0.0;
}
diff --git a/src/Http/Headers/src/HeaderUtilities.cs b/src/Http/Headers/src/HeaderUtilities.cs
index bb4d101b12..b8a8c83ff3 100644
--- a/src/Http/Headers/src/HeaderUtilities.cs
+++ b/src/Http/Headers/src/HeaderUtilities.cs
@@ -9,611 +9,611 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Provides utilities to parse and modify HTTP header values.
+/// </summary>
+public static class HeaderUtilities
{
- /// <summary>
- /// Provides utilities to parse and modify HTTP header values.
- /// </summary>
- public static class HeaderUtilities
- {
- private const int _qualityValueMaxCharCount = 10; // Little bit more permissive than RFC7231 5.3.1
- private const string QualityName = "q";
- internal const string BytesUnit = "bytes";
+ private const 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)
+ internal static void SetQuality(IList<NameValueHeaderValue> parameters, double? value)
+ {
+ var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
+ if (value.HasValue)
{
- 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() they 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))
{
- // 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() they 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));
- }
+ 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));
- }
+ var qualityString = ((double)value).ToString("0.0##", NumberFormatInfo.InvariantInfo);
+ if (qualityParameter != null)
+ {
+ qualityParameter.Value = qualityString;
}
else
{
- // Remove quality parameter
- if (qualityParameter != null)
- {
- parameters.Remove(qualityParameter);
- }
+ parameters.Add(new NameValueHeaderValue(QualityName, qualityString));
}
}
-
- internal static double? GetQuality(IList<NameValueHeaderValue>? parameters)
+ else
{
- var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
+ // Remove quality parameter
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 _))
- {
- return qualityValue;
- }
+ parameters.Remove(qualityParameter);
}
- return null;
}
+ }
- internal static void CheckValidToken(StringSegment value, string parameterName)
+ internal static double? GetQuality(IList<NameValueHeaderValue>? parameters)
+ {
+ var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
+ if (qualityParameter != null)
{
- if (StringSegment.IsNullOrEmpty(value))
+ // 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 _))
{
- throw new ArgumentException("An empty string is not allowed.", parameterName);
+ return qualityValue;
}
+ }
+ return null;
+ }
- if (HttpRuleParser.GetTokenLength(value, 0) != value.Length)
- {
- throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid token '{0}'.", value));
- }
+ internal static void CheckValidToken(StringSegment value, string parameterName)
+ {
+ if (StringSegment.IsNullOrEmpty(value))
+ {
+ throw new ArgumentException("An empty string is not allowed.", parameterName);
}
- internal static bool AreEqualCollections<T>(ICollection<T>? x, ICollection<T>? y)
+ if (HttpRuleParser.GetTokenLength(value, 0) != value.Length)
{
- return AreEqualCollections(x, y, null);
+ 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)
+ internal static bool AreEqualCollections<T>(ICollection<T>? x, ICollection<T>? y, IEqualityComparer<T>? comparer)
+ {
+ if (x == null)
{
- if (x == null)
- {
- return (y == null) || (y.Count == 0);
- }
+ return (y == null) || (y.Count == 0);
+ }
- if (y == null)
- {
- return (x.Count == 0);
- }
+ if (y == null)
+ {
+ return (x.Count == 0);
+ }
- if (x.Count != y.Count)
- {
- return false;
- }
+ if (x.Count != y.Count)
+ {
+ return false;
+ }
- if (x.Count == 0)
- {
- return true;
- }
+ 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);
+ // 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)
+ i = 0;
+ var found = false;
+ foreach (var yItem in y)
+ {
+ if (!alreadyFound[i])
{
- if (!alreadyFound[i])
+ if (((comparer == null) && xItem.Equals(yItem)) ||
+ ((comparer != null) && comparer.Equals(xItem, yItem)))
{
- if (((comparer == null) && xItem.Equals(yItem)) ||
- ((comparer != null) && comparer.Equals(xItem, yItem)))
- {
- alreadyFound[i] = true;
- found = true;
- break;
- }
+ alreadyFound[i] = true;
+ found = true;
+ break;
}
- i++;
- }
-
- if (!found)
- {
- return false;
}
+ i++;
}
- // Since we never re-use a "found" value in 'y', we expected '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;
+ if (!found)
+ {
+ return false;
+ }
}
- internal static int GetNextNonEmptyOrWhitespaceIndex(
- StringSegment input,
- int startIndex,
- bool skipEmptyValues,
- out bool separatorFound)
- {
- Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
-
- separatorFound = false;
- var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+ // Since we never re-use a "found" value in 'y', we expected '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.");
- if ((current == input.Length) || (input[current] != ','))
- {
- return current;
- }
+ return true;
+ }
- // 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);
+ internal static int GetNextNonEmptyOrWhitespaceIndex(
+ StringSegment input,
+ int startIndex,
+ bool skipEmptyValues,
+ out bool separatorFound)
+ {
+ Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
- if (skipEmptyValues)
- {
- while ((current < input.Length) && (input[current] == ','))
- {
- current++; // skip delimiter.
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- }
- }
+ separatorFound = false;
+ var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+ if ((current == input.Length) || (input[current] != ','))
+ {
return current;
}
- private static int AdvanceCacheDirectiveIndex(int current, string headerValue)
- {
- // Skip until the next potential name
- current += HttpRuleParser.GetWhitespaceLength(headerValue, 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);
- // Skip the value if present
- if (current < headerValue.Length && headerValue[current] == '=')
+ if (skipEmptyValues)
+ {
+ while ((current < input.Length) && (input[current] == ','))
{
- current++; // skip '='
- current += NameValueHeaderValue.GetValueLength(headerValue, current);
+ current++; // skip delimiter.
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
}
+ }
- // Find the next delimiter
- current = headerValue.IndexOf(',', current);
+ return current;
+ }
- if (current == -1)
- {
- // If no delimiter found, skip to the end
- return headerValue.Length;
- }
+ 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);
+ }
- current++; // skip ','
- current += HttpRuleParser.GetWhitespaceLength(headerValue, current);
+ // Find the next delimiter
+ current = headerValue.IndexOf(',', current);
- return current;
+ if (current == -1)
+ {
+ // If no delimiter found, skip to the end
+ return headerValue.Length;
}
- /// <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>
- /// <see langword="true" /> if <paramref name="targetValue"/> is found and successfully parsed; otherwise,
- /// <see langword="false" />.
- /// </returns>
- // e.g. { "headerValue=10, targetHeaderValue=30" }
- public static bool TryParseSeconds(StringValues headerValues, string targetValue, [NotNullWhen(true)] out TimeSpan? value)
- {
- if (StringValues.IsNullOrEmpty(headerValues) || string.IsNullOrEmpty(targetValue))
- {
- value = null;
- return false;
- }
+ current++; // skip ','
+ current += HttpRuleParser.GetWhitespaceLength(headerValue, current);
- for (var i = 0; i < headerValues.Count; i++)
- {
- var segment = headerValues[i] ?? string.Empty;
+ 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>
+ /// <see langword="true" /> if <paramref name="targetValue"/> is found and successfully parsed; otherwise,
+ /// <see langword="false" />.
+ /// </returns>
+ // e.g. { "headerValue=10, targetHeaderValue=30" }
+ public static bool TryParseSeconds(StringValues headerValues, string targetValue, [NotNullWhen(true)] out TimeSpan? value)
+ {
+ if (StringValues.IsNullOrEmpty(headerValues) || string.IsNullOrEmpty(targetValue))
+ {
+ value = null;
+ return false;
+ }
- // Trim leading white space
- var current = HttpRuleParser.GetWhitespaceLength(segment, 0);
+ for (var i = 0; i < headerValues.Count; i++)
+ {
+ var segment = headerValues[i] ?? string.Empty;
- while (current < segment.Length)
+ // Trim leading white space
+ var current = HttpRuleParser.GetWhitespaceLength(segment, 0);
+
+ while (current < segment.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, segment, out seconds))
{
- 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, segment, out seconds))
- {
- // Token matches target value and seconds were parsed
- value = TimeSpan.FromSeconds(seconds);
- return true;
- }
+ // Token matches target value and seconds were parsed
+ value = TimeSpan.FromSeconds(seconds);
+ return true;
+ }
- current = AdvanceCacheDirectiveIndex(current + tokenLength, segment);
+ current = AdvanceCacheDirectiveIndex(current + tokenLength, segment);
- // Ensure index was advanced
- if (current <= initial)
- {
- Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
- value = null;
- return false;
- }
+ // 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;
+ }
+ 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>
+ /// <see langword="true" /> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirectives"/>;
+ /// otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool ContainsCacheDirective(StringValues cacheControlDirectives, string targetDirectives)
+ {
+ if (StringValues.IsNullOrEmpty(cacheControlDirectives) || string.IsNullOrEmpty(targetDirectives))
+ {
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>
- /// <see langword="true" /> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirectives"/>;
- /// otherwise, <see langword="false" />.
- /// </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++)
+ {
+ var segment = cacheControlDirectives[i] ?? string.Empty;
- for (var i = 0; i < cacheControlDirectives.Count; i++)
- {
- var segment = cacheControlDirectives[i] ?? string.Empty;
+ // Trim leading white space
+ var current = HttpRuleParser.GetWhitespaceLength(segment, 0);
- // Trim leading white space
- var current = HttpRuleParser.GetWhitespaceLength(segment, 0);
+ while (current < segment.Length)
+ {
+ var initial = current;
- while (current < segment.Length)
+ var tokenLength = HttpRuleParser.GetTokenLength(segment, current);
+ if (tokenLength == targetDirectives.Length
+ && string.Compare(segment, current, targetDirectives, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0)
{
- var initial = current;
-
- var tokenLength = HttpRuleParser.GetTokenLength(segment, current);
- if (tokenLength == targetDirectives.Length
- && string.Compare(segment, current, targetDirectives, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0)
- {
- // Token matches target value
- return true;
- }
+ // Token matches target value
+ return true;
+ }
- current = AdvanceCacheDirectiveIndex(current + tokenLength, segment);
+ current = AdvanceCacheDirectiveIndex(current + tokenLength, segment);
- // Ensure index was advanced
- if (current <= initial)
- {
- Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
- return false;
- }
+ // 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 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);
- private static bool TryParseNonNegativeInt64FromHeaderValue(int startIndex, string headerValue, out long result)
+ // Try parse the number
+ if (TryParseNonNegativeInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(headerValue, startIndex, false)), out result))
{
- // Trim leading whitespace
- startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
+ return true;
+ }
- // 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++;
+ result = 0;
+ return false;
+ }
- // Trim trailing whitespace
- startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
+ /// <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><see langword="true" /> if parsing succeeded; otherwise, <see langword="false" />.</returns>
+ public static bool TryParseNonNegativeInt32(StringSegment value, out int result)
+ {
+ if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
+ {
+ result = 0;
+ return false;
+ }
- // Try parse the number
- if (TryParseNonNegativeInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(headerValue, startIndex, false)), out result))
- {
- return true;
- }
+ return int.TryParse(value.AsSpan(), NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
+ }
+ /// <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><see langword="true" /> if parsing succeeded; otherwise, <see langword="false" />.</returns>
+ public static bool TryParseNonNegativeInt64(StringSegment value, out long result)
+ {
+ if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
+ {
result = 0;
return false;
}
+ return long.TryParse(value.AsSpan(), NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
+ }
- /// <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><see langword="true" /> if parsing succeeded; otherwise, <see langword="false" />.</returns>
- public static bool TryParseNonNegativeInt32(StringSegment value, out int result)
- {
- if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
- {
- result = 0;
- return false;
- }
+ // 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;
- return int.TryParse(value.AsSpan(), NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
- }
-
- /// <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><see langword="true" /> if parsing succeeded; otherwise, <see langword="false" />.</returns>
- public static bool TryParseNonNegativeInt64(StringSegment value, out long result)
- {
- if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
- {
- result = 0;
- return false;
- }
- return long.TryParse(value.AsSpan(), NumberStyles.None, NumberFormatInfo.InvariantInfo, out result);
- }
+ var inputLength = input.Length;
+ var current = startIndex;
+ var limit = startIndex + _qualityValueMaxCharCount;
- // 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 intPart = 0;
+ var decPart = 0;
+ var decPow = 1;
- var inputLength = input.Length;
- var current = startIndex;
- var limit = startIndex + _qualityValueMaxCharCount;
+ if (current >= inputLength)
+ {
+ return false;
+ }
- var intPart = 0;
- var decPart = 0;
- var decPow = 1;
+ var ch = input[current];
- if (current >= inputLength)
- {
- return false;
- }
+ 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;
+ }
- var ch = input[current];
+ if (current < inputLength)
+ {
+ ch = input[current];
- if (ch >= '0' && ch <= '1') // Only values between 0 and 1 are accepted, according to RFC
- {
- intPart = ch - '0';
- current++;
- }
- else
+ if (ch >= '0' && ch <= '9')
{
- // The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in the
- // form "0.123".
+ // The RFC accepts only one digit before the dot
return false;
}
- if (current < inputLength)
+ if (ch == '.')
{
- ch = input[current];
-
- if (ch >= '0' && ch <= '9')
- {
- // The RFC accepts only one digit before the dot
- return false;
- }
+ current++;
- if (ch == '.')
+ while (current < inputLength)
{
- current++;
-
- while (current < inputLength)
+ ch = input[current];
+ if (ch >= '0' && ch <= '9')
{
- ch = input[current];
- if (ch >= '0' && ch <= '9')
- {
- if (current >= limit)
- {
- return false;
- }
-
- decPart = decPart * 10 + ch - '0';
- decPow *= 10;
- current++;
- }
- else
+ if (current >= limit)
{
- break;
+ 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 static string FormatNonNegativeInt64(long value)
+ if (decPart != 0)
{
- if (value < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(value), value, "The value to be formatted must be non-negative.");
- }
-
- if (value == 0)
- {
- return "0";
- }
-
- return ((ulong)value).ToString(NumberFormatInfo.InvariantInfo);
+ quality = intPart + decPart / (double)decPow;
}
-
- /// <summary>
- ///Attempts to parse the specified <paramref name="input"/> as a <see cref="DateTimeOffset"/> value.
- /// </summary>
- /// <param name="input">The input value.</param>
- /// <param name="result">The parsed value.</param>
- /// <returns>
- /// <see langword="true" /> if <paramref name="input"/> can be parsed as a date, otherwise <see langword="false" />.
- /// </returns>
- public static bool TryParseDate(StringSegment input, out DateTimeOffset result)
+ else
{
- return HttpRuleParser.TryStringToDate(input, out result);
+ quality = intPart;
}
- /// <summary>
- /// Formats the <paramref name="dateTime"/> using the RFC1123 format specifier.
- /// </summary>
- /// <param name="dateTime">The date to format.</param>
- /// <returns>The formatted date.</returns>
- public static string FormatDate(DateTimeOffset dateTime)
+ if (quality > 1)
{
- return FormatDate(dateTime, quoted: false);
+ // reset quality
+ quality = 0;
+ return false;
}
- /// <summary>
- /// Formats the <paramref name="dateTime"/> using the RFC1123 format specifier and optionally quotes it.
- /// </summary>
- /// <param name="dateTime">The date to format.</param>
- /// <param name="quoted">Determines if the formatted date should be quoted.</param>
- /// <returns>The formatted date.</returns>
- public static string FormatDate(DateTimeOffset dateTime, bool quoted)
+ 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 static string FormatNonNegativeInt64(long value)
+ {
+ if (value < 0)
{
- if (quoted)
- {
- return string.Create(31, dateTime, (span, dt) =>
- {
- span[0] = span[30] = '"';
- dt.TryFormat(span.Slice(1), out _, "r");
- });
- }
+ throw new ArgumentOutOfRangeException(nameof(value), value, "The value to be formatted must be non-negative.");
+ }
- return dateTime.ToString("r", CultureInfo.InvariantCulture);
+ if (value == 0)
+ {
+ return "0";
}
- /// <summary>
- /// Removes quotes from the specified <paramref name="input"/> if quoted.
- /// </summary>
- /// <param name="input">The input to remove quotes from.</param>
- /// <returns>The value without quotes.</returns>
- public static StringSegment RemoveQuotes(StringSegment input)
+ return ((ulong)value).ToString(NumberFormatInfo.InvariantInfo);
+ }
+
+ /// <summary>
+ ///Attempts to parse the specified <paramref name="input"/> as a <see cref="DateTimeOffset"/> value.
+ /// </summary>
+ /// <param name="input">The input value.</param>
+ /// <param name="result">The parsed value.</param>
+ /// <returns>
+ /// <see langword="true" /> if <paramref name="input"/> can be parsed as a date, otherwise <see langword="false" />.
+ /// </returns>
+ public static bool TryParseDate(StringSegment input, out DateTimeOffset result)
+ {
+ return HttpRuleParser.TryStringToDate(input, out result);
+ }
+
+ /// <summary>
+ /// Formats the <paramref name="dateTime"/> using the RFC1123 format specifier.
+ /// </summary>
+ /// <param name="dateTime">The date to format.</param>
+ /// <returns>The formatted date.</returns>
+ public static string FormatDate(DateTimeOffset dateTime)
+ {
+ return FormatDate(dateTime, quoted: false);
+ }
+
+ /// <summary>
+ /// Formats the <paramref name="dateTime"/> using the RFC1123 format specifier and optionally quotes it.
+ /// </summary>
+ /// <param name="dateTime">The date to format.</param>
+ /// <param name="quoted">Determines if the formatted date should be quoted.</param>
+ /// <returns>The formatted date.</returns>
+ public static string FormatDate(DateTimeOffset dateTime, bool quoted)
+ {
+ if (quoted)
{
- if (IsQuoted(input))
+ return string.Create(31, dateTime, (span, dt) =>
{
- input = input.Subsegment(1, input.Length - 2);
- }
- return input;
+ span[0] = span[30] = '"';
+ dt.TryFormat(span.Slice(1), out _, "r");
+ });
}
- /// <summary>
- /// Determines if the specified <paramref name="input"/> is quoted.
- /// </summary>
- /// <param name="input">The value to inspect.</param>
- /// <returns><see langword="true"/> if the value is quoted, otherwise <see langword="false"/>.</returns>
- public static bool IsQuoted(StringSegment input)
+ return dateTime.ToString("r", CultureInfo.InvariantCulture);
+ }
+
+ /// <summary>
+ /// Removes quotes from the specified <paramref name="input"/> if quoted.
+ /// </summary>
+ /// <param name="input">The input to remove quotes from.</param>
+ /// <returns>The value without quotes.</returns>
+ public static StringSegment RemoveQuotes(StringSegment input)
+ {
+ if (IsQuoted(input))
{
- return !StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"';
+ input = input.Subsegment(1, input.Length - 2);
}
+ return input;
+ }
- /// <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);
+ /// <summary>
+ /// Determines if the specified <paramref name="input"/> is quoted.
+ /// </summary>
+ /// <param name="input">The value to inspect.</param>
+ /// <returns><see langword="true"/> if the value is quoted, otherwise <see langword="false"/>.</returns>
+ 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 string
- var backSlashCount = CountBackslashesForDecodingQuotedString(input);
+ // First pass to calculate the size of the string
+ var backSlashCount = CountBackslashesForDecodingQuotedString(input);
- if (backSlashCount == 0)
- {
- return input;
- }
+ if (backSlashCount == 0)
+ {
+ return input;
+ }
- return string.Create(input.Length - backSlashCount, input, (span, segment) =>
+ return string.Create(input.Length - backSlashCount, input, (span, segment) =>
+ {
+ var spanIndex = 0;
+ var spanLength = span.Length;
+ for (var i = 0; i < segment.Length && (uint)spanIndex < (uint)spanLength; i++)
{
- var spanIndex = 0;
- var spanLength = span.Length;
- for (var i = 0; i < segment.Length && (uint)spanIndex < (uint)spanLength; i++)
+ int nextIndex = i + 1;
+ if ((uint)nextIndex < (uint)segment.Length && segment[i] == '\\')
{
- int nextIndex = i + 1;
- if ((uint)nextIndex < (uint)segment.Length && segment[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\\"
@@ -621,102 +621,101 @@ namespace Microsoft.Net.Http.Headers
// we will assume it is over escaping and just add a n to the string.
// Ex: "he\\llo" => "hello"
span[spanIndex] = segment[nextIndex];
- i++;
- }
- else
- {
- span[spanIndex] = segment[i];
- }
-
- spanIndex++;
+ i++;
}
- });
- }
+ else
+ {
+ span[spanIndex] = segment[i];
+ }
+
+ spanIndex++;
+ }
+ });
+ }
- private static int CountBackslashesForDecodingQuotedString(StringSegment input)
+ private static int CountBackslashesForDecodingQuotedString(StringSegment input)
+ {
+ var numberBackSlashes = 0;
+ for (var i = 0; i < input.Length; i++)
{
- var numberBackSlashes = 0;
- for (var i = 0; i < input.Length; i++)
- {
- if (i < input.Length - 1 && input[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] == '\\')
{
- // 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++;
+ // 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);
-
- // 2 for quotes
- return string.Create(input.Length + backSlashCount + 2, input, (span, segment) =>
- {
+ }
+ 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);
+
+ // 2 for quotes
+ return string.Create(input.Length + backSlashCount + 2, input, (span, segment) =>
+ {
// Helps to elide the bounds check for span[0]
span[span.Length - 1] = span[0] = '\"';
- var spanIndex = 1;
- for (var i = 0; i < segment.Length; i++)
+ var spanIndex = 1;
+ for (var i = 0; i < segment.Length; i++)
+ {
+ if (segment[i] == '\\' || segment[i] == '\"')
+ {
+ span[spanIndex++] = '\\';
+ }
+ else if ((segment[i] <= 0x1F || segment[i] == 0x7F) && segment[i] != 0x09)
{
- if (segment[i] == '\\' || segment[i] == '\"')
- {
- span[spanIndex++] = '\\';
- }
- else if ((segment[i] <= 0x1F || segment[i] == 0x7F) && segment[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 '{segment[i]}' in input.");
- }
- span[spanIndex++] = segment[i];
}
- });
- }
+ span[spanIndex++] = segment[i];
+ }
+ });
+ }
- private static int CountAndCheckCharactersNeedingBackslashesWhenEncoding(StringSegment input)
+ private static int CountAndCheckCharactersNeedingBackslashesWhenEncoding(StringSegment input)
+ {
+ var numberOfCharactersNeedingEscaping = 0;
+ for (var i = 0; i < input.Length; i++)
{
- var numberOfCharactersNeedingEscaping = 0;
- for (var i = 0; i < input.Length; i++)
+ if (input[i] == '\\' || input[i] == '\"')
{
- if (input[i] == '\\' || input[i] == '\"')
- {
- numberOfCharactersNeedingEscaping++;
- }
+ numberOfCharactersNeedingEscaping++;
}
- return numberOfCharactersNeedingEscaping;
}
+ return numberOfCharactersNeedingEscaping;
+ }
- internal static void ThrowIfReadOnly(bool isReadOnly)
+ internal static void ThrowIfReadOnly(bool isReadOnly)
+ {
+ if (isReadOnly)
{
- if (isReadOnly)
- {
- throw new InvalidOperationException("The object cannot be modified because it is read-only.");
- }
+ 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
index eb654fca3f..ba7a5874c0 100644
--- a/src/Http/Headers/src/HttpHeaderParser.cs
+++ b/src/Http/Headers/src/HttpHeaderParser.cs
@@ -8,161 +8,160 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal abstract class HttpHeaderParser<T>
{
- internal abstract class HttpHeaderParser<T>
+ private readonly bool _supportsMultipleValues;
+
+ protected HttpHeaderParser(bool supportsMultipleValues)
{
- private readonly bool _supportsMultipleValues;
+ _supportsMultipleValues = supportsMultipleValues;
+ }
- protected HttpHeaderParser(bool supportsMultipleValues)
- {
- _supportsMultipleValues = supportsMultipleValues;
- }
+ public bool SupportsMultipleValues
+ {
+ get { return _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);
- // 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)));
- public T? ParseValue(StringSegment value, ref int index)
+ // If a parser returns 'null', it means there was no value, but that's valid (e.g. "Accept: "). The caller
+ // can ignore the value.
+ if (!TryParseValue(value, ref index, out var result))
{
- // 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.
- if (!TryParseValue(value, ref index, out var result))
- {
- throw new FormatException(string.Format(CultureInfo.InvariantCulture,
- "The header contains invalid values at index {0}: '{1}'", index, value.Value ?? "<null>"));
- }
- return 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, [NotNullWhen(true)] out IList<T>? parsedValues)
- {
- return TryParseValues(values, strict: false, parsedValues: out parsedValues);
- }
+ public virtual bool TryParseValues(IList<string>? values, [NotNullWhen(true)] out IList<T>? parsedValues)
+ {
+ return TryParseValues(values, strict: false, parsedValues: out parsedValues);
+ }
- public virtual bool TryParseStrictValues(IList<string>? values, [NotNullWhen(true)] out IList<T>? parsedValues)
+ public virtual bool TryParseStrictValues(IList<string>? values, [NotNullWhen(true)] out IList<T>? parsedValues)
+ {
+ return TryParseValues(values, strict: true, parsedValues: out parsedValues);
+ }
+
+ protected virtual bool TryParseValues(IList<string>? values, bool strict, [NotNullWhen(true)] 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 TryParseValues(values, strict: true, parsedValues: out parsedValues);
+ return false;
}
-
- protected virtual bool TryParseValues(IList<string>? values, bool strict, [NotNullWhen(true)] out IList<T>? parsedValues)
+ for (var i = 0; i < values.Count; i++)
{
- 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];
- var index = 0;
+ var value = values[i];
+ var index = 0;
- while (!string.IsNullOrEmpty(value) && index < value.Length)
+ while (!string.IsNullOrEmpty(value) && index < value.Length)
+ {
+ if (TryParseValue(value, ref index, out var output))
{
- if (TryParseValue(value, ref index, out var output))
+ // The entry may not contain an actual value, like " , "
+ if (output != null)
{
- // The entry may not contain an actual value, like " , "
- if (output != null)
+ if (results == null)
{
- if (results == null)
- {
- results = new List<T>(); // Allocate it only when used
- }
- results.Add(output);
+ results = new List<T>(); // Allocate it only when used
}
- }
- else if (strict)
- {
- return false;
- }
- else
- {
- // Skip the invalid values and keep trying.
- index++;
+ 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)
+ if (results != null)
{
- return ParseValues(values, strict: false);
+ 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)
+ 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 ParseValues(values, strict: true);
+ return parsedValues;
}
-
- protected virtual IList<T> ParseValues(IList<string>? values, bool strict)
+ foreach (var value in values)
{
- 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;
+ int index = 0;
- while (!string.IsNullOrEmpty(value) && index < value.Length)
+ while (!string.IsNullOrEmpty(value) && index < value.Length)
+ {
+ if (TryParseValue(value, ref index, out var output))
{
- if (TryParseValue(value, ref index, out var output))
+ // The entry may not contain an actual value, like " , "
+ if (output != null)
{
- // 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++;
+ 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;
}
+ 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)
- {
- return value.ToString()!;
- }
+ // 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)
+ {
+ return value.ToString()!;
}
}
diff --git a/src/Http/Headers/src/MediaTypeHeaderValue.cs b/src/Http/Headers/src/MediaTypeHeaderValue.cs
index 5b21693433..126d246e92 100644
--- a/src/Http/Headers/src/MediaTypeHeaderValue.cs
+++ b/src/Http/Headers/src/MediaTypeHeaderValue.cs
@@ -11,829 +11,828 @@ using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+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
{
- /// <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 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 const char ForwardSlashCharacter = '/';
+ private const char PeriodCharacter = '.';
+ private const char PlusCharacter = '+';
- private static readonly char[] PeriodCharacterArray = new char[] { PeriodCharacter };
+ 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);
+ 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;
+ // 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.
- }
+ 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>
+ 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>
+ /// 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
+ /// <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
{
- get
- {
- return NameValueHeaderValue.Find(_parameters, CharsetString)?.Value ?? default;
- }
- 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));
- }
- }
- }
+ return NameValueHeaderValue.Find(_parameters, CharsetString)?.Value ?? default;
}
-
- /// <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
+ set
{
- get
+ 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))
{
- var charset = Charset;
-
- // Check HasValue; IsNullOrEmpty lacks [MemberNotNullWhen(false, nameof(Value))].
- if (charset.HasValue && !StringSegment.IsNullOrEmpty(charset))
+ // Remove charset parameter
+ if (charsetParameter != null)
{
- try
- {
- return Encoding.GetEncoding(charset.Value);
- }
- catch (ArgumentException)
- {
- // Invalid or not supported
- }
+ Parameters.Remove(charsetParameter);
}
- return null;
}
- set
+ else
{
- HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
- if (value == null)
+ if (charsetParameter != null)
{
- Charset = null;
+ charsetParameter.Value = value;
}
else
{
- Charset = value.WebName;
+ Parameters.Add(new NameValueHeaderValue(CharsetString, value));
}
}
}
+ }
- /// <summary>
- /// Gets or sets the value of the boundary parameter. Returns <see cref="StringSegment.Empty"/>
- /// if there is no boundary.
- /// </summary>
- public StringSegment Boundary
+ /// <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
{
- get
- {
- return NameValueHeaderValue.Find(_parameters, BoundaryString)?.Value ?? default(StringSegment);
- }
- set
+ var charset = Charset;
+
+ // Check HasValue; IsNullOrEmpty lacks [MemberNotNullWhen(false, nameof(Value))].
+ if (charset.HasValue && !StringSegment.IsNullOrEmpty(charset))
{
- HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
- var boundaryParameter = NameValueHeaderValue.Find(_parameters, BoundaryString);
- if (StringSegment.IsNullOrEmpty(value))
+ try
{
- // Remove charset parameter
- if (boundaryParameter != null)
- {
- Parameters.Remove(boundaryParameter);
- }
+ return Encoding.GetEncoding(charset.Value);
}
- else
+ catch (ArgumentException)
{
- if (boundaryParameter != null)
- {
- boundaryParameter.Value = value;
- }
- else
- {
- Parameters.Add(new NameValueHeaderValue(BoundaryString, value));
- }
+ // Invalid or not supported
}
}
+ return null;
}
-
- /// <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
+ set
{
- get
+ HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+ if (value == null)
{
- if (_parameters == null)
- {
- if (IsReadOnly)
- {
- _parameters = ObjectCollection<NameValueHeaderValue>.EmptyReadOnlyCollection;
- }
- else
- {
- _parameters = new ObjectCollection<NameValueHeaderValue>();
- }
- }
- return _parameters;
+ Charset = null;
}
- }
-
- /// <summary>
- /// Gets or sets the value of the quality parameter. Returns null
- /// if there is no quality.
- /// </summary>
- public double? Quality
- {
- get => 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
+ else
{
- HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
- CheckMediaTypeFormat(value, nameof(value));
- _mediaType = value;
+ Charset = value.WebName;
}
}
+ }
- /// <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
+ /// <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
{
- get
- {
- return _mediaType.Subsegment(0, _mediaType.IndexOf(ForwardSlashCharacter));
- }
+ return NameValueHeaderValue.Find(_parameters, BoundaryString)?.Value ?? default(StringSegment);
}
-
- /// <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
+ set
{
- get
+ HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+ var boundaryParameter = NameValueHeaderValue.Find(_parameters, BoundaryString);
+ if (StringSegment.IsNullOrEmpty(value))
{
- return _mediaType.Subsegment(_mediaType.IndexOf(ForwardSlashCharacter) + 1);
+ // Remove charset parameter
+ if (boundaryParameter != null)
+ {
+ Parameters.Remove(boundaryParameter);
+ }
}
- }
-
- /// <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
+ else
{
- var subType = SubType;
- var startOfSuffix = subType.LastIndexOf(PlusCharacter);
- if (startOfSuffix == -1)
+ if (boundaryParameter != null)
{
- return subType;
+ boundaryParameter.Value = value;
}
else
{
- return subType.Subsegment(0, startOfSuffix);
+ Parameters.Add(new NameValueHeaderValue(BoundaryString, value));
}
}
}
+ }
- /// <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
+ /// <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
{
- get
+ if (_parameters == null)
{
- var subType = SubType;
- var startOfSuffix = subType.LastIndexOf(PlusCharacter);
- if (startOfSuffix == -1)
+ if (IsReadOnly)
{
- return default(StringSegment);
+ _parameters = ObjectCollection<NameValueHeaderValue>.EmptyReadOnlyCollection;
}
else
{
- return subType.Subsegment(startOfSuffix + 1);
+ _parameters = new ObjectCollection<NameValueHeaderValue>();
}
}
+ return _parameters;
}
+ }
-
- /// <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
+ /// <summary>
+ /// Gets or sets the value of the quality parameter. Returns null
+ /// if there is no quality.
+ /// </summary>
+ public double? Quality
+ {
+ get => HeaderUtilities.GetQuality(_parameters);
+ set
{
- get
- {
- return SubTypeWithoutSuffix.Split(PeriodCharacterArray);
- }
+ HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+ HeaderUtilities.SetQuality(Parameters, value);
}
+ }
- /// <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>
+ /// 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>
- /// 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()
+ /// <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
{
- var other = new MediaTypeHeaderValue();
- other._mediaType = _mediaType;
+ return _mediaType.Subsegment(0, _mediaType.IndexOf(ForwardSlashCharacter));
+ }
+ }
- if (_parameters != null)
- {
- other._parameters = new ObjectCollection<NameValueHeaderValue>(
- _parameters.Select(item => item.Copy()));
- }
- return other;
+ /// <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>
- /// 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()
+ /// <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
{
- if (IsReadOnly)
+ var subType = SubType;
+ var startOfSuffix = subType.LastIndexOf(PlusCharacter);
+ if (startOfSuffix == -1)
+ {
+ return subType;
+ }
+ else
{
- return this;
+ return subType.Subsegment(0, startOfSuffix);
}
+ }
+ }
- var other = new MediaTypeHeaderValue();
- other._mediaType = _mediaType;
- if (_parameters != null)
+ /// <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)
{
- other._parameters = new ObjectCollection<NameValueHeaderValue>(
- _parameters.Select(item => item.CopyAsReadOnly()), isReadOnly: true);
+ return default(StringSegment);
}
- other._isReadOnly = true;
- return other;
- }
-
- /// <summary>
- /// Gets a value indicating whether <paramref name="otherMediaType"/> is a subset of
- /// this <see cref="MediaTypeHeaderValue"/> in terms of type/subType. 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="StringSegment"/> to compare.</param>
- /// <returns>
- /// A value indicating whether <paramref name="otherMediaType"/> is a subset of
- /// this <see cref="MediaTypeHeaderValue"/>.
- /// </returns>
- /// <remarks>
- /// For example "multipart/mixed" is a subset of "multipart/mixed",
- /// "multipart/*", and "*/*" but not "multipart/message."
- /// </remarks>
- public bool MatchesMediaType(StringSegment otherMediaType)
- {
- if (StringSegment.IsNullOrEmpty(otherMediaType))
+ else
{
- return false;
+ return subType.Subsegment(startOfSuffix + 1);
}
- GetMediaTypeExpressionLength(otherMediaType, 0, out var mediaType);
-
- return MatchesType(mediaType) && MatchesSubtype(mediaType);
}
+ }
- /// <inheritdoc />
- public override string ToString()
+
+ /// <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
{
- var builder = new StringBuilder();
- builder.Append(_mediaType.AsSpan());
- NameValueHeaderValue.ToString(_parameters, separator: ';', leadingSeparator: true, destination: builder);
- return builder.ToString();
+ return SubTypeWithoutSuffix.Split(PeriodCharacterArray);
}
+ }
- /// <inheritdoc />
- public override bool Equals(object? obj)
- {
- var other = obj as MediaTypeHeaderValue;
+ /// <summary>
+ /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all types.
+ /// </summary>
+ public bool MatchesAllTypes => MediaType.Equals(MatchesAllString, StringComparison.Ordinal);
- if (other == null)
- {
- return false;
- }
+ /// <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);
- return _mediaType.Equals(other._mediaType, StringComparison.OrdinalIgnoreCase) &&
- HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
- }
+ /// <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);
- /// <inheritdoc />
- public override int GetHashCode()
- {
- // The media-type string is case-insensitive.
- return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_parameters);
- }
+ /// <summary>
+ /// Gets whether the <see cref="MediaTypeHeaderValue"/> is readonly.
+ /// </summary>
+ public bool IsReadOnly
+ {
+ get { return _isReadOnly; }
+ }
- /// <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)
+ /// <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)
{
- var index = 0;
- return SingleValueParser.ParseValue(input, ref index)!;
+ return false;
}
- /// <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, [NotNullWhen(true)] out MediaTypeHeaderValue? parsedValue)
+ // "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)
{
- var index = 0;
- return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ other._parameters = new ObjectCollection<NameValueHeaderValue>(
+ _parameters.Select(item => item.Copy()));
}
+ return other;
+ }
- /// <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)
+ /// <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 MultipleValueParser.ParseValues(inputs);
+ return this;
}
- /// <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)
+ var other = new MediaTypeHeaderValue();
+ other._mediaType = _mediaType;
+ if (_parameters != null)
{
- return MultipleValueParser.ParseStrictValues(inputs);
+ other._parameters = new ObjectCollection<NameValueHeaderValue>(
+ _parameters.Select(item => item.CopyAsReadOnly()), isReadOnly: true);
}
+ other._isReadOnly = true;
+ return other;
+ }
- /// <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, [NotNullWhen(true)] out IList<MediaTypeHeaderValue>? parsedValues)
+ /// <summary>
+ /// Gets a value indicating whether <paramref name="otherMediaType"/> is a subset of
+ /// this <see cref="MediaTypeHeaderValue"/> in terms of type/subType. 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="StringSegment"/> to compare.</param>
+ /// <returns>
+ /// A value indicating whether <paramref name="otherMediaType"/> is a subset of
+ /// this <see cref="MediaTypeHeaderValue"/>.
+ /// </returns>
+ /// <remarks>
+ /// For example "multipart/mixed" is a subset of "multipart/mixed",
+ /// "multipart/*", and "*/*" but not "multipart/message."
+ /// </remarks>
+ public bool MatchesMediaType(StringSegment otherMediaType)
+ {
+ if (StringSegment.IsNullOrEmpty(otherMediaType))
{
- return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ return false;
}
+ GetMediaTypeExpressionLength(otherMediaType, 0, out var mediaType);
+
+ return MatchesType(mediaType) && MatchesSubtype(mediaType);
+ }
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append(_mediaType.AsSpan());
+ NameValueHeaderValue.ToString(_parameters, separator: ';', leadingSeparator: true, destination: builder);
+ return builder.ToString();
+ }
- /// <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, [NotNullWhen(true)] out IList<MediaTypeHeaderValue>? parsedValues)
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as MediaTypeHeaderValue;
+
+ if (other == null)
{
- return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+ return false;
}
- private static int GetMediaTypeLength(StringSegment input, int startIndex, out MediaTypeHeaderValue? parsedValue)
- {
- Contract.Requires(startIndex >= 0);
+ return _mediaType.Equals(other._mediaType, StringComparison.OrdinalIgnoreCase) &&
+ HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
+ }
- parsedValue = null;
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ // The media-type string is case-insensitive.
+ return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_parameters);
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ /// <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)!;
+ }
- // Caller must remove leading whitespace. If not, we'll return 0.
- var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out var mediaType);
+ /// <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, [NotNullWhen(true)] out MediaTypeHeaderValue? parsedValue)
+ {
+ var index = 0;
+ return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ }
- if (mediaTypeLength == 0)
- {
- return 0;
- }
+ /// <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);
+ }
- var current = startIndex + mediaTypeLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- MediaTypeHeaderValue? mediaTypeHeader = null;
+ /// <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);
+ }
- // 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;
+ /// <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, [NotNullWhen(true)] out IList<MediaTypeHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ }
- current++; // skip delimiter.
- var parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';',
- mediaTypeHeader.Parameters);
+ /// <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, [NotNullWhen(true)] out IList<MediaTypeHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+ }
- parsedValue = mediaTypeHeader;
- return current + parameterLength - startIndex;
- }
+ private static int GetMediaTypeLength(StringSegment input, int startIndex, out MediaTypeHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- // We have a media type without parameters.
- mediaTypeHeader = new MediaTypeHeaderValue();
- mediaTypeHeader._mediaType = mediaType;
- parsedValue = mediaTypeHeader;
- return current - startIndex;
- }
+ parsedValue = null;
- private static int GetMediaTypeExpressionLength(StringSegment input, int startIndex, out StringSegment mediaType)
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
- Contract.Requires((input.Length > 0) && (startIndex < input.Length));
+ return 0;
+ }
- // This method just parses the "type/subtype" string, it does not parse parameters.
- mediaType = null;
+ // Caller must remove leading whitespace. If not, we'll return 0.
+ var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out var mediaType);
- // Parse the type, i.e. <type> in media type string "<type>/<subtype>; param1=value1; param2=value2"
- var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);
+ if (mediaTypeLength == 0)
+ {
+ return 0;
+ }
- if (typeLength == 0)
- {
- return 0;
- }
+ var current = startIndex + mediaTypeLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ MediaTypeHeaderValue? mediaTypeHeader = null;
- var current = startIndex + typeLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ // 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;
- // Parse the separator between type and subtype
- if ((current >= input.Length) || (input[current] != '/'))
- {
- return 0;
- }
current++; // skip delimiter.
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ var parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';',
+ mediaTypeHeader.Parameters);
- // Parse the subtype, i.e. <subtype> in media type string "<type>/<subtype>; param1=value1; param2=value2"
- var subtypeLength = HttpRuleParser.GetTokenLength(input, current);
+ parsedValue = mediaTypeHeader;
+ return current + parameterLength - startIndex;
+ }
- if (subtypeLength == 0)
- {
- return 0;
- }
+ // We have a media type without parameters.
+ mediaTypeHeader = new MediaTypeHeaderValue();
+ mediaTypeHeader._mediaType = mediaType;
+ parsedValue = mediaTypeHeader;
+ return current - startIndex;
+ }
- // 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 = string.Concat(input.AsSpan().Slice(startIndex, typeLength), "/", input.AsSpan().Slice(current, subtypeLength));
- }
+ private static int GetMediaTypeExpressionLength(StringSegment input, int startIndex, out StringSegment mediaType)
+ {
+ Contract.Requires((input.Length > 0) && (startIndex < input.Length));
- return mediaTypeLength;
- }
+ // This method just parses the "type/subtype" string, it does not parse parameters.
+ mediaType = null;
- private static void CheckMediaTypeFormat(StringSegment mediaType, string parameterName)
- {
- if (StringSegment.IsNullOrEmpty(mediaType))
- {
- throw new ArgumentException("An empty string is not allowed.", parameterName);
- }
+ // Parse the type, i.e. <type> in media type string "<type>/<subtype>; param1=value1; param2=value2"
+ var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);
- // 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));
- }
+ if (typeLength == 0)
+ {
+ return 0;
}
- private bool MatchesType(MediaTypeHeaderValue set)
+ var current = startIndex + typeLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+ // Parse the separator between type and subtype
+ if ((current >= input.Length) || (input[current] != '/'))
{
- return set.MatchesAllTypes ||
- set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase);
+ return 0;
}
+ current++; // skip delimiter.
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- private bool MatchesType(StringSegment mediaType)
- {
- var type = mediaType.Subsegment(0, mediaType.IndexOf(ForwardSlashCharacter));
+ // Parse the subtype, i.e. <subtype> in media type string "<type>/<subtype>; param1=value1; param2=value2"
+ var subtypeLength = HttpRuleParser.GetTokenLength(input, current);
- return MatchesAllTypes ||
- Type.Equals(type, StringComparison.OrdinalIgnoreCase);
+ if (subtypeLength == 0)
+ {
+ return 0;
}
- private bool MatchesSubtype(MediaTypeHeaderValue set)
+ // 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)
{
- if (set.MatchesAllSubTypes)
- {
- return true;
- }
+ mediaType = input.Subsegment(startIndex, mediaTypeLength);
+ }
+ else
+ {
+ mediaType = string.Concat(input.AsSpan().Slice(startIndex, typeLength), "/", input.AsSpan().Slice(current, subtypeLength));
+ }
- if (set.Suffix.HasValue)
- {
- if (Suffix.HasValue)
- {
- return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set);
- }
- else
- {
- return false;
- }
- }
- else
- {
- // If this subtype or suffix matches the subtype of the set,
- // it is considered a subtype.
- // Ex: application/json > application/val+json
- return MatchesEitherSubtypeOrSuffix(set);
- }
+ return mediaTypeLength;
+ }
+
+ private static void CheckMediaTypeFormat(StringSegment mediaType, string parameterName)
+ {
+ if (StringSegment.IsNullOrEmpty(mediaType))
+ {
+ throw new ArgumentException("An empty string is not allowed.", parameterName);
}
- private bool MatchesSubtype(StringSegment mediaType)
+ // 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))
{
- if (MatchesAllSubTypes)
- {
- return true;
- }
+ throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid media type '{0}'.", mediaType));
+ }
+ }
- var subType = mediaType.Subsegment(mediaType.IndexOf(ForwardSlashCharacter) + 1);
+ private bool MatchesType(MediaTypeHeaderValue set)
+ {
+ return set.MatchesAllTypes ||
+ set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase);
+ }
- StringSegment suffix;
- var startOfSuffix = subType.LastIndexOf(PlusCharacter);
- if (startOfSuffix == -1)
- {
- suffix = default(StringSegment);
- }
- else
- {
- suffix = subType.Subsegment(startOfSuffix + 1);
- }
+ private bool MatchesType(StringSegment mediaType)
+ {
+ var type = mediaType.Subsegment(0, mediaType.IndexOf(ForwardSlashCharacter));
+
+ return MatchesAllTypes ||
+ Type.Equals(type, StringComparison.OrdinalIgnoreCase);
+ }
+ private bool MatchesSubtype(MediaTypeHeaderValue set)
+ {
+ if (set.MatchesAllSubTypes)
+ {
+ return true;
+ }
+
+ if (set.Suffix.HasValue)
+ {
if (Suffix.HasValue)
{
- if (suffix.HasValue)
- {
- return MatchesSubtypeWithoutSuffix(subType, startOfSuffix) && MatchesSubtypeSuffix(suffix);
- }
- else
- {
- return false;
- }
+ return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set);
}
else
{
- // If this subtype or suffix matches the subtype of the mediaType,
- // it is considered a subtype.
- // Ex: application/json > application/val+json
- return MatchesEitherSubtypeOrSuffix(subType, suffix);
+ return false;
}
}
+ else
+ {
+ // If this subtype or suffix matches the subtype of the set,
+ // it is considered a subtype.
+ // Ex: application/json > application/val+json
+ return MatchesEitherSubtypeOrSuffix(set);
+ }
+ }
- private bool MatchesSubtypeWithoutSuffix(MediaTypeHeaderValue set)
+ private bool MatchesSubtype(StringSegment mediaType)
+ {
+ if (MatchesAllSubTypes)
{
- return set.MatchesAllSubTypesWithoutSuffix ||
- set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
+ return true;
}
- private bool MatchesSubtypeWithoutSuffix(StringSegment subType, int startOfSuffix)
+ var subType = mediaType.Subsegment(mediaType.IndexOf(ForwardSlashCharacter) + 1);
+
+ StringSegment suffix;
+ var startOfSuffix = subType.LastIndexOf(PlusCharacter);
+ if (startOfSuffix == -1)
{
- StringSegment subTypeWithoutSuffix;
- if (startOfSuffix == -1)
+ suffix = default(StringSegment);
+ }
+ else
+ {
+ suffix = subType.Subsegment(startOfSuffix + 1);
+ }
+
+ if (Suffix.HasValue)
+ {
+ if (suffix.HasValue)
{
- subTypeWithoutSuffix = subType;
+ return MatchesSubtypeWithoutSuffix(subType, startOfSuffix) && MatchesSubtypeSuffix(suffix);
}
else
{
- subTypeWithoutSuffix = subType.Subsegment(0, startOfSuffix);
+ return false;
}
- return SubTypeWithoutSuffix.Equals(WildcardString, StringComparison.OrdinalIgnoreCase) ||
- SubTypeWithoutSuffix.Equals(subTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
}
-
- private bool MatchesEitherSubtypeOrSuffix(MediaTypeHeaderValue set)
+ else
{
- return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase) ||
- set.SubType.Equals(Suffix, StringComparison.OrdinalIgnoreCase);
+ // If this subtype or suffix matches the subtype of the mediaType,
+ // it is considered a subtype.
+ // Ex: application/json > application/val+json
+ return MatchesEitherSubtypeOrSuffix(subType, suffix);
}
+ }
- private bool MatchesEitherSubtypeOrSuffix(StringSegment subType, StringSegment suffix)
+ private bool MatchesSubtypeWithoutSuffix(MediaTypeHeaderValue set)
+ {
+ return set.MatchesAllSubTypesWithoutSuffix ||
+ set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool MatchesSubtypeWithoutSuffix(StringSegment subType, int startOfSuffix)
+ {
+ StringSegment subTypeWithoutSuffix;
+ if (startOfSuffix == -1)
{
- return subType.Equals(SubType, StringComparison.OrdinalIgnoreCase) ||
- SubType.Equals(suffix, StringComparison.OrdinalIgnoreCase);
+ subTypeWithoutSuffix = subType;
}
+ else
+ {
+ subTypeWithoutSuffix = subType.Subsegment(0, startOfSuffix);
+ }
+ return SubTypeWithoutSuffix.Equals(WildcardString, StringComparison.OrdinalIgnoreCase) ||
+ SubTypeWithoutSuffix.Equals(subTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool MatchesEitherSubtypeOrSuffix(MediaTypeHeaderValue set)
+ {
+ return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase) ||
+ set.SubType.Equals(Suffix, StringComparison.OrdinalIgnoreCase);
+ }
- private bool MatchesParameters(MediaTypeHeaderValue set)
+ private bool MatchesEitherSubtypeOrSuffix(StringSegment subType, StringSegment suffix)
+ {
+ return subType.Equals(SubType, StringComparison.OrdinalIgnoreCase) ||
+ SubType.Equals(suffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool MatchesParameters(MediaTypeHeaderValue set)
+ {
+ if (set._parameters != null && set._parameters.Count != 0)
{
- 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)
{
- // 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))
{
- 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;
- }
+ // 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;
}
+ 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);
- }
+ 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);
+ }
- private bool MatchesSubtypeSuffix(StringSegment suffix)
- {
- // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*")
- // because there's no clear use case for it.
- return Suffix.Equals(suffix, StringComparison.OrdinalIgnoreCase);
- }
+ private bool MatchesSubtypeSuffix(StringSegment suffix)
+ {
+ // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*")
+ // because there's no clear use case for it.
+ return Suffix.Equals(suffix, StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs b/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs
index d5f25cc68f..df46786efc 100644
--- a/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs
+++ b/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs
@@ -4,136 +4,135 @@
using System;
using System.Collections.Generic;
-namespace Microsoft.Net.Http.Headers
+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 MediaTypeHeaderValueComparer()
+ {
+ }
+
/// <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).
+ /// Gets the <see cref="MediaTypeHeaderValueComparer"/> instance.
/// </summary>
- public class MediaTypeHeaderValueComparer : IComparer<MediaTypeHeaderValue>
+ public static MediaTypeHeaderValueComparer QualityComparer { get; } = new MediaTypeHeaderValueComparer();
+
+ /// <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)
{
- private MediaTypeHeaderValueComparer()
+ if (object.ReferenceEquals(mediaType1, mediaType2))
{
+ return 0;
}
- /// <summary>
- /// Gets the <see cref="MediaTypeHeaderValueComparer"/> instance.
- /// </summary>
- public static MediaTypeHeaderValueComparer QualityComparer { get; } = new MediaTypeHeaderValueComparer();
-
- /// <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 (mediaType1 is null)
{
- if (object.ReferenceEquals(mediaType1, mediaType2))
- {
- return 0;
- }
-
- if (mediaType1 is null)
- {
- return -1;
- }
+ return -1;
+ }
- if (mediaType2 is null)
- {
- return 1;
- }
+ if (mediaType2 is null)
+ {
+ return 1;
+ }
- var returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2);
+ var returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2);
- if (returnValue == 0)
+ if (returnValue == 0)
+ {
+ if (!mediaType1.Type.Equals(mediaType2.Type, StringComparison.OrdinalIgnoreCase))
{
- 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)
{
- 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;
- }
+ return -1;
}
- else if (!mediaType1.SubType.Equals(mediaType2.SubType, StringComparison.OrdinalIgnoreCase))
+ else if (!mediaType1.MatchesAllSubTypes && mediaType2.MatchesAllSubTypes)
{
- 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;
- }
+ return 1;
}
- else if (!mediaType1.Suffix.Equals(mediaType2.Suffix, StringComparison.OrdinalIgnoreCase))
+ else if (mediaType1.MatchesAllSubTypesWithoutSuffix && !mediaType2.MatchesAllSubTypesWithoutSuffix)
{
- if (mediaType1.MatchesAllSubTypesWithoutSuffix)
- {
- return -1;
- }
- else if (mediaType2.MatchesAllSubTypesWithoutSuffix)
- {
- return 1;
- }
+ return -1;
+ }
+ else if (!mediaType1.MatchesAllSubTypesWithoutSuffix && 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)
+ else if (!mediaType1.SubType.Equals(mediaType2.SubType, StringComparison.OrdinalIgnoreCase))
{
- return -1;
+ 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 (qualityDifference > 0)
+ else if (!mediaType1.Suffix.Equals(mediaType2.Suffix, StringComparison.OrdinalIgnoreCase))
{
- return 1;
+ if (mediaType1.MatchesAllSubTypesWithoutSuffix)
+ {
+ return -1;
+ }
+ else if (mediaType2.MatchesAllSubTypesWithoutSuffix)
+ {
+ return 1;
+ }
}
+ }
- return 0;
+ 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/NameValueHeaderValue.cs b/src/Http/Headers/src/NameValueHeaderValue.cs
index 86988652a0..ef58172196 100644
--- a/src/Http/Headers/src/NameValueHeaderValue.cs
+++ b/src/Http/Headers/src/NameValueHeaderValue.cs
@@ -9,489 +9,488 @@ using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+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.
+/// <summary>
+/// Represents a name/value pair used in various headers as defined in RFC 2616.
+/// </summary>
+public class NameValueHeaderValue
{
- // 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.
+ 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.
+ }
+
/// <summary>
- /// Represents a name/value pair used in various headers as defined in RFC 2616.
+ /// Initializes a new instance of <see cref="NameValueHeaderValue"/>.
/// </summary>
- public class NameValueHeaderValue
+ /// <param name="name">The header name.</param>
+ public NameValueHeaderValue(StringSegment name)
+ : this(name, null)
{
- 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;
+ /// <summary>
+ /// Initializes a new instance of <see cref="NameValueHeaderValue"/>.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="value">The header value.</param>
+ public NameValueHeaderValue(StringSegment name, StringSegment value)
+ {
+ CheckNameValueFormat(name, value);
- private NameValueHeaderValue()
- {
- // Used by the parser to create a new instance of this type.
- }
+ _name = name;
+ _value = value;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="NameValueHeaderValue"/>.
- /// </summary>
- /// <param name="name">The header name.</param>
- public NameValueHeaderValue(StringSegment name)
- : this(name, null)
- {
- }
+ /// <summary>
+ /// Gets the header name.
+ /// </summary>
+ public StringSegment Name
+ {
+ get { return _name; }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="NameValueHeaderValue"/>.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="value">The header value.</param>
- public NameValueHeaderValue(StringSegment name, StringSegment value)
+ /// <summary>
+ /// Gets or sets the header value.
+ /// </summary>
+ public StringSegment Value
+ {
+ get { return _value; }
+ set
{
- CheckNameValueFormat(name, value);
-
- _name = name;
+ HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+ CheckValueFormat(value);
_value = value;
}
+ }
- /// <summary>
- /// Gets the header name.
- /// </summary>
- public StringSegment Name
- {
- get { return _name; }
- }
-
- /// <summary>
- /// Gets or sets the header value.
- /// </summary>
- public StringSegment Value
- {
- get { return _value; }
- set
- {
- HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
- CheckValueFormat(value);
- _value = value;
- }
- }
-
- /// <summary>
- /// Gets a value that determines if this header is read only.
- /// </summary>
- public bool IsReadOnly { get { return _isReadOnly; } }
+ /// <summary>
+ /// Gets a value that determines if this header is read only.
+ /// </summary>
+ 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()
+ /// <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()
{
- return new NameValueHeaderValue()
- {
- _name = _name,
- _value = _value
- };
- }
+ _name = _name,
+ _value = _value
+ };
+ }
- /// <summary>
- /// Provides a copy of this instance while making it immutable.
- /// </summary>
- /// <returns>The readonly <see cref="NameValueHeaderValue"/>.</returns>
- public NameValueHeaderValue CopyAsReadOnly()
+ /// <summary>
+ /// Provides a copy of this instance while making it immutable.
+ /// </summary>
+ /// <returns>The readonly <see cref="NameValueHeaderValue"/>.</returns>
+ public NameValueHeaderValue CopyAsReadOnly()
+ {
+ if (IsReadOnly)
{
- if (IsReadOnly)
- {
- return this;
- }
-
- return new NameValueHeaderValue()
- {
- _name = _name,
- _value = _value,
- _isReadOnly = true
- };
+ return this;
}
- /// <inheritdoc/>
- public override int GetHashCode()
+ return new NameValueHeaderValue()
{
- Contract.Assert(_name != null);
-
- var nameHashCode = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_name);
+ _name = _name,
+ _value = _value,
+ _isReadOnly = true
+ };
+ }
- 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);
- }
+ /// <inheritdoc/>
+ public override int GetHashCode()
+ {
+ Contract.Assert(_name != null);
- return nameHashCode;
- }
+ var nameHashCode = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_name);
- /// <inheritdoc/>
- public override bool Equals(object? obj)
+ if (!StringSegment.IsNullOrEmpty(_value))
{
- var other = obj as NameValueHeaderValue;
-
- if (other == null)
+ // 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 false;
+ return nameHashCode ^ _value.GetHashCode();
}
- if (!_name.Equals(other._name, StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
+ return nameHashCode ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
+ }
- // 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.
+ return nameHashCode;
+ }
- if (StringSegment.IsNullOrEmpty(_value))
- {
- return StringSegment.IsNullOrEmpty(other._value);
- }
+ /// <inheritdoc/>
+ public override bool Equals(object? obj)
+ {
+ var other = obj as NameValueHeaderValue;
- 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));
- }
+ if (other == null)
+ {
+ return false;
}
- /// <summary>
- /// If the value is 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.
- /// </summary>
- /// <returns>An unescaped version of <see cref="Value"/>.</returns>
- public StringSegment GetUnescapedValue()
+ if (!_name.Equals(other._name, StringComparison.OrdinalIgnoreCase))
{
- if (!HeaderUtilities.IsQuoted(_value))
- {
- return _value;
- }
- return HeaderUtilities.UnescapeAsQuotedString(_value);
+ return false;
}
- /// <summary>
- /// Sets <see cref="Value"/> after it has been quoted as defined by <see href="https://tools.ietf.org/html/rfc7230#section-3.2.6">the RFC specification</see>.
- /// </summary>
- /// <param name="value"></param>
- public void SetAndEscapeValue(StringSegment value)
+ // 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))
{
- HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
- if (StringSegment.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length))
- {
- _value = value;
- }
- else
- {
- Value = HeaderUtilities.EscapeAsQuotedString(value);
- }
+ return StringSegment.IsNullOrEmpty(other._value);
}
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="NameValueHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static NameValueHeaderValue Parse(StringSegment input)
+ if (_value[0] == '"')
{
- var index = 0;
- return SingleValueParser.ParseValue(input, ref index)!;
+ // We have a quoted string, so we need to do case-sensitive comparison.
+ return (_value.Equals(other._value, StringComparison.Ordinal));
}
-
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="NameValueHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="NameValueHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out NameValueHeaderValue? parsedValue)
+ else
{
- var index = 0;
- return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ return (_value.Equals(other._value, StringComparison.OrdinalIgnoreCase));
}
+ }
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="NameValueHeaderValue"/> values.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<NameValueHeaderValue> ParseList(IList<string>? input)
+ /// <summary>
+ /// If the value is 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.
+ /// </summary>
+ /// <returns>An unescaped version of <see cref="Value"/>.</returns>
+ public StringSegment GetUnescapedValue()
+ {
+ if (!HeaderUtilities.IsQuoted(_value))
{
- return MultipleValueParser.ParseValues(input);
+ return _value;
}
+ return HeaderUtilities.UnescapeAsQuotedString(_value);
+ }
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="NameValueHeaderValue"/> values using string parsing rules.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<NameValueHeaderValue> ParseStrictList(IList<string>? input)
+ /// <summary>
+ /// Sets <see cref="Value"/> after it has been quoted as defined by <see href="https://tools.ietf.org/html/rfc7230#section-3.2.6">the RFC specification</see>.
+ /// </summary>
+ /// <param name="value"></param>
+ public void SetAndEscapeValue(StringSegment value)
+ {
+ HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+ if (StringSegment.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length))
{
- return MultipleValueParser.ParseStrictValues(input);
+ _value = value;
}
-
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="NameValueHeaderValue"/>.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="NameValueHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseList(IList<string>? input, [NotNullWhen(true)] out IList<NameValueHeaderValue>? parsedValues)
+ else
{
- return MultipleValueParser.TryParseValues(input, out parsedValues);
+ Value = HeaderUtilities.EscapeAsQuotedString(value);
}
+ }
+
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="NameValueHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static NameValueHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return SingleValueParser.ParseValue(input, ref index)!;
+ }
+
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="NameValueHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="NameValueHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out NameValueHeaderValue? parsedValue)
+ {
+ var index = 0;
+ return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ }
+
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="NameValueHeaderValue"/> values.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<NameValueHeaderValue> ParseList(IList<string>? input)
+ {
+ return MultipleValueParser.ParseValues(input);
+ }
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="NameValueHeaderValue"/> using string parsing rules.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseStrictList(IList<string>? input, [NotNullWhen(true)] out IList<NameValueHeaderValue>? parsedValues)
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="NameValueHeaderValue"/> values using string parsing rules.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<NameValueHeaderValue> ParseStrictList(IList<string>? input)
+ {
+ return MultipleValueParser.ParseStrictValues(input);
+ }
+
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="NameValueHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="NameValueHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseList(IList<string>? input, [NotNullWhen(true)] out IList<NameValueHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseValues(input, out parsedValues);
+ }
+
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="NameValueHeaderValue"/> using string parsing rules.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseStrictList(IList<string>? input, [NotNullWhen(true)] out IList<NameValueHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
+ }
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ if (!StringSegment.IsNullOrEmpty(_value))
{
- return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
+ return _name + "=" + _value;
}
+ return _name.ToString();
+ }
- /// <inheritdoc />
- public override string ToString()
+ internal static void ToString(
+ IList<NameValueHeaderValue>? values,
+ char separator,
+ bool leadingSeparator,
+ StringBuilder destination)
+ {
+ Contract.Assert(destination != null);
+
+ if ((values == null) || (values.Count == 0))
{
- if (!StringSegment.IsNullOrEmpty(_value))
- {
- return _name + "=" + _value;
- }
- return _name.ToString();
+ return;
}
- internal static void ToString(
- IList<NameValueHeaderValue>? values,
- char separator,
- bool leadingSeparator,
- StringBuilder destination)
+ for (var i = 0; i < values.Count; i++)
{
- Contract.Assert(destination != null);
-
- if ((values == null) || (values.Count == 0))
+ if (leadingSeparator || (destination.Length > 0))
{
- return;
+ destination.Append(separator);
+ destination.Append(' ');
}
-
- for (var i = 0; i < values.Count; i++)
+ destination.Append(values[i].Name.AsSpan());
+ if (!StringSegment.IsNullOrEmpty(values[i].Value))
{
- if (leadingSeparator || (destination.Length > 0))
- {
- destination.Append(separator);
- destination.Append(' ');
- }
- destination.Append(values[i].Name.AsSpan());
- if (!StringSegment.IsNullOrEmpty(values[i].Value))
- {
- destination.Append('=');
- destination.Append(values[i].Value.AsSpan());
- }
+ destination.Append('=');
+ destination.Append(values[i].Value.AsSpan());
}
}
+ }
- internal static string? ToString(IList<NameValueHeaderValue>? values, char separator, bool leadingSeparator)
+ internal static string? ToString(IList<NameValueHeaderValue>? values, char separator, bool leadingSeparator)
+ {
+ if ((values == null) || (values.Count == 0))
{
- if ((values == null) || (values.Count == 0))
- {
- return null;
- }
+ return null;
+ }
- var sb = new StringBuilder();
+ var sb = new StringBuilder();
- ToString(values, separator, leadingSeparator, sb);
+ ToString(values, separator, leadingSeparator, sb);
- return sb.ToString();
- }
+ return sb.ToString();
+ }
- internal static int GetHashCode(IList<NameValueHeaderValue>? values)
+ internal static int GetHashCode(IList<NameValueHeaderValue>? values)
+ {
+ if ((values == null) || (values.Count == 0))
{
- 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;
+ return 0;
}
- private static int GetNameValueLength(StringSegment input, int startIndex, out NameValueHeaderValue? parsedValue)
+ var result = 0;
+ for (var i = 0; i < values.Count; i++)
{
- Contract.Requires(startIndex >= 0);
-
- parsedValue = null;
+ result = result ^ values[i].GetHashCode();
+ }
+ return result;
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ private static int GetNameValueLength(StringSegment input, int startIndex, out NameValueHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- // Parse the name, i.e. <name> in name/value string "<name>=<value>". Caller must remove
- // leading whitespaces.
- var nameLength = HttpRuleParser.GetTokenLength(input, startIndex);
+ parsedValue = null;
- if (nameLength == 0)
- {
- return 0;
- }
-
- var name = input.Subsegment(startIndex, nameLength);
- var current = startIndex + nameLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
- // 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;
- }
+ // Parse the name, i.e. <name> in name/value string "<name>=<value>". Caller must remove
+ // leading whitespaces.
+ var nameLength = HttpRuleParser.GetTokenLength(input, startIndex);
- current++; // skip delimiter.
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ if (nameLength == 0)
+ {
+ return 0;
+ }
- // Parse the value, i.e. <value> in name/value string "<name>=<value>"
- int valueLength = GetValueLength(input, current);
+ var name = input.Subsegment(startIndex, nameLength);
+ var current = startIndex + nameLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(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.
+ // 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;
- 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(startIndex >= 0);
+ current++; // skip delimiter.
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
- {
- return 0;
- }
+ // Parse the value, i.e. <value> in name/value string "<name>=<value>"
+ int valueLength = GetValueLength(input, current);
- var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
- while (true)
- {
- var nameValueLength = GetNameValueLength(input, current, out var 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);
- }
+ // 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(startIndex >= 0);
+
+ if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
+ {
+ return 0;
}
- /// <summary>
- /// Finds a <see cref="NameValueHeaderValue"/> with the specified <paramref name="name"/>.
- /// </summary>
- /// <param name="values">The collection to search.</param>
- /// <param name="name">The name to find.</param>
- /// <returns>The <see cref="NameValueHeaderValue" /> if found, otherwise <see langword="null" />.</returns>
- public static NameValueHeaderValue? Find(IList<NameValueHeaderValue>? values, StringSegment name)
+ var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+ while (true)
{
- Contract.Requires(name.Length > 0);
+ var nameValueLength = GetNameValueLength(input, current, out var parameter);
- if ((values == null) || (values.Count == 0))
+ if (nameValueLength == 0)
{
- return null;
+ // There may be a trailing ';'
+ return current - startIndex;
}
- for (var i = 0; i < values.Count; i++)
+ nameValueCollection!.Add(parameter!);
+ current = current + nameValueLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+ if ((current == input.Length) || (input[current] != delimiter))
{
- var value = values[i];
- if (value.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
- {
- return value;
- }
+ // We're done and we have at least one valid name/value pair.
+ return current - startIndex;
}
- return null;
+
+ // input[current] is 'delimiter'. Skip the delimiter and whitespaces and try to parse again.
+ current++; // skip delimiter.
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
}
+ }
- internal static int GetValueLength(StringSegment input, int startIndex)
- {
- if (startIndex >= input.Length)
- {
- return 0;
- }
+ /// <summary>
+ /// Finds a <see cref="NameValueHeaderValue"/> with the specified <paramref name="name"/>.
+ /// </summary>
+ /// <param name="values">The collection to search.</param>
+ /// <param name="name">The name to find.</param>
+ /// <returns>The <see cref="NameValueHeaderValue" /> if found, otherwise <see langword="null" />.</returns>
+ public static NameValueHeaderValue? Find(IList<NameValueHeaderValue>? values, StringSegment name)
+ {
+ Contract.Requires(name.Length > 0);
- var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
+ if ((values == null) || (values.Count == 0))
+ {
+ return null;
+ }
- if (valueLength == 0)
+ for (var i = 0; i < values.Count; i++)
+ {
+ var value = values[i];
+ if (value.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
{
- // 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 value;
}
- return valueLength;
}
+ return null;
+ }
- private static void CheckNameValueFormat(StringSegment name, StringSegment value)
+ internal static int GetValueLength(StringSegment input, int startIndex)
+ {
+ if (startIndex >= input.Length)
{
- HeaderUtilities.CheckValidToken(name, nameof(name));
- CheckValueFormat(value);
+ return 0;
}
- private static void CheckValueFormat(StringSegment value)
+ var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+ if (valueLength == 0)
{
- // Either value is null/empty or a valid token/quoted string
- if (!(StringSegment.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length)))
+ // 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)
{
- throw new FormatException(string.Format(CultureInfo.InvariantCulture, "The header value is invalid: '{0}'", value));
+ // We have an invalid value. Reset the name and return.
+ return 0;
}
}
+ return valueLength;
+ }
- private static NameValueHeaderValue CreateNameValue()
+ 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)))
{
- return new NameValueHeaderValue();
+ 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
index a8edc0498c..ad35d39898 100644
--- a/src/Http/Headers/src/ObjectCollection.cs
+++ b/src/Http/Headers/src/ObjectCollection.cs
@@ -5,77 +5,76 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
-namespace Microsoft.Net.Http.Headers
+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 sealed class ObjectCollection<T> : Collection<T>
{
- // 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 sealed class ObjectCollection<T> : Collection<T>
- {
- internal static readonly Action<T> DefaultValidator = CheckNotNull;
- internal static readonly ObjectCollection<T> EmptyReadOnlyCollection
- = new ObjectCollection<T>(DefaultValidator, isReadOnly: true);
+ internal static readonly Action<T> DefaultValidator = CheckNotNull;
+ internal static readonly ObjectCollection<T> EmptyReadOnlyCollection
+ = new ObjectCollection<T>(DefaultValidator, isReadOnly: true);
- private readonly Action<T> _validator;
+ 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)
+ // 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);
}
-
- public ObjectCollection(Action<T> validator, bool isReadOnly = false)
- : base(CreateInnerList(isReadOnly))
+ else
{
- _validator = validator;
+ return list;
}
+ }
- public ObjectCollection(IEnumerable<T> other, bool isReadOnly = false)
- : base(CreateInnerList(isReadOnly, other))
- {
- _validator = DefaultValidator;
- foreach (T item in Items)
- {
- _validator(item);
- }
- }
+ public ObjectCollection()
+ : this(DefaultValidator)
+ {
+ }
- public bool IsReadOnly => ((ICollection<T>)this).IsReadOnly;
+ public ObjectCollection(Action<T> validator, bool isReadOnly = false)
+ : base(CreateInnerList(isReadOnly))
+ {
+ _validator = validator;
+ }
- protected override void InsertItem(int index, T item)
+ public ObjectCollection(IEnumerable<T> other, bool isReadOnly = false)
+ : base(CreateInnerList(isReadOnly, other))
+ {
+ _validator = DefaultValidator;
+ foreach (T item in Items)
{
_validator(item);
- base.InsertItem(index, item);
}
+ }
- protected override void SetItem(int index, T item)
- {
- _validator(item);
- base.SetItem(index, item);
- }
+ public bool IsReadOnly => ((ICollection<T>)this).IsReadOnly;
- private static void CheckNotNull(T item)
+ protected override void InsertItem(int index, T item)
+ {
+ _validator(item);
+ base.InsertItem(index, item);
+ }
+
+ 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)
{
- // null values cannot be added to the collection.
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
+ throw new ArgumentNullException(nameof(item));
}
}
}
diff --git a/src/Http/Headers/src/RangeConditionHeaderValue.cs b/src/Http/Headers/src/RangeConditionHeaderValue.cs
index a26aef8873..f4df007298 100644
--- a/src/Http/Headers/src/RangeConditionHeaderValue.cs
+++ b/src/Http/Headers/src/RangeConditionHeaderValue.cs
@@ -6,198 +6,197 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents an <c>If-Range</c> header value which can either be a date/time or an entity-tag value.
+/// </summary>
+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.
+ }
+
/// <summary>
- /// Represents an <c>If-Range</c> header value which can either be a date/time or an entity-tag value.
+ /// Initializes a new instance of <see cref="RangeConditionHeaderValue"/>.
/// </summary>
- public class RangeConditionHeaderValue
+ /// <param name="lastModified">A date value used to initialize the new instance.</param>
+ public RangeConditionHeaderValue(DateTimeOffset lastModified)
{
- private static readonly HttpHeaderParser<RangeConditionHeaderValue> Parser
- = new GenericHeaderParser<RangeConditionHeaderValue>(false, GetRangeConditionLength);
-
- private DateTimeOffset? _lastModified;
- private EntityTagHeaderValue? _entityTag;
+ _lastModified = lastModified;
+ }
- private RangeConditionHeaderValue()
+ /// <summary>
+ /// Initializes a new instance of <see cref="RangeConditionHeaderValue"/>.
+ /// </summary>
+ /// <param name="entityTag">An entity tag uniquely representing the requested resource.</param>
+ public RangeConditionHeaderValue(EntityTagHeaderValue entityTag)
+ {
+ if (entityTag == null)
{
- // Used by the parser to create a new instance of this type.
+ throw new ArgumentNullException(nameof(entityTag));
}
- /// <summary>
- /// Initializes a new instance of <see cref="RangeConditionHeaderValue"/>.
- /// </summary>
- /// <param name="lastModified">A date value used to initialize the new instance.</param>
- public RangeConditionHeaderValue(DateTimeOffset lastModified)
- {
- _lastModified = lastModified;
- }
+ _entityTag = entityTag;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="RangeConditionHeaderValue"/>.
- /// </summary>
- /// <param name="entityTag">An entity tag uniquely representing the requested resource.</param>
- public RangeConditionHeaderValue(EntityTagHeaderValue entityTag)
- {
- if (entityTag == null)
- {
- throw new ArgumentNullException(nameof(entityTag));
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="RangeConditionHeaderValue"/>.
+ /// </summary>
+ /// <param name="entityTag">An entity tag uniquely representing the requested resource.</param>
+ public RangeConditionHeaderValue(string? entityTag)
+ : this(new EntityTagHeaderValue(entityTag))
+ {
+ }
- _entityTag = entityTag;
- }
+ /// <summary>
+ /// Gets the LastModified date from header.
+ /// </summary>
+ public DateTimeOffset? LastModified
+ {
+ get { return _lastModified; }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="RangeConditionHeaderValue"/>.
- /// </summary>
- /// <param name="entityTag">An entity tag uniquely representing the requested resource.</param>
- public RangeConditionHeaderValue(string? entityTag)
- : this(new EntityTagHeaderValue(entityTag))
- {
- }
+ /// <summary>
+ /// Gets the <see cref="EntityTagHeaderValue"/> from header.
+ /// </summary>
+ public EntityTagHeaderValue? EntityTag
+ {
+ get { return _entityTag; }
+ }
- /// <summary>
- /// Gets the LastModified date from header.
- /// </summary>
- public DateTimeOffset? LastModified
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ if (_entityTag == null)
{
- get { return _lastModified; }
+ return HeaderUtilities.FormatDate(_lastModified.GetValueOrDefault());
}
+ return _entityTag.ToString();
+ }
+
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as RangeConditionHeaderValue;
- /// <summary>
- /// Gets the <see cref="EntityTagHeaderValue"/> from header.
- /// </summary>
- public EntityTagHeaderValue? EntityTag
+ if (other == null)
{
- get { return _entityTag; }
+ return false;
}
- /// <inheritdoc />
- public override string ToString()
+ if (_entityTag == null)
{
- if (_entityTag == null)
- {
- return HeaderUtilities.FormatDate(_lastModified.GetValueOrDefault());
- }
- return _entityTag.ToString();
+ return (other._lastModified != null) && (_lastModified.GetValueOrDefault() == other._lastModified.GetValueOrDefault());
}
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ return _entityTag.Equals(other._entityTag);
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ if (_entityTag == null)
{
- var other = obj as RangeConditionHeaderValue;
+ return _lastModified.GetValueOrDefault().GetHashCode();
+ }
- if (other == null)
- {
- return false;
- }
+ return _entityTag.GetHashCode();
+ }
- if (_entityTag == null)
- {
- return (other._lastModified != null) && (_lastModified.GetValueOrDefault() == other._lastModified.GetValueOrDefault());
- }
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="RangeConditionHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static RangeConditionHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return Parser.ParseValue(input, ref index)!;
+ }
- return _entityTag.Equals(other._entityTag);
- }
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="RangeConditionHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="RangeConditionHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out RangeConditionHeaderValue? parsedValue)
+ {
+ var index = 0;
+ return Parser.TryParseValue(input, ref index, out parsedValue!);
+ }
- /// <inheritdoc />
- public override int GetHashCode()
- {
- if (_entityTag == null)
- {
- return _lastModified.GetValueOrDefault().GetHashCode();
- }
+ private static int GetRangeConditionLength(StringSegment input, int startIndex, out RangeConditionHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- return _entityTag.GetHashCode();
- }
+ parsedValue = null;
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="RangeConditionHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static RangeConditionHeaderValue Parse(StringSegment input)
+ // Make sure we have at least 2 characters
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
{
- var index = 0;
- return Parser.ParseValue(input, ref index)!;
+ return 0;
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="RangeConditionHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="RangeConditionHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out RangeConditionHeaderValue? parsedValue)
- {
- var index = 0;
- return Parser.TryParseValue(input, ref index, out parsedValue!);
- }
+ var current = startIndex;
- private static int GetRangeConditionLength(StringSegment input, int startIndex, out RangeConditionHeaderValue? parsedValue)
- {
- Contract.Requires(startIndex >= 0);
+ // Caller must remove leading whitespaces.
+ DateTimeOffset date = DateTimeOffset.MinValue;
+ EntityTagHeaderValue? entityTag = null;
- parsedValue = 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];
- // Make sure we have at least 2 characters
- if (StringSegment.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
+ 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;
}
- var current = startIndex;
-
- // Caller must remove leading whitespaces.
- DateTimeOffset date = DateTimeOffset.MinValue;
- EntityTagHeaderValue? entityTag = null;
+ current = current + entityTagLength;
- // 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 == '/')))
+ // RangeConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after an
+ // entity tag.
+ if (current != input.Length)
{
- // 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;
- }
+ return 0;
}
- else
+ }
+ else
+ {
+ if (!HttpRuleParser.TryStringToDate(input.Subsegment(current), out date))
{
- 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;
+ return 0;
}
- parsedValue = new RangeConditionHeaderValue();
- if (entityTag == null)
- {
- parsedValue._lastModified = date;
- }
- else
- {
- parsedValue._entityTag = entityTag;
- }
+ // If we got a valid date, then the parser consumed the whole string (incl. trailing whitespaces).
+ current = input.Length;
+ }
- return current - startIndex;
+ parsedValue = new RangeConditionHeaderValue();
+ if (entityTag == null)
+ {
+ parsedValue._lastModified = date;
}
+ else
+ {
+ parsedValue._entityTag = entityTag;
+ }
+
+ return current - startIndex;
}
}
diff --git a/src/Http/Headers/src/RangeHeaderValue.cs b/src/Http/Headers/src/RangeHeaderValue.cs
index 6270722f46..4cfd368326 100644
--- a/src/Http/Headers/src/RangeHeaderValue.cs
+++ b/src/Http/Headers/src/RangeHeaderValue.cs
@@ -8,193 +8,192 @@ using System.Diagnostics.Contracts;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents a <c>Range</c> header value.
+/// <para>
+/// The <see cref="RangeHeaderValue"/> class provides support for the Range header as defined in
+/// <see href="https://tools.ietf.org/html/rfc2616">RFC 2616</see>.
+/// </para>
+/// </summary>
+public class RangeHeaderValue
{
+ private static readonly HttpHeaderParser<RangeHeaderValue> Parser
+ = new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength);
+
+ private StringSegment _unit;
+ private ICollection<RangeItemHeaderValue>? _ranges;
+
/// <summary>
- /// Represents a <c>Range</c> header value.
- /// <para>
- /// The <see cref="RangeHeaderValue"/> class provides support for the Range header as defined in
- /// <see href="https://tools.ietf.org/html/rfc2616">RFC 2616</see>.
- /// </para>
+ /// Initializes a new instance of <see cref="RangeHeaderValue"/>.
/// </summary>
- public class RangeHeaderValue
+ public RangeHeaderValue()
{
- private static readonly HttpHeaderParser<RangeHeaderValue> Parser
- = new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength);
-
- private StringSegment _unit;
- private ICollection<RangeItemHeaderValue>? _ranges;
+ _unit = HeaderUtilities.BytesUnit;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="RangeHeaderValue"/>.
- /// </summary>
- public RangeHeaderValue()
- {
- _unit = HeaderUtilities.BytesUnit;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="RangeHeaderValue"/>.
+ /// </summary>
+ /// <param name="from">The position at which to start sending data.</param>
+ /// <param name="to">The position at which to stop sending data.</param>
+ public RangeHeaderValue(long? from, long? to)
+ {
+ // convenience ctor: "Range: bytes=from-to"
+ _unit = HeaderUtilities.BytesUnit;
+ Ranges.Add(new RangeItemHeaderValue(from, to));
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="RangeHeaderValue"/>.
- /// </summary>
- /// <param name="from">The position at which to start sending data.</param>
- /// <param name="to">The position at which to stop sending data.</param>
- public RangeHeaderValue(long? from, long? to)
+ /// <summary>
+ /// Gets or sets the unit from the header.
+ /// </summary>
+ /// <value>Defaults to <c>bytes</c>.</value>
+ public StringSegment Unit
+ {
+ get { return _unit; }
+ set
{
- // convenience ctor: "Range: bytes=from-to"
- _unit = HeaderUtilities.BytesUnit;
- Ranges.Add(new RangeItemHeaderValue(from, to));
+ HeaderUtilities.CheckValidToken(value, nameof(value));
+ _unit = value;
}
+ }
- /// <summary>
- /// Gets or sets the unit from the header.
- /// </summary>
- /// <value>Defaults to <c>bytes</c>.</value>
- public StringSegment Unit
+ /// <summary>
+ /// Gets the ranges specified in the header.
+ /// </summary>
+ public ICollection<RangeItemHeaderValue> Ranges
+ {
+ get
{
- get { return _unit; }
- set
+ if (_ranges == null)
{
- HeaderUtilities.CheckValidToken(value, nameof(value));
- _unit = value;
+ _ranges = new ObjectCollection<RangeItemHeaderValue>();
}
+ return _ranges;
}
+ }
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.Append(_unit.AsSpan());
+ sb.Append('=');
- /// <summary>
- /// Gets the ranges specified in the header.
- /// </summary>
- public ICollection<RangeItemHeaderValue> Ranges
+ var first = true;
+ foreach (var range in Ranges)
{
- get
+ if (first)
{
- if (_ranges == null)
- {
- _ranges = new ObjectCollection<RangeItemHeaderValue>();
- }
- return _ranges;
+ first = false;
}
- }
-
- /// <inheritdoc />
- public override string ToString()
- {
- var sb = new StringBuilder();
- sb.Append(_unit.AsSpan());
- sb.Append('=');
-
- var first = true;
- foreach (var range in Ranges)
+ else
{
- if (first)
- {
- first = false;
- }
- else
- {
- sb.Append(", ");
- }
-
- sb.Append(range.From);
- sb.Append('-');
- sb.Append(range.To);
+ sb.Append(", ");
}
- return sb.ToString();
+ sb.Append(range.From);
+ sb.Append('-');
+ sb.Append(range.To);
}
- /// <inheritdoc />
- public override bool Equals(object? obj)
- {
- var other = obj as RangeHeaderValue;
-
- if (other == null)
- {
- return false;
- }
+ return sb.ToString();
+ }
- return StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase) &&
- HeaderUtilities.AreEqualCollections(Ranges, other.Ranges);
- }
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as RangeHeaderValue;
- /// <inheritdoc />
- public override int GetHashCode()
+ if (other == null)
{
- var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
+ return false;
+ }
- foreach (var range in Ranges)
- {
- result = result ^ range.GetHashCode();
- }
+ return StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase) &&
+ HeaderUtilities.AreEqualCollections(Ranges, other.Ranges);
+ }
- return result;
- }
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="RangeHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static RangeHeaderValue Parse(StringSegment input)
+ foreach (var range in Ranges)
{
- var index = 0;
- return Parser.ParseValue(input, ref index)!;
+ result = result ^ range.GetHashCode();
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="RangeHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="RangeHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out RangeHeaderValue parsedValue)
- {
- var index = 0;
- return Parser.TryParseValue(input, ref index, out parsedValue!);
- }
+ return result;
+ }
- private static int GetRangeLength(StringSegment input, int startIndex, out RangeHeaderValue? parsedValue)
- {
- Contract.Requires(startIndex >= 0);
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="RangeHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static RangeHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return Parser.ParseValue(input, ref index)!;
+ }
- parsedValue = null;
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="RangeHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="RangeHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out RangeHeaderValue parsedValue)
+ {
+ var index = 0;
+ return Parser.TryParseValue(input, ref index, out parsedValue!);
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ private static int GetRangeLength(StringSegment input, int startIndex, out RangeHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- // Parse the unit string: <unit> in '<unit>=<from1>-<to1>, <from2>-<to2>'
- var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
+ parsedValue = null;
- if (unitLength == 0)
- {
- return 0;
- }
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
- RangeHeaderValue result = new RangeHeaderValue();
- result._unit = input.Subsegment(startIndex, unitLength);
- var current = startIndex + unitLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ // Parse the unit string: <unit> in '<unit>=<from1>-<to1>, <from2>-<to2>'
+ var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
- if ((current == input.Length) || (input[current] != '='))
- {
- return 0;
- }
+ if (unitLength == 0)
+ {
+ return 0;
+ }
- current++; // skip '=' separator
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ RangeHeaderValue result = new RangeHeaderValue();
+ result._unit = input.Subsegment(startIndex, unitLength);
+ var current = startIndex + unitLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- var rangesLength = RangeItemHeaderValue.GetRangeItemListLength(input, current, result.Ranges);
+ if ((current == input.Length) || (input[current] != '='))
+ {
+ return 0;
+ }
- if (rangesLength == 0)
- {
- return 0;
- }
+ current++; // skip '=' separator
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- current = current + rangesLength;
- Contract.Assert(current == input.Length, "GetRangeItemListLength() should consume the whole string or fail.");
+ var rangesLength = RangeItemHeaderValue.GetRangeItemListLength(input, current, result.Ranges);
- parsedValue = result;
- return current - startIndex;
+ 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
index 7f5f1da441..cd48094c4f 100644
--- a/src/Http/Headers/src/RangeItemHeaderValue.cs
+++ b/src/Http/Headers/src/RangeItemHeaderValue.cs
@@ -7,235 +7,234 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents a byte range in a Range header value.
+/// <para>
+/// The <see cref="RangeItemHeaderValue"/> class provides support for a byte range in a <c>Range</c> as defined
+/// in <see href="https://tools.ietf.org/html/rfc2616">RFC 2616</see>.
+/// </para>
+/// </summary>
+public class RangeItemHeaderValue
{
+ private readonly long? _from;
+ private readonly long? _to;
+
/// <summary>
- /// Represents a byte range in a Range header value.
- /// <para>
- /// The <see cref="RangeItemHeaderValue"/> class provides support for a byte range in a <c>Range</c> as defined
- /// in <see href="https://tools.ietf.org/html/rfc2616">RFC 2616</see>.
- /// </para>
+ /// Initializes a new instance of the <see cref="RangeItemHeaderValue"/> class.
/// </summary>
- public class RangeItemHeaderValue
+ /// <param name="from">The position at which to start sending data.</param>
+ /// <param name="to">The position at which to stop sending data.</param>
+ public RangeItemHeaderValue(long? from, long? to)
{
- private readonly long? _from;
- private readonly long? _to;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="RangeItemHeaderValue"/> class.
- /// </summary>
- /// <param name="from">The position at which to start sending data.</param>
- /// <param name="to">The position at which to stop sending data.</param>
- public RangeItemHeaderValue(long? from, long? to)
+ if (!from.HasValue && !to.HasValue)
{
- if (!from.HasValue && !to.HasValue)
- {
- throw new ArgumentException("Invalid header range.");
- }
- if (from.HasValue && (from.GetValueOrDefault() < 0))
- {
- throw new ArgumentOutOfRangeException(nameof(from));
- }
- if (to.HasValue && (to.GetValueOrDefault() < 0))
- {
- throw new ArgumentOutOfRangeException(nameof(to));
- }
- if (from.HasValue && to.HasValue && (from.GetValueOrDefault() > to.GetValueOrDefault()))
- {
- throw new ArgumentOutOfRangeException(nameof(from));
- }
-
- _from = from;
- _to = to;
+ throw new ArgumentException("Invalid header range.");
}
-
- /// <summary>
- /// Gets the position at which to start sending data.
- /// </summary>
- public long? From
+ if (from.HasValue && (from.GetValueOrDefault() < 0))
{
- get { return _from; }
+ throw new ArgumentOutOfRangeException(nameof(from));
}
+ if (to.HasValue && (to.GetValueOrDefault() < 0))
+ {
+ throw new ArgumentOutOfRangeException(nameof(to));
+ }
+ if (from.HasValue && to.HasValue && (from.GetValueOrDefault() > to.GetValueOrDefault()))
+ {
+ throw new ArgumentOutOfRangeException(nameof(from));
+ }
+
+ _from = from;
+ _to = to;
+ }
+
+ /// <summary>
+ /// Gets the position at which to start sending data.
+ /// </summary>
+ public long? From
+ {
+ get { return _from; }
+ }
+
+ /// <summary>
+ /// Gets the position at which to stop sending data.
+ /// </summary>
+ public long? To
+ {
+ get { return _to; }
+ }
- /// <summary>
- /// Gets the position at which to stop sending data.
- /// </summary>
- public long? To
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ if (!_from.HasValue)
{
- get { return _to; }
+ return "-" + _to.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo);
}
+ else if (!_to.HasValue)
+ {
+ return _from.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo) + "-";
+ }
+ return _from.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo) + "-" +
+ _to.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo);
+ }
- /// <inheritdoc />
- public override string ToString()
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ return obj is RangeItemHeaderValue other && ((_from == other._from) && (_to == other._to));
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ if (!_from.HasValue)
{
- if (!_from.HasValue)
- {
- return "-" + _to.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo);
- }
- else if (!_to.HasValue)
- {
- return _from.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo) + "-";
- }
- return _from.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo) + "-" +
- _to.GetValueOrDefault().ToString(NumberFormatInfo.InvariantInfo);
+ return _to.GetValueOrDefault().GetHashCode();
+ }
+ else if (!_to.HasValue)
+ {
+ return _from.GetValueOrDefault().GetHashCode();
}
+ return _from.GetValueOrDefault().GetHashCode() ^ _to.GetValueOrDefault().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(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.");
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
{
- return obj is RangeItemHeaderValue other && ((_from == other._from) && (_to == other._to));
+ return 0;
}
- /// <inheritdoc />
- public override int GetHashCode()
+ // Empty segments are allowed, so skip all delimiter-only segments (e.g. ", ,").
+ var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, startIndex, true, out var separatorFound);
+ // It's OK if we didn't find leading separator characters. Ignore 'separatorFound'.
+
+ if (current == input.Length)
{
- if (!_from.HasValue)
- {
- return _to.GetValueOrDefault().GetHashCode();
- }
- else if (!_to.HasValue)
- {
- return _from.GetValueOrDefault().GetHashCode();
- }
- return _from.GetValueOrDefault().GetHashCode() ^ _to.GetValueOrDefault().GetHashCode();
+ return 0;
}
- // 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)
+ while (true)
{
- 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.");
+ var rangeLength = GetRangeItemLength(input, current, out var range);
- if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
+ if (rangeLength == 0)
{
return 0;
}
- // Empty segments are allowed, so skip all delimiter-only segments (e.g. ", ,").
- var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, startIndex, true, out var separatorFound);
- // It's OK if we didn't find leading separator characters. Ignore 'separatorFound'.
+ rangeCollection!.Add(range!);
- if (current == input.Length)
+ 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;
}
- while (true)
+ if (current == input.Length)
{
- var rangeLength = GetRangeItemLength(input, current, out var range);
-
- if (rangeLength == 0)
- {
- return 0;
- }
+ return current - startIndex;
+ }
+ }
+ }
- rangeCollection!.Add(range!);
+ internal static int GetRangeItemLength(StringSegment input, int startIndex, out RangeItemHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- current = current + rangeLength;
- current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, current, true, out separatorFound);
+ // This parser parses number ranges: e.g. '1-2', '1-', '-2'.
- // 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;
- }
+ parsedValue = null;
- if (current == input.Length)
- {
- return current - startIndex;
- }
- }
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
}
- internal static int GetRangeItemLength(StringSegment input, int startIndex, out RangeItemHeaderValue? parsedValue)
- {
- Contract.Requires(startIndex >= 0);
+ // Caller must remove leading whitespaces. If not, we'll return 0.
+ var current = startIndex;
- // This parser parses number ranges: e.g. '1-2', '1-', '-2'.
+ // Try parse the first value of a value pair.
+ var fromStartIndex = current;
+ var fromLength = HttpRuleParser.GetNumberLength(input, current, false);
- parsedValue = null;
+ if (fromLength > HttpRuleParser.MaxInt64Digits)
+ {
+ return 0;
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ current = current + fromLength;
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- // Caller must remove leading whitespaces. If not, we'll return 0.
- var current = startIndex;
+ // 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 0;
+ }
- // Try parse the first value of a value pair.
- var fromStartIndex = current;
- var fromLength = HttpRuleParser.GetNumberLength(input, current, false);
+ current++; // skip the '-' character
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- if (fromLength > HttpRuleParser.MaxInt64Digits)
- {
- return 0;
- }
+ var toStartIndex = current;
+ var toLength = 0;
- current = current + fromLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ // 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);
- // After the first value, the '-' character must follow.
- if ((current == input.Length) || (input[current] != '-'))
+ if (toLength > HttpRuleParser.MaxInt64Digits)
{
- // We need a '-' character otherwise this can't be a valid range.
return 0;
}
- current++; // skip the '-' character
+ current = current + toLength;
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;
- }
+ if ((fromLength == 0) && (toLength == 0))
+ {
+ return 0; // At least one value must be provided in order to be a valid range.
+ }
- // Try convert second value to int64
- long to = 0;
- if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
- {
- return 0;
- }
+ // Try convert first value to int64
+ long from = 0;
+ if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(fromStartIndex, fromLength), out from))
+ {
+ return 0;
+ }
- // 'from' must not be greater than 'to'
- if ((fromLength > 0) && (toLength > 0) && (from > to))
- {
- return 0;
- }
+ // Try convert second value to int64
+ long to = 0;
+ if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
+ {
+ return 0;
+ }
- parsedValue = new RangeItemHeaderValue((fromLength == 0 ? (long?)null : (long?)from),
- (toLength == 0 ? (long?)null : (long?)to));
- return current - startIndex;
+ // '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
index 8b5904880d..3e3ce2675b 100644
--- a/src/Http/Headers/src/SameSiteMode.cs
+++ b/src/Http/Headers/src/SameSiteMode.cs
@@ -1,22 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Indicates if the client should include a cookie on "same-site" or "cross-site" requests.
+/// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
+/// </summary>
+// This mirrors Microsoft.AspNetCore.Http.SameSiteMode
+public enum SameSiteMode
{
- /// <summary>
- /// Indicates if the client should include a cookie on "same-site" or "cross-site" requests.
- /// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
- /// </summary>
- // This mirrors Microsoft.AspNetCore.Http.SameSiteMode
- public enum SameSiteMode
- {
- /// <summary>No SameSite field will be set, the client should follow its default cookie policy.</summary>
- Unspecified = -1,
- /// <summary>Indicates the client should disable same-site restrictions.</summary>
- None = 0,
- /// <summary>Indicates the client should send the cookie with "same-site" requests, and with "cross-site" top-level navigations.</summary>
- Lax,
- /// <summary>Indicates the client should only send the cookie with "same-site" requests.</summary>
- Strict
- }
+ /// <summary>No SameSite field will be set, the client should follow its default cookie policy.</summary>
+ Unspecified = -1,
+ /// <summary>Indicates the client should disable same-site restrictions.</summary>
+ None = 0,
+ /// <summary>Indicates the client should send the cookie with "same-site" requests, and with "cross-site" top-level navigations.</summary>
+ Lax,
+ /// <summary>Indicates the client should only send the cookie with "same-site" requests.</summary>
+ Strict
}
diff --git a/src/Http/Headers/src/SetCookieHeaderValue.cs b/src/Http/Headers/src/SetCookieHeaderValue.cs
index cd5b424203..a03593ec8f 100644
--- a/src/Http/Headers/src/SetCookieHeaderValue.cs
+++ b/src/Http/Headers/src/SetCookieHeaderValue.cs
@@ -10,722 +10,721 @@ using System.Linq;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// Represents the <c>Set-Cookie</c> header.
+/// <para>
+/// See http://tools.ietf.org/html/rfc6265 for the Set-Cookie header specification.
+/// </para>
+/// </summary>
+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 SameSiteNoneToken = SameSiteMode.None.ToString().ToLowerInvariant();
+ private static readonly string SameSiteLaxToken = SameSiteMode.Lax.ToString().ToLowerInvariant();
+ private static readonly string SameSiteStrictToken = SameSiteMode.Strict.ToString().ToLowerInvariant();
+
+ private const string HttpOnlyToken = "httponly";
+ private const string SeparatorToken = "; ";
+ private const string EqualsToken = "=";
+ private const int ExpiresDateLength = 29;
+ private const string ExpiresDateFormat = "r";
+
+ 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.
+ }
+
/// <summary>
- /// Represents the <c>Set-Cookie</c> header.
- /// <para>
- /// See http://tools.ietf.org/html/rfc6265 for the Set-Cookie header specification.
- /// </para>
+ /// Initializes a new instance of <see cref="SetCookieHeaderValue"/>.
/// </summary>
- public class SetCookieHeaderValue
+ /// <param name="name">The cookie name.</param>
+ public SetCookieHeaderValue(StringSegment name)
+ : this(name, StringSegment.Empty)
{
- 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 SameSiteNoneToken = SameSiteMode.None.ToString().ToLowerInvariant();
- private static readonly string SameSiteLaxToken = SameSiteMode.Lax.ToString().ToLowerInvariant();
- private static readonly string SameSiteStrictToken = SameSiteMode.Strict.ToString().ToLowerInvariant();
-
- private const string HttpOnlyToken = "httponly";
- private const string SeparatorToken = "; ";
- private const string EqualsToken = "=";
- private const int ExpiresDateLength = 29;
- private const string ExpiresDateFormat = "r";
-
- 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()
+ /// <summary>
+ /// Initializes a new instance of <see cref="SetCookieHeaderValue"/>.
+ /// </summary>
+ /// <param name="name">The cookie name.</param>
+ /// <param name="value">The cookie value.</param>
+ public SetCookieHeaderValue(StringSegment name, StringSegment value)
+ {
+ if (name == null)
{
- // Used by the parser to create a new instance of this type.
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Initializes a new instance of <see cref="SetCookieHeaderValue"/>.
- /// </summary>
- /// <param name="name">The cookie name.</param>
- public SetCookieHeaderValue(StringSegment name)
- : this(name, StringSegment.Empty)
+ if (value == null)
{
+ throw new ArgumentNullException(nameof(value));
}
- /// <summary>
- /// Initializes a new instance of <see cref="SetCookieHeaderValue"/>.
- /// </summary>
- /// <param name="name">The cookie name.</param>
- /// <param name="value">The cookie value.</param>
- 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;
- }
+ Name = name;
+ Value = value;
+ }
- /// <summary>
- /// Gets or sets the cookie name.
- /// </summary>
- public StringSegment Name
+ /// <summary>
+ /// Gets or sets the cookie name.
+ /// </summary>
+ public StringSegment Name
+ {
+ get { return _name; }
+ set
{
- get { return _name; }
- set
- {
- CookieHeaderValue.CheckNameFormat(value, nameof(value));
- _name = value;
- }
+ CookieHeaderValue.CheckNameFormat(value, nameof(value));
+ _name = value;
}
+ }
- /// <summary>
- /// Gets or sets the cookie value.
- /// </summary>
- public StringSegment Value
+ /// <summary>
+ /// Gets or sets the cookie value.
+ /// </summary>
+ public StringSegment Value
+ {
+ get { return _value; }
+ set
{
- get { return _value; }
- set
- {
- CookieHeaderValue.CheckValueFormat(value, nameof(value));
- _value = value;
- }
+ CookieHeaderValue.CheckValueFormat(value, nameof(value));
+ _value = value;
}
+ }
- /// <summary>
- /// Gets or sets a value for the <c>Expires</c> cookie attribute.
- /// <para>
- /// The Expires attribute indicates the maximum lifetime of the cookie,
- /// represented as the date and time at which the cookie expires.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.1</remarks>
- public DateTimeOffset? Expires { get; set; }
-
- /// <summary>
- /// Gets or sets a value for the <c>Max-Age</c> cookie attribute.
- /// <para>
- /// The Max-Age attribute indicates the maximum lifetime of the cookie,
- /// represented as the number of seconds until the cookie expires.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.2</remarks>
- public TimeSpan? MaxAge { get; set; }
-
- /// <summary>
- /// Gets or sets a value for the <c>Domain</c> cookie attribute.
- /// <para>
- /// The Domain attribute specifies those hosts to which the cookie will
- /// be sent.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.3</remarks>
- public StringSegment Domain { get; set; }
-
- /// <summary>
- /// Gets or sets a value for the <c>Path</c> cookie attribute.
- /// <para>
- /// The path attribute specifies those hosts to which the cookie will
- /// be sent.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.4</remarks>
- public StringSegment Path { get; set; }
-
- /// <summary>
- /// Gets or sets a value for the <c>Secure</c> cookie attribute.
- /// <para>
- /// The Secure attribute limits the scope of the cookie to "secure"
- /// channels.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.5</remarks>
- public bool Secure { get; set; }
-
- /// <summary>
- /// Gets or sets a value for the <c>SameSite</c> cookie attribute.
- /// <para>
- /// "SameSite" cookies offer a robust defense against CSRF attack when
- /// deployed in strict mode, and when supported by the client.
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05#section-8.8</remarks>
- public SameSiteMode SameSite { get; set; } = SameSiteMode.Unspecified;
-
- /// <summary>
- /// Gets or sets a value for the <c>HttpOnly</c> cookie attribute.
- /// <para>
- /// HttpOnly instructs the user agent to
- /// omit the cookie when providing access to cookies via "non-HTTP" APIs
- /// (such as a web browser API that exposes cookies to scripts).
- /// </para>
- /// </summary>
- /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.6</remarks>
- public bool HttpOnly { get; set; }
-
- /// <summary>
- /// Gets a collection of additional values to append to the cookie.
- /// </summary>
- public IList<StringSegment> Extensions { get; } = new List<StringSegment>();
-
- // name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={strict|lax|none}; httponly
- /// <inheritdoc />
- public override string ToString()
- {
- var length = _name.Length + EqualsToken.Length + _value.Length;
-
- string? maxAge = null;
- string? sameSite = null;
-
- if (Expires.HasValue)
- {
- length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + ExpiresDateLength;
- }
-
- if (MaxAge.HasValue)
- {
- maxAge = HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().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;
- }
+ /// <summary>
+ /// Gets or sets a value for the <c>Expires</c> cookie attribute.
+ /// <para>
+ /// The Expires attribute indicates the maximum lifetime of the cookie,
+ /// represented as the date and time at which the cookie expires.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.1</remarks>
+ public DateTimeOffset? Expires { get; set; }
- // Allow for Unspecified (-1) to skip SameSite
- if (SameSite == SameSiteMode.None)
- {
- sameSite = SameSiteNoneToken;
- length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
- }
- else if (SameSite == SameSiteMode.Lax)
- {
- sameSite = SameSiteLaxToken;
- length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
- }
- else if (SameSite == SameSiteMode.Strict)
- {
- sameSite = SameSiteStrictToken;
- length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
- }
+ /// <summary>
+ /// Gets or sets a value for the <c>Max-Age</c> cookie attribute.
+ /// <para>
+ /// The Max-Age attribute indicates the maximum lifetime of the cookie,
+ /// represented as the number of seconds until the cookie expires.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.2</remarks>
+ public TimeSpan? MaxAge { get; set; }
- if (HttpOnly)
- {
- length += SeparatorToken.Length + HttpOnlyToken.Length;
- }
+ /// <summary>
+ /// Gets or sets a value for the <c>Domain</c> cookie attribute.
+ /// <para>
+ /// The Domain attribute specifies those hosts to which the cookie will
+ /// be sent.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.3</remarks>
+ public StringSegment Domain { get; set; }
- foreach (var extension in Extensions)
- {
- length += SeparatorToken.Length + extension.Length;
- }
+ /// <summary>
+ /// Gets or sets a value for the <c>Path</c> cookie attribute.
+ /// <para>
+ /// The path attribute specifies those hosts to which the cookie will
+ /// be sent.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.4</remarks>
+ public StringSegment Path { get; set; }
- return string.Create(length, (this, maxAge, sameSite), (span, tuple) =>
- {
- var (headerValue, maxAgeValue, sameSite) = tuple;
+ /// <summary>
+ /// Gets or sets a value for the <c>Secure</c> cookie attribute.
+ /// <para>
+ /// The Secure attribute limits the scope of the cookie to "secure"
+ /// channels.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.5</remarks>
+ public bool Secure { get; set; }
- Append(ref span, headerValue._name);
- Append(ref span, EqualsToken);
- Append(ref span, headerValue._value);
+ /// <summary>
+ /// Gets or sets a value for the <c>SameSite</c> cookie attribute.
+ /// <para>
+ /// "SameSite" cookies offer a robust defense against CSRF attack when
+ /// deployed in strict mode, and when supported by the client.
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05#section-8.8</remarks>
+ public SameSiteMode SameSite { get; set; } = SameSiteMode.Unspecified;
- if (headerValue.Expires is DateTimeOffset expiresValue)
- {
- Append(ref span, SeparatorToken);
- Append(ref span, ExpiresToken);
- Append(ref span, EqualsToken);
+ /// <summary>
+ /// Gets or sets a value for the <c>HttpOnly</c> cookie attribute.
+ /// <para>
+ /// HttpOnly instructs the user agent to
+ /// omit the cookie when providing access to cookies via "non-HTTP" APIs
+ /// (such as a web browser API that exposes cookies to scripts).
+ /// </para>
+ /// </summary>
+ /// <remarks>See https://tools.ietf.org/html/rfc6265#section-4.1.2.6</remarks>
+ public bool HttpOnly { get; set; }
- var formatted = expiresValue.TryFormat(span, out var charsWritten, ExpiresDateFormat);
- span = span.Slice(charsWritten);
+ /// <summary>
+ /// Gets a collection of additional values to append to the cookie.
+ /// </summary>
+ public IList<StringSegment> Extensions { get; } = new List<StringSegment>();
- Debug.Assert(formatted);
- }
+ // name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={strict|lax|none}; httponly
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ var length = _name.Length + EqualsToken.Length + _value.Length;
- if (maxAgeValue != null)
- {
- AppendSegment(ref span, MaxAgeToken, maxAgeValue);
- }
+ string? maxAge = null;
+ string? sameSite = null;
- if (headerValue.Domain != null)
- {
- AppendSegment(ref span, DomainToken, headerValue.Domain);
- }
+ if (Expires.HasValue)
+ {
+ length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + ExpiresDateLength;
+ }
- if (headerValue.Path != null)
- {
- AppendSegment(ref span, PathToken, headerValue.Path);
- }
+ if (MaxAge.HasValue)
+ {
+ maxAge = HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds);
+ length += SeparatorToken.Length + MaxAgeToken.Length + EqualsToken.Length + maxAge.Length;
+ }
- if (headerValue.Secure)
- {
- AppendSegment(ref span, SecureToken, null);
- }
+ if (Domain != null)
+ {
+ length += SeparatorToken.Length + DomainToken.Length + EqualsToken.Length + Domain.Length;
+ }
- if (sameSite != null)
- {
- AppendSegment(ref span, SameSiteToken, sameSite);
- }
+ if (Path != null)
+ {
+ length += SeparatorToken.Length + PathToken.Length + EqualsToken.Length + Path.Length;
+ }
- if (headerValue.HttpOnly)
- {
- AppendSegment(ref span, HttpOnlyToken, null);
- }
+ if (Secure)
+ {
+ length += SeparatorToken.Length + SecureToken.Length;
+ }
- foreach (var extension in Extensions)
- {
- AppendSegment(ref span, extension, null);
- }
- });
+ // Allow for Unspecified (-1) to skip SameSite
+ if (SameSite == SameSiteMode.None)
+ {
+ sameSite = SameSiteNoneToken;
+ length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
+ }
+ else if (SameSite == SameSiteMode.Lax)
+ {
+ sameSite = SameSiteLaxToken;
+ length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
+ }
+ else if (SameSite == SameSiteMode.Strict)
+ {
+ sameSite = SameSiteStrictToken;
+ length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
}
- private static void AppendSegment(ref Span<char> span, StringSegment name, StringSegment value)
+ if (HttpOnly)
{
- Append(ref span, SeparatorToken);
- Append(ref span, name.AsSpan());
- if (value != null)
- {
- Append(ref span, EqualsToken);
- Append(ref span, value.AsSpan());
- }
+ length += SeparatorToken.Length + HttpOnlyToken.Length;
}
- private static void Append(ref Span<char> span, ReadOnlySpan<char> other)
+ foreach (var extension in Extensions)
{
- other.CopyTo(span);
- span = span.Slice(other.Length);
+ length += SeparatorToken.Length + extension.Length;
}
- /// <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)
+ return string.Create(length, (this, maxAge, sameSite), (span, tuple) =>
{
- builder.Append(_name.AsSpan());
- builder.Append('=');
- builder.Append(_value.AsSpan());
+ var (headerValue, maxAgeValue, sameSite) = tuple;
- if (Expires.HasValue)
- {
- AppendSegment(builder, ExpiresToken, HeaderUtilities.FormatDate(Expires.GetValueOrDefault()));
- }
+ Append(ref span, headerValue._name);
+ Append(ref span, EqualsToken);
+ Append(ref span, headerValue._value);
- if (MaxAge.HasValue)
+ if (headerValue.Expires is DateTimeOffset expiresValue)
{
- AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds));
- }
+ Append(ref span, SeparatorToken);
+ Append(ref span, ExpiresToken);
+ Append(ref span, EqualsToken);
- if (Domain != null)
- {
- AppendSegment(builder, DomainToken, Domain);
+ var formatted = expiresValue.TryFormat(span, out var charsWritten, ExpiresDateFormat);
+ span = span.Slice(charsWritten);
+
+ Debug.Assert(formatted);
}
- if (Path != null)
+ if (maxAgeValue != null)
{
- AppendSegment(builder, PathToken, Path);
+ AppendSegment(ref span, MaxAgeToken, maxAgeValue);
}
- if (Secure)
+ if (headerValue.Domain != null)
{
- AppendSegment(builder, SecureToken, null);
+ AppendSegment(ref span, DomainToken, headerValue.Domain);
}
- // Allow for Unspecified (-1) to skip SameSite
- if (SameSite == SameSiteMode.None)
+ if (headerValue.Path != null)
{
- AppendSegment(builder, SameSiteToken, SameSiteNoneToken);
+ AppendSegment(ref span, PathToken, headerValue.Path);
}
- else if (SameSite == SameSiteMode.Lax)
+
+ if (headerValue.Secure)
{
- AppendSegment(builder, SameSiteToken, SameSiteLaxToken);
+ AppendSegment(ref span, SecureToken, null);
}
- else if (SameSite == SameSiteMode.Strict)
+
+ if (sameSite != null)
{
- AppendSegment(builder, SameSiteToken, SameSiteStrictToken);
+ AppendSegment(ref span, SameSiteToken, sameSite);
}
- if (HttpOnly)
+ if (headerValue.HttpOnly)
{
- AppendSegment(builder, HttpOnlyToken, null);
+ AppendSegment(ref span, HttpOnlyToken, null);
}
foreach (var extension in Extensions)
{
- AppendSegment(builder, extension, null);
+ AppendSegment(ref span, extension, null);
}
+ });
+ }
+
+ private static void AppendSegment(ref Span<char> span, StringSegment name, StringSegment value)
+ {
+ Append(ref span, SeparatorToken);
+ Append(ref span, name.AsSpan());
+ if (value != null)
+ {
+ Append(ref span, EqualsToken);
+ Append(ref span, value.AsSpan());
}
+ }
- private static void AppendSegment(StringBuilder builder, StringSegment name, StringSegment value)
+ private static void Append(ref Span<char> span, ReadOnlySpan<char> other)
+ {
+ other.CopyTo(span);
+ span = span.Slice(other.Length);
+ }
+
+ /// <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.AsSpan());
+ builder.Append('=');
+ builder.Append(_value.AsSpan());
+
+ if (Expires.HasValue)
{
- builder.Append("; ");
- builder.Append(name.AsSpan());
- if (value != null)
- {
- builder.Append('=');
- builder.Append(value.AsSpan());
- }
+ AppendSegment(builder, ExpiresToken, HeaderUtilities.FormatDate(Expires.GetValueOrDefault()));
+ }
+
+ if (MaxAge.HasValue)
+ {
+ AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds));
+ }
+
+ if (Domain != null)
+ {
+ AppendSegment(builder, DomainToken, Domain);
}
- /// <summary>
- /// Parses <paramref name="input"/> as a <see cref="SetCookieHeaderValue"/> value.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static SetCookieHeaderValue Parse(StringSegment input)
+ if (Path != null)
{
- var index = 0;
- return SingleValueParser.ParseValue(input, ref index)!;
+ AppendSegment(builder, PathToken, Path);
}
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="SetCookieHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out SetCookieHeaderValue? parsedValue)
+ if (Secure)
{
- var index = 0;
- return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ AppendSegment(builder, SecureToken, null);
}
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="SetCookieHeaderValue"/> values.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<SetCookieHeaderValue> ParseList(IList<string>? inputs)
+ // Allow for Unspecified (-1) to skip SameSite
+ if (SameSite == SameSiteMode.None)
+ {
+ AppendSegment(builder, SameSiteToken, SameSiteNoneToken);
+ }
+ else if (SameSite == SameSiteMode.Lax)
+ {
+ AppendSegment(builder, SameSiteToken, SameSiteLaxToken);
+ }
+ else if (SameSite == SameSiteMode.Strict)
{
- return MultipleValueParser.ParseValues(inputs);
+ AppendSegment(builder, SameSiteToken, SameSiteStrictToken);
}
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="SetCookieHeaderValue"/> values using string parsing rules.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<SetCookieHeaderValue> ParseStrictList(IList<string>? inputs)
+ if (HttpOnly)
{
- return MultipleValueParser.ParseStrictValues(inputs);
+ AppendSegment(builder, HttpOnlyToken, null);
}
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="SetCookieHeaderValue"/>.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseList(IList<string>? inputs, [NotNullWhen(true)] out IList<SetCookieHeaderValue>? parsedValues)
+ foreach (var extension in Extensions)
{
- return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ AppendSegment(builder, extension, null);
}
+ }
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="SetCookieHeaderValue"/> using string parsing rules.
- /// </summary>
- /// <param name="inputs">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseStrictList(IList<string>? inputs, [NotNullWhen(true)] out IList<SetCookieHeaderValue>? parsedValues)
+ private static void AppendSegment(StringBuilder builder, StringSegment name, StringSegment value)
+ {
+ builder.Append("; ");
+ builder.Append(name.AsSpan());
+ if (value != null)
{
- return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+ builder.Append('=');
+ builder.Append(value.AsSpan());
}
+ }
+
+ /// <summary>
+ /// Parses <paramref name="input"/> as a <see cref="SetCookieHeaderValue"/> value.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static SetCookieHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return SingleValueParser.ParseValue(input, ref index)!;
+ }
+
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="SetCookieHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out SetCookieHeaderValue? parsedValue)
+ {
+ var index = 0;
+ return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ }
+
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="SetCookieHeaderValue"/> values.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<SetCookieHeaderValue> ParseList(IList<string>? inputs)
+ {
+ return MultipleValueParser.ParseValues(inputs);
+ }
+
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="SetCookieHeaderValue"/> values using string parsing rules.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<SetCookieHeaderValue> ParseStrictList(IList<string>? inputs)
+ {
+ return MultipleValueParser.ParseStrictValues(inputs);
+ }
+
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="SetCookieHeaderValue"/>.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="SetCookieHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseList(IList<string>? inputs, [NotNullWhen(true)] out IList<SetCookieHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+ }
+
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="SetCookieHeaderValue"/> using string parsing rules.
+ /// </summary>
+ /// <param name="inputs">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseStrictList(IList<string>? inputs, [NotNullWhen(true)] 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|None}; httponly
+ private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
+ var offset = startIndex;
+
+ parsedValue = null;
- // name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax|None}; httponly
- private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue? parsedValue)
+ if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
{
- Contract.Requires(startIndex >= 0);
- var offset = startIndex;
+ return 0;
+ }
- parsedValue = null;
+ var result = new SetCookieHeaderValue();
- if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
- {
- return 0;
- }
+ // The caller should have already consumed any leading whitespace, commas, etc..
- var result = new SetCookieHeaderValue();
+ // Name=value;
- // The caller should have already consumed any leading whitespace, commas, etc..
+ // Name
+ var itemLength = HttpRuleParser.GetTokenLength(input, offset);
+ if (itemLength == 0)
+ {
+ return 0;
+ }
+ result._name = input.Subsegment(offset, itemLength);
+ offset += itemLength;
- // Name=value;
+ // = (no spaces)
+ if (!ReadEqualsSign(input, ref offset))
+ {
+ return 0;
+ }
- // Name
- var itemLength = HttpRuleParser.GetTokenLength(input, offset);
- if (itemLength == 0)
+ // value or "quoted value"
+ // The value may be empty
+ result._value = CookieHeaderParserShared.GetCookieValue(input, ref offset);
+
+ // *(';' SP cookie-av)
+ while (offset < input.Length)
+ {
+ if (input[offset] == ',')
{
- return 0;
+ // Divider between headers
+ break;
}
- result._name = input.Subsegment(offset, itemLength);
- offset += itemLength;
-
- // = (no spaces)
- if (!ReadEqualsSign(input, ref offset))
+ if (input[offset] != ';')
{
+ // Expecting a ';' between parameters
return 0;
}
+ offset++;
+
+ offset += HttpRuleParser.GetWhitespaceLength(input, offset);
- // value or "quoted value"
- // The value may be empty
- result._value = CookieHeaderParserShared.GetCookieValue(input, ref 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;
- // *(';' SP cookie-av)
- while (offset < input.Length)
+ // expires-av = "Expires=" sane-cookie-date
+ if (StringSegment.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase))
{
- if (input[offset] == ',')
+ // = (no spaces)
+ if (!ReadEqualsSign(input, ref offset))
{
- // Divider between headers
- break;
+ return 0;
}
- if (input[offset] != ';')
+ // We don't want to include comma, becouse date may contain it (eg. Sun, 06 Nov...)
+ var dateString = ReadToSemicolonOrEnd(input, ref offset, includeComma: false);
+ DateTimeOffset expirationDate;
+ if (!HttpRuleParser.TryStringToDate(dateString, out expirationDate))
{
- // Expecting a ';' between parameters
+ // Invalid expiration date, abort
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)
+ 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))
{
- // Trailing ';' or leading into garbage. Let the next parser fail.
- break;
+ return 0;
}
- var token = input.Subsegment(offset, itemLength);
- offset += itemLength;
- // expires-av = "Expires=" sane-cookie-date
- if (StringSegment.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase))
+ itemLength = HttpRuleParser.GetNumberLength(input, offset, allowDecimal: false);
+ if (itemLength == 0)
{
- // = (no spaces)
- if (!ReadEqualsSign(input, ref offset))
- {
- return 0;
- }
- // We don't want to include comma, becouse date may contain it (eg. Sun, 06 Nov...)
- var dateString = ReadToSemicolonOrEnd(input, ref offset, includeComma: false);
- DateTimeOffset expirationDate;
- if (!HttpRuleParser.TryStringToDate(dateString, out expirationDate))
- {
- // Invalid expiration date, abort
- return 0;
- }
- result.Expires = expirationDate;
+ return 0;
}
- // max-age-av = "Max-Age=" non-zero-digit *DIGIT
- else if (StringSegment.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase))
+ var numberString = input.Subsegment(offset, itemLength);
+ long maxAge;
+ if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge))
{
- // = (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;
+ // Invalid expiration date, abort
+ return 0;
}
- // 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))
+ 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))
{
- // = (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);
+ return 0;
}
- // path-av = "Path=" path-value
- // path-value = <any CHAR except CTLs or ";">
- else if (StringSegment.Equals(token, PathToken, StringComparison.OrdinalIgnoreCase))
+ // 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))
{
- // = (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);
+ return 0;
}
- // secure-av = "Secure"
- else if (StringSegment.Equals(token, SecureToken, StringComparison.OrdinalIgnoreCase))
+ // 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-value
+ // samesite-value = "Strict" / "Lax" / "None"
+ else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!ReadEqualsSign(input, ref offset))
{
- result.Secure = true;
+ result.SameSite = SameSiteMode.Unspecified;
}
- // samesite-av = "SameSite=" samesite-value
- // samesite-value = "Strict" / "Lax" / "None"
- else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
+ else
{
- if (!ReadEqualsSign(input, ref offset))
+ var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);
+
+ if (StringSegment.Equals(enforcementMode, SameSiteStrictToken, StringComparison.OrdinalIgnoreCase))
{
- result.SameSite = SameSiteMode.Unspecified;
+ result.SameSite = SameSiteMode.Strict;
+ }
+ else if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
+ {
+ result.SameSite = SameSiteMode.Lax;
+ }
+ else if (StringSegment.Equals(enforcementMode, SameSiteNoneToken, StringComparison.OrdinalIgnoreCase))
+ {
+ result.SameSite = SameSiteMode.None;
}
else
{
- var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);
-
- if (StringSegment.Equals(enforcementMode, SameSiteStrictToken, StringComparison.OrdinalIgnoreCase))
- {
- result.SameSite = SameSiteMode.Strict;
- }
- else if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
- {
- result.SameSite = SameSiteMode.Lax;
- }
- else if (StringSegment.Equals(enforcementMode, SameSiteNoneToken, StringComparison.OrdinalIgnoreCase))
- {
- result.SameSite = SameSiteMode.None;
- }
- else
- {
- result.SameSite = SameSiteMode.Unspecified;
- }
+ result.SameSite = SameSiteMode.Unspecified;
}
}
- // httponly-av = "HttpOnly"
- else if (StringSegment.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
- {
- result.HttpOnly = true;
- }
- // extension-av = <any CHAR except CTLs or ";">
- else
- {
- var tokenStart = offset - itemLength;
- ReadToSemicolonOrEnd(input, ref offset, includeComma: true);
- result.Extensions.Add(input.Subsegment(tokenStart, offset - tokenStart));
- }
}
-
- parsedValue = result;
- return offset - startIndex;
+ // httponly-av = "HttpOnly"
+ else if (StringSegment.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
+ {
+ result.HttpOnly = true;
+ }
+ // extension-av = <any CHAR except CTLs or ";">
+ else
+ {
+ var tokenStart = offset - itemLength;
+ ReadToSemicolonOrEnd(input, ref offset, includeComma: true);
+ result.Extensions.Add(input.Subsegment(tokenStart, offset - tokenStart));
+ }
}
- private static bool ReadEqualsSign(StringSegment input, ref int offset)
+ parsedValue = result;
+ return offset - startIndex;
+ }
+
+ private static bool ReadEqualsSign(StringSegment input, ref int offset)
+ {
+ // = (no spaces)
+ if (offset >= input.Length || input[offset] != '=')
{
- // = (no spaces)
- if (offset >= input.Length || input[offset] != '=')
- {
- return false;
- }
- offset++;
- return true;
+ return false;
}
+ offset++;
+ return true;
+ }
- private static StringSegment ReadToSemicolonOrEnd(StringSegment input, ref int offset, bool includeComma = true)
+ private static StringSegment ReadToSemicolonOrEnd(StringSegment input, ref int offset, bool includeComma = true)
+ {
+ var end = input.IndexOf(';', offset);
+ if (end < 0)
{
- var end = input.IndexOf(';', offset);
- if (end < 0)
+ // Also valid end of cookie
+ if (includeComma)
{
- // Also valid end of cookie
- if (includeComma)
- {
- end = input.IndexOf(',', offset);
- }
+ end = input.IndexOf(',', offset);
}
- else if (includeComma)
- {
- var commaPosition = input.IndexOf(',', offset);
- if (commaPosition >= 0 && commaPosition < end)
- {
- end = commaPosition;
- }
- }
-
- if (end < 0)
+ }
+ else if (includeComma)
+ {
+ var commaPosition = input.IndexOf(',', offset);
+ if (commaPosition >= 0 && commaPosition < end)
{
- // Remainder of the string
- end = input.Length;
+ end = commaPosition;
}
-
- var itemLength = end - offset;
- var result = input.Subsegment(offset, itemLength);
- offset += itemLength;
- return result;
}
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ if (end < 0)
{
- var other = obj as SetCookieHeaderValue;
+ // Remainder of the string
+ end = input.Length;
+ }
- if (other == null)
- {
- return false;
- }
+ var itemLength = end - offset;
+ var result = input.Subsegment(offset, itemLength);
+ offset += itemLength;
+ return result;
+ }
- 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
- && HeaderUtilities.AreEqualCollections(Extensions, other.Extensions, StringSegmentComparer.OrdinalIgnoreCase);
- }
-
- /// <inheritdoc />
- public override int GetHashCode()
- {
- var hash = 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();
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as SetCookieHeaderValue;
- foreach (var extension in Extensions)
- {
- hash ^= extension.GetHashCode();
- }
+ if (other == null)
+ {
+ return false;
+ }
- return hash;
+ 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
+ && HeaderUtilities.AreEqualCollections(Extensions, other.Extensions, StringSegmentComparer.OrdinalIgnoreCase);
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ var hash = 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();
+
+ foreach (var extension in Extensions)
+ {
+ hash ^= extension.GetHashCode();
}
+
+ return hash;
}
}
diff --git a/src/Http/Headers/src/StringWithQualityHeaderValue.cs b/src/Http/Headers/src/StringWithQualityHeaderValue.cs
index 6c18794217..20cc699259 100644
--- a/src/Http/Headers/src/StringWithQualityHeaderValue.cs
+++ b/src/Http/Headers/src/StringWithQualityHeaderValue.cs
@@ -8,264 +8,263 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+/// <summary>
+/// A string header value with an optional quality.
+/// </summary>
+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.
+ }
+
/// <summary>
- /// A string header value with an optional quality.
+ /// Initializes a new instance of <see cref="StringWithQualityHeaderValue"/>.
/// </summary>
- public class StringWithQualityHeaderValue
+ /// <param name="value">The <see cref="StringSegment"/> used to initialize the new instance.</param>
+ public StringWithQualityHeaderValue(StringSegment value)
{
- private static readonly HttpHeaderParser<StringWithQualityHeaderValue> SingleValueParser
- = new GenericHeaderParser<StringWithQualityHeaderValue>(false, GetStringWithQualityLength);
- private static readonly HttpHeaderParser<StringWithQualityHeaderValue> MultipleValueParser
- = new GenericHeaderParser<StringWithQualityHeaderValue>(true, GetStringWithQualityLength);
+ HeaderUtilities.CheckValidToken(value, nameof(value));
- private StringSegment _value;
- private double? _quality;
+ _value = value;
+ }
- private StringWithQualityHeaderValue()
- {
- // Used by the parser to create a new instance of this type.
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="StringWithQualityHeaderValue"/>.
+ /// </summary>
+ /// <param name="value">The <see cref="StringSegment"/> used to initialize the new instance.</param>
+ /// <param name="quality">The quality factor.</param>
+ public StringWithQualityHeaderValue(StringSegment value, double quality)
+ {
+ HeaderUtilities.CheckValidToken(value, nameof(value));
- /// <summary>
- /// Initializes a new instance of <see cref="StringWithQualityHeaderValue"/>.
- /// </summary>
- /// <param name="value">The <see cref="StringSegment"/> used to initialize the new instance.</param>
- public StringWithQualityHeaderValue(StringSegment value)
+ if ((quality < 0) || (quality > 1))
{
- HeaderUtilities.CheckValidToken(value, nameof(value));
-
- _value = value;
+ throw new ArgumentOutOfRangeException(nameof(quality));
}
- /// <summary>
- /// Initializes a new instance of <see cref="StringWithQualityHeaderValue"/>.
- /// </summary>
- /// <param name="value">The <see cref="StringSegment"/> used to initialize the new instance.</param>
- /// <param name="quality">The quality factor.</param>
- public StringWithQualityHeaderValue(StringSegment value, double quality)
- {
- HeaderUtilities.CheckValidToken(value, nameof(value));
+ _value = value;
+ _quality = quality;
+ }
- if ((quality < 0) || (quality > 1))
- {
- throw new ArgumentOutOfRangeException(nameof(quality));
- }
+ /// <summary>
+ /// Gets the string header value.
+ /// </summary>
+ public StringSegment Value => _value;
- _value = value;
- _quality = quality;
+ /// <summary>
+ /// Gets the quality factor.
+ /// </summary>
+ public double? Quality => _quality;
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ if (_quality.HasValue)
+ {
+ return _value + "; q=" + _quality.GetValueOrDefault().ToString("0.0##", NumberFormatInfo.InvariantInfo);
}
- /// <summary>
- /// Gets the string header value.
- /// </summary>
- public StringSegment Value => _value;
+ return _value.ToString();
+ }
- /// <summary>
- /// Gets the quality factor.
- /// </summary>
- public double? Quality => _quality;
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as StringWithQualityHeaderValue;
- /// <inheritdoc />
- public override string ToString()
+ if (other == null)
{
- if (_quality.HasValue)
- {
- return _value + "; q=" + _quality.GetValueOrDefault().ToString("0.0##", NumberFormatInfo.InvariantInfo);
- }
-
- return _value.ToString();
+ return false;
}
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ if (!StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase))
{
- 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.GetValueOrDefault() == 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;
+ return false;
}
- /// <inheritdoc />
- public override int GetHashCode()
+ if (_quality.HasValue)
{
- var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
-
- if (_quality.HasValue)
- {
- result = result ^ _quality.GetValueOrDefault().GetHashCode();
- }
-
- return result;
+ // 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.GetValueOrDefault() == other._quality.Value);
}
- /// <summary>
- /// Parses the specified <paramref name="input"/> as a <see cref="StringWithQualityHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <returns>The parsed value.</returns>
- public static StringWithQualityHeaderValue Parse(StringSegment input)
- {
- var index = 0;
- return SingleValueParser.ParseValue(input, ref index)!;
- }
+ // 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;
+ }
- /// <summary>
- /// Attempts to parse the specified <paramref name="input"/> as a <see cref="StringWithQualityHeaderValue"/>.
- /// </summary>
- /// <param name="input">The value to parse.</param>
- /// <param name="parsedValue">The parsed value.</param>
- /// <returns><see langword="true"/> if input is a valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParse(StringSegment input, [NotNullWhen(true)] out StringWithQualityHeaderValue parsedValue)
- {
- var index = 0;
- return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
- }
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="StringWithQualityHeaderValue"/> values.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<StringWithQualityHeaderValue> ParseList(IList<string>? input)
+ if (_quality.HasValue)
{
- return MultipleValueParser.ParseValues(input);
+ result = result ^ _quality.GetValueOrDefault().GetHashCode();
}
- /// <summary>
- /// Parses a sequence of inputs as a sequence of <see cref="StringWithQualityHeaderValue"/> values using string parsing rules.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <returns>The parsed values.</returns>
- public static IList<StringWithQualityHeaderValue> ParseStrictList(IList<string>? input)
- {
- return MultipleValueParser.ParseStrictValues(input);
- }
+ return result;
+ }
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="StringWithQualityHeaderValue"/>.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseList(IList<string>? input, [NotNullWhen(true)] out IList<StringWithQualityHeaderValue>? parsedValues)
- {
- return MultipleValueParser.TryParseValues(input, out parsedValues);
- }
+ /// <summary>
+ /// Parses the specified <paramref name="input"/> as a <see cref="StringWithQualityHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <returns>The parsed value.</returns>
+ public static StringWithQualityHeaderValue Parse(StringSegment input)
+ {
+ var index = 0;
+ return SingleValueParser.ParseValue(input, ref index)!;
+ }
- /// <summary>
- /// Attempts to parse the sequence of values as a sequence of <see cref="StringWithQualityHeaderValue"/> using string parsing rules.
- /// </summary>
- /// <param name="input">The values to parse.</param>
- /// <param name="parsedValues">The parsed values.</param>
- /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
- public static bool TryParseStrictList(IList<string>? input, [NotNullWhen(true)] out IList<StringWithQualityHeaderValue>? parsedValues)
- {
- return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
- }
+ /// <summary>
+ /// Attempts to parse the specified <paramref name="input"/> as a <see cref="StringWithQualityHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The value to parse.</param>
+ /// <param name="parsedValue">The parsed value.</param>
+ /// <returns><see langword="true"/> if input is a valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParse(StringSegment input, [NotNullWhen(true)] out StringWithQualityHeaderValue parsedValue)
+ {
+ var index = 0;
+ return SingleValueParser.TryParseValue(input, ref index, out parsedValue!);
+ }
- private static int GetStringWithQualityLength(StringSegment input, int startIndex, out StringWithQualityHeaderValue? parsedValue)
- {
- Contract.Requires(startIndex >= 0);
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="StringWithQualityHeaderValue"/> values.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<StringWithQualityHeaderValue> ParseList(IList<string>? input)
+ {
+ return MultipleValueParser.ParseValues(input);
+ }
- parsedValue = null;
+ /// <summary>
+ /// Parses a sequence of inputs as a sequence of <see cref="StringWithQualityHeaderValue"/> values using string parsing rules.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <returns>The parsed values.</returns>
+ public static IList<StringWithQualityHeaderValue> ParseStrictList(IList<string>? input)
+ {
+ return MultipleValueParser.ParseStrictValues(input);
+ }
- if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
- {
- return 0;
- }
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="StringWithQualityHeaderValue"/>.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseList(IList<string>? input, [NotNullWhen(true)] out IList<StringWithQualityHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseValues(input, out parsedValues);
+ }
- // Parse the value string: <value> in '<value>; q=<quality>'
- var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
+ /// <summary>
+ /// Attempts to parse the sequence of values as a sequence of <see cref="StringWithQualityHeaderValue"/> using string parsing rules.
+ /// </summary>
+ /// <param name="input">The values to parse.</param>
+ /// <param name="parsedValues">The parsed values.</param>
+ /// <returns><see langword="true"/> if all inputs are valid <see cref="StringWithQualityHeaderValue"/>, otherwise <see langword="false"/>.</returns>
+ public static bool TryParseStrictList(IList<string>? input, [NotNullWhen(true)] out IList<StringWithQualityHeaderValue>? parsedValues)
+ {
+ return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
+ }
- if (valueLength == 0)
- {
- return 0;
- }
+ private static int GetStringWithQualityLength(StringSegment input, int startIndex, out StringWithQualityHeaderValue? parsedValue)
+ {
+ Contract.Requires(startIndex >= 0);
- StringWithQualityHeaderValue result = new StringWithQualityHeaderValue();
- result._value = input.Subsegment(startIndex, valueLength);
- var current = startIndex + valueLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ parsedValue = null;
- if ((current == input.Length) || (input[current] != ';'))
- {
- parsedValue = result;
- return current - startIndex; // we have a valid token, but no quality.
- }
+ if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+ {
+ return 0;
+ }
+
+ // Parse the value string: <value> in '<value>; q=<quality>'
+ var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
- current++; // skip ';' separator
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ if (valueLength == 0)
+ {
+ return 0;
+ }
- // If we found a ';' separator, it must be followed by a quality information
- if (!TryReadQuality(input, result, ref current))
- {
- 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;
+ return current - startIndex; // we have a valid token, but no quality.
}
- private static bool TryReadQuality(StringSegment input, StringWithQualityHeaderValue result, ref int index)
- {
- var current = index;
+ current++; // skip ';' separator
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- // See if we have a quality value by looking for "q"
- if ((current == input.Length) || ((input[current] != 'q') && (input[current] != 'Q')))
- {
- return false;
- }
+ // If we found a ';' separator, it must be followed by a quality information
+ if (!TryReadQuality(input, result, ref current))
+ {
+ return 0;
+ }
- current++; // skip 'q' identifier
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ parsedValue = result;
+ return current - startIndex;
+ }
- // If we found "q" it must be followed by "="
- if ((current == input.Length) || (input[current] != '='))
- {
- return false;
- }
+ private static bool TryReadQuality(StringSegment input, StringWithQualityHeaderValue result, ref int index)
+ {
+ var current = index;
- current++; // skip '=' separator
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ // See if we have a quality value by looking for "q"
+ if ((current == input.Length) || ((input[current] != 'q') && (input[current] != 'Q')))
+ {
+ return false;
+ }
- if (current == input.Length)
- {
- return false;
- }
+ current++; // skip 'q' identifier
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- if (!HeaderUtilities.TryParseQualityDouble(input, current, out var quality, out var qualityLength))
- {
- return false;
- }
+ // If we found "q" it must be followed by "="
+ if ((current == input.Length) || (input[current] != '='))
+ {
+ return false;
+ }
- result._quality = quality;
+ current++; // skip '=' separator
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
- current = current + qualityLength;
- current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+ if (current == input.Length)
+ {
+ return false;
+ }
- index = current;
- return true;
+ 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
index 9d3cc91e09..967d2aea46 100644
--- a/src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs
+++ b/src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs
@@ -5,76 +5,75 @@ using System;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+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 StringWithQualityHeaderValueComparer()
+ {
+ }
+
+ /// <summary>
+ /// Gets the default instance of <see cref="StringWithQualityHeaderValueComparer"/>.
+ /// </summary>
+ public static StringWithQualityHeaderValueComparer QualityComparer { get; } = new StringWithQualityHeaderValueComparer();
+
/// <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.
+ /// 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>
- public class StringWithQualityHeaderValueComparer : IComparer<StringWithQualityHeaderValue>
+ /// <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)
{
- private StringWithQualityHeaderValueComparer()
+ if (stringWithQuality1 == null)
{
+ throw new ArgumentNullException(nameof(stringWithQuality1));
}
- /// <summary>
- /// Gets the default instance of <see cref="StringWithQualityHeaderValueComparer"/>.
- /// </summary>
- public static StringWithQualityHeaderValueComparer QualityComparer { get; } = new StringWithQualityHeaderValueComparer();
-
- /// <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 (stringWithQuality2 == null)
{
- if (stringWithQuality1 == null)
- {
- throw new ArgumentNullException(nameof(stringWithQuality1));
- }
+ throw new ArgumentNullException(nameof(stringWithQuality2));
+ }
- 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;
+ }
- var quality1 = stringWithQuality1.Quality ?? HeaderQuality.Match;
- var quality2 = stringWithQuality2.Quality ?? HeaderQuality.Match;
- var qualityDifference = quality1 - quality2;
- if (qualityDifference < 0)
+ if (!StringSegment.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ if (StringSegment.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal))
{
return -1;
}
- else if (qualityDifference > 0)
+ else if (StringSegment.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal))
{
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;
}
+
+ return 0;
}
}
diff --git a/src/Http/Headers/test/CacheControlHeaderValueTest.cs b/src/Http/Headers/test/CacheControlHeaderValueTest.cs
index bd40a63345..f2f077c705 100644
--- a/src/Http/Headers/test/CacheControlHeaderValueTest.cs
+++ b/src/Http/Headers/test/CacheControlHeaderValueTest.cs
@@ -5,593 +5,592 @@ using System;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class CacheControlHeaderValueTest
{
- public class CacheControlHeaderValueTest
+ [Fact]
+ public void Properties_SetAndGetAllProperties_SetValueReturnedInGetter()
{
- [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 PLACEHOLDER"));
- cacheControl.NoCacheHeaders.Add("PLACEHOLDER");
- Assert.Equal(1, cacheControl.NoCacheHeaders.Count);
- Assert.Equal("PLACEHOLDER", cacheControl.NoCacheHeaders.First());
-
- Assert.NotNull(cacheControl.PrivateHeaders);
- Assert.Throws<ArgumentException>(() => cacheControl.PrivateHeaders.Add(null));
- Assert.Throws<FormatException>(() => cacheControl.PrivateHeaders.Add("invalid PLACEHOLDER"));
- cacheControl.PrivateHeaders.Add("PLACEHOLDER");
- Assert.Equal(1, cacheControl.PrivateHeaders.Count);
- Assert.Equal("PLACEHOLDER", 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());
- }
+ 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 PLACEHOLDER"));
+ cacheControl.NoCacheHeaders.Add("PLACEHOLDER");
+ Assert.Equal(1, cacheControl.NoCacheHeaders.Count);
+ Assert.Equal("PLACEHOLDER", cacheControl.NoCacheHeaders.First());
+
+ Assert.NotNull(cacheControl.PrivateHeaders);
+ Assert.Throws<ArgumentException>(() => cacheControl.PrivateHeaders.Add(null));
+ Assert.Throws<FormatException>(() => cacheControl.PrivateHeaders.Add("invalid PLACEHOLDER"));
+ cacheControl.PrivateHeaders.Add("PLACEHOLDER");
+ Assert.Equal(1, cacheControl.PrivateHeaders.Count);
+ Assert.Equal("PLACEHOLDER", 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_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("PLACEHOLDER1");
+ Assert.Equal("no-cache=\"PLACEHOLDER1\"", cacheControl.ToString());
+ cacheControl.Public = true;
+ Assert.Equal("public, no-cache=\"PLACEHOLDER1\"", cacheControl.ToString());
+
+ cacheControl = new CacheControlHeaderValue();
+ cacheControl.Private = true;
+ Assert.Equal("private", cacheControl.ToString());
+ cacheControl.PrivateHeaders.Add("PLACEHOLDER2");
+ cacheControl.PrivateHeaders.Add("PLACEHOLDER3");
+ Assert.Equal("private=\"PLACEHOLDER2, PLACEHOLDER3\"", cacheControl.ToString());
+ cacheControl.MustRevalidate = true;
+ Assert.Equal("must-revalidate, private=\"PLACEHOLDER2, PLACEHOLDER3\"", cacheControl.ToString());
+ cacheControl.ProxyRevalidate = true;
+ Assert.Equal("must-revalidate, proxy-revalidate, private=\"PLACEHOLDER2, PLACEHOLDER3\"", cacheControl.ToString());
+ }
- [Fact]
- public void ToString_UseResponseDirectiveValues_AllSerializedCorrectly()
+ [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++)
{
- var cacheControl = new CacheControlHeaderValue();
- Assert.Equal("", cacheControl.ToString());
-
- cacheControl.NoCache = true;
- Assert.Equal("no-cache", cacheControl.ToString());
- cacheControl.NoCacheHeaders.Add("PLACEHOLDER1");
- Assert.Equal("no-cache=\"PLACEHOLDER1\"", cacheControl.ToString());
- cacheControl.Public = true;
- Assert.Equal("public, no-cache=\"PLACEHOLDER1\"", cacheControl.ToString());
-
- cacheControl = new CacheControlHeaderValue();
- cacheControl.Private = true;
- Assert.Equal("private", cacheControl.ToString());
- cacheControl.PrivateHeaders.Add("PLACEHOLDER2");
- cacheControl.PrivateHeaders.Add("PLACEHOLDER3");
- Assert.Equal("private=\"PLACEHOLDER2, PLACEHOLDER3\"", cacheControl.ToString());
- cacheControl.MustRevalidate = true;
- Assert.Equal("must-revalidate, private=\"PLACEHOLDER2, PLACEHOLDER3\"", cacheControl.ToString());
- cacheControl.ProxyRevalidate = true;
- Assert.Equal("must-revalidate, proxy-revalidate, private=\"PLACEHOLDER2, PLACEHOLDER3\"", cacheControl.ToString());
+ values[i] = new CacheControlHeaderValue();
}
- [Fact]
- public void GetHashCode_CompareValuesWithBoolFieldsSet_MatchExpectation()
+ 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++)
{
- // Verify that different bool fields return different hash values.
- var values = new CacheControlHeaderValue[9];
-
- for (int i = 0; i < values.Length; i++)
+ for (int j = 0; j < values.Length; j++)
{
- 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)
{
- if (i != j)
- {
- CompareHashCodes(values[i], values[j], false);
- }
+ 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];
+ // 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);
+ }
- for (int i = 0; i < values.Length; i++)
- {
- values[i] = new CacheControlHeaderValue();
- }
+ [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);
+ 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++)
+ // 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++)
{
- for (int j = 0; j < values.Length; j++)
+ if (i != j)
{
- if (i != j)
- {
- CompareHashCodes(values[i], values[j], false);
- }
+ CompareHashCodes(values[i], values[j], false);
}
}
+ }
- values[0].MaxStaleLimit = new TimeSpan(0, 1, 2);
- CompareHashCodes(values[0], values[1], 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);
- }
+ 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();
+ [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("PLACEHOLDER2");
+ cacheControl1.NoCache = true;
+ cacheControl1.NoCacheHeaders.Add("PLACEHOLDER2");
- cacheControl2.NoCache = true;
- cacheControl2.NoCacheHeaders.Add("PLACEHOLDER1");
- cacheControl2.NoCacheHeaders.Add("PLACEHOLDER2");
+ cacheControl2.NoCache = true;
+ cacheControl2.NoCacheHeaders.Add("PLACEHOLDER1");
+ cacheControl2.NoCacheHeaders.Add("PLACEHOLDER2");
- CompareHashCodes(cacheControl1, cacheControl2, false);
+ CompareHashCodes(cacheControl1, cacheControl2, false);
- cacheControl1.NoCacheHeaders.Add("PLACEHOLDER1");
- CompareHashCodes(cacheControl1, cacheControl2, true);
+ cacheControl1.NoCacheHeaders.Add("PLACEHOLDER1");
+ 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("PLACEHOLDER2");
- CompareHashCodes(cacheControl1, cacheControl3, false);
+ // 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("PLACEHOLDER2");
+ CompareHashCodes(cacheControl1, cacheControl3, false);
- cacheControl4.Extensions.Add(new NameValueHeaderValue("custom"));
- CompareHashCodes(cacheControl1, cacheControl4, 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);
+ 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);
- }
+ 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];
+ [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();
- }
+ 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++)
+ 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++)
{
- for (int j = 0; j < values.Length; j++)
+ if (i != j)
{
- if (i != j)
- {
- CompareValues(values[i], values[j], false);
- }
+ 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];
+ // 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);
+ }
- for (int i = 0; i < values.Length; i++)
- {
- values[i] = new CacheControlHeaderValue();
- }
+ [Fact]
+ public void Equals_CompareValuesWithTimeSpanFieldsSet_MatchExpectation()
+ {
+ // Verify that different timespan fields return different hash values.
+ var values = new CacheControlHeaderValue[4];
- 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);
+ 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++)
+ // 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++)
{
- for (int j = 0; j < values.Length; j++)
+ if (i != j)
{
- if (i != j)
- {
- CompareValues(values[i], values[j], false);
- }
+ CompareValues(values[i], values[j], false);
}
}
+ }
- values[0].MaxStaleLimit = new TimeSpan(0, 1, 2);
- CompareValues(values[0], values[1], 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);
+ 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);
+ 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);
- }
+ 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();
+ [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("PLACEHOLDER2");
+ cacheControl1.NoCache = true;
+ cacheControl1.NoCacheHeaders.Add("PLACEHOLDER2");
- Assert.False(cacheControl1.Equals(null), "Compare with 'null'");
+ Assert.False(cacheControl1.Equals(null), "Compare with 'null'");
- cacheControl2.NoCache = true;
- cacheControl2.NoCacheHeaders.Add("PLACEHOLDER1");
- cacheControl2.NoCacheHeaders.Add("PLACEHOLDER2");
+ cacheControl2.NoCache = true;
+ cacheControl2.NoCacheHeaders.Add("PLACEHOLDER1");
+ cacheControl2.NoCacheHeaders.Add("PLACEHOLDER2");
- CompareValues(cacheControl1!, cacheControl2, false);
+ CompareValues(cacheControl1!, cacheControl2, false);
- cacheControl1!.NoCacheHeaders.Add("PLACEHOLDER1");
- CompareValues(cacheControl1, cacheControl2, true);
+ cacheControl1!.NoCacheHeaders.Add("PLACEHOLDER1");
+ 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("PLACEHOLDER2");
- CompareValues(cacheControl1, cacheControl3, false);
+ // 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("PLACEHOLDER2");
+ CompareValues(cacheControl1, cacheControl3, false);
- cacheControl4.Private = true;
- cacheControl4.PrivateHeaders.Add("PLACEHOLDER3");
- CompareValues(cacheControl3, cacheControl4, false);
+ cacheControl4.Private = true;
+ cacheControl4.PrivateHeaders.Add("PLACEHOLDER3");
+ CompareValues(cacheControl3, cacheControl4, false);
- cacheControl5.Extensions.Add(new NameValueHeaderValue("custom"));
- CompareValues(cacheControl1, cacheControl5, 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);
+ 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);
- }
+ 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("PLACEHOLDER1");
- expected.NoCacheHeaders.Add("PLACEHOLDER2");
- CheckValidTryParse("no-cache=\"PLACEHOLDER1, PLACEHOLDER2\"", 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("PLACEHOLDER1");
- expected.MustRevalidate = true;
- expected.ProxyRevalidate = true;
- expected.Extensions.Add(new NameValueHeaderValue("c", "d"));
- expected.Extensions.Add(new NameValueHeaderValue("a", "b"));
- CheckValidTryParse(",public, , private=\"PLACEHOLDER1\", 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);
- }
+ [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("PLACEHOLDER1");
+ expected.NoCacheHeaders.Add("PLACEHOLDER2");
+ CheckValidTryParse("no-cache=\"PLACEHOLDER1, PLACEHOLDER2\"", 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("PLACEHOLDER1");
+ expected.MustRevalidate = true;
+ expected.ProxyRevalidate = true;
+ expected.Extensions.Add(new NameValueHeaderValue("c", "d"));
+ expected.Extensions.Add(new NameValueHeaderValue("a", "b"));
+ CheckValidTryParse(",public, , private=\"PLACEHOLDER1\", 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(" ")]
- // PLACEHOLDER-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=")]
- // PLACEHOLDER with optional field-name list
- [InlineData("no-cache=")]
- [InlineData("no-cache=PLACEHOLDER")]
- [InlineData("no-cache=\"PLACEHOLDER")]
- [InlineData("no-cache=\"\"")] // at least one PLACEHOLDER expected as value
- [InlineData("private=")]
- [InlineData("private=PLACEHOLDER")]
- [InlineData("private=\"PLACEHOLDER")]
- [InlineData("private=\",\"")] // at least one PLACEHOLDER expected as value
- [InlineData("private=\"=\"")]
- // PLACEHOLDER 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);
- }
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ // PLACEHOLDER-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=")]
+ // PLACEHOLDER with optional field-name list
+ [InlineData("no-cache=")]
+ [InlineData("no-cache=PLACEHOLDER")]
+ [InlineData("no-cache=\"PLACEHOLDER")]
+ [InlineData("no-cache=\"\"")] // at least one PLACEHOLDER expected as value
+ [InlineData("private=")]
+ [InlineData("private=PLACEHOLDER")]
+ [InlineData("private=\"PLACEHOLDER")]
+ [InlineData("private=\",\"")] // at least one PLACEHOLDER expected as value
+ [InlineData("private=\"=\"")]
+ // PLACEHOLDER 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_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 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_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("会");
- }
+ [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
+ #region Helper methods
- private void CompareHashCodes(CacheControlHeaderValue x, CacheControlHeaderValue y, bool areEqual)
+ private void CompareHashCodes(CacheControlHeaderValue x, CacheControlHeaderValue y, bool areEqual)
+ {
+ if (areEqual)
{
- if (areEqual)
- {
- Assert.Equal(x.GetHashCode(), y.GetHashCode());
- }
- else
- {
- Assert.NotEqual(x.GetHashCode(), y.GetHashCode());
- }
+ Assert.Equal(x.GetHashCode(), y.GetHashCode());
}
-
- private void CompareValues(CacheControlHeaderValue x, CacheControlHeaderValue y, bool areEqual)
+ else
{
- Assert.Equal(areEqual, x.Equals(y));
- Assert.Equal(areEqual, y.Equals(x));
+ Assert.NotEqual(x.GetHashCode(), y.GetHashCode());
}
+ }
- private void CheckValidParse(string? input, CacheControlHeaderValue expectedResult)
- {
- var result = CacheControlHeaderValue.Parse(input);
- Assert.Equal(expectedResult, result);
- }
+ private void CompareValues(CacheControlHeaderValue x, CacheControlHeaderValue y, bool areEqual)
+ {
+ Assert.Equal(areEqual, x.Equals(y));
+ Assert.Equal(areEqual, y.Equals(x));
+ }
- private void CheckInvalidParse(string? input)
- {
- Assert.Throws<FormatException>(() => CacheControlHeaderValue.Parse(input));
- }
+ private void CheckValidParse(string? input, CacheControlHeaderValue expectedResult)
+ {
+ var result = CacheControlHeaderValue.Parse(input);
+ Assert.Equal(expectedResult, result);
+ }
- private void CheckValidTryParse(string? input, CacheControlHeaderValue expectedResult)
- {
- Assert.True(CacheControlHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
+ private void CheckInvalidParse(string? input)
+ {
+ Assert.Throws<FormatException>(() => CacheControlHeaderValue.Parse(input));
+ }
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(CacheControlHeaderValue.TryParse(input, out var result));
- Assert.Null(result);
- }
+ private void CheckValidTryParse(string? input, CacheControlHeaderValue expectedResult)
+ {
+ Assert.True(CacheControlHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
- #endregion
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(CacheControlHeaderValue.TryParse(input, out var result));
+ Assert.Null(result);
}
+
+ #endregion
}
diff --git a/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs b/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs
index c0c45133de..d579a990c0 100644
--- a/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs
+++ b/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs
@@ -7,488 +7,488 @@ using System.Globalization;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class ContentDispositionHeaderValueTest
{
- public class ContentDispositionHeaderValueTest
+ [Fact]
+ public void Ctor_ContentDispositionNull_Throw()
{
- [Fact]
- public void Ctor_ContentDispositionNull_Throw()
- {
- Assert.Throws<ArgumentException>(() => new ContentDispositionHeaderValue(null));
- }
+ 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_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_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 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 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);
+ [Fact]
+ public void ContentDisposition_SetAndGetContentDisposition_MatchExpectations()
+ {
+ var contentDisposition = new ContentDispositionHeaderValue("inline");
+ Assert.Equal("inline", contentDisposition.DispositionType);
- contentDisposition.DispositionType = "attachment";
- Assert.Equal("attachment", 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_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");
+ [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);
+ // 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.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);
- }
+ contentDisposition.Parameters.Remove(name);
+ Assert.Null(contentDisposition.Name.Value);
+ }
- [Fact]
- public void FileName_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
- {
- var contentDisposition = new ContentDispositionHeaderValue("inline");
+ [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);
+ // 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.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);
- }
+ contentDisposition.Parameters.Remove(fileName);
+ Assert.Null(contentDisposition.FileName.Value);
+ }
- [Fact]
- public void FileName_NeedsEncoding_EncodedAndDecodedCorrectly()
- {
- var contentDisposition = new ContentDispositionHeaderValue("inline");
+ [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.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);
- }
+ contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
+ Assert.Null(contentDisposition.FileName.Value);
+ }
- [Fact]
- public void FileName_NeedsEncodingBecauseOfNewLine_EncodedAndDecodedCorrectly()
- {
- var contentDisposition = new ContentDispositionHeaderValue("inline");
+ [Fact]
+ public void FileName_NeedsEncodingBecauseOfNewLine_EncodedAndDecodedCorrectly()
+ {
+ var contentDisposition = new ContentDispositionHeaderValue("inline");
- contentDisposition.FileName = "File\nName.bat";
- Assert.Equal("File\nName.bat", contentDisposition.FileName);
- Assert.Equal(1, contentDisposition.Parameters.Count);
- Assert.Equal("filename", contentDisposition.Parameters.First().Name);
- Assert.Equal("\"=?utf-8?B?RmlsZQpOYW1lLmJhdA==?=\"", contentDisposition.Parameters.First().Value);
+ contentDisposition.FileName = "File\nName.bat";
+ Assert.Equal("File\nName.bat", contentDisposition.FileName);
+ Assert.Equal(1, contentDisposition.Parameters.Count);
+ Assert.Equal("filename", contentDisposition.Parameters.First().Name);
+ Assert.Equal("\"=?utf-8?B?RmlsZQpOYW1lLmJhdA==?=\"", contentDisposition.Parameters.First().Value);
- contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
- Assert.Null(contentDisposition.FileName.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 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_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");
+ [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.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);
- }
+ 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 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);
+ }
- [Theory]
- [InlineData("FileName.bat", "FileName.bat")]
- [InlineData("FileÃName.bat", "File_Name.bat")]
- [InlineData("File\nName.bat", "File_Name.bat")]
- public void SetHttpFileName_ShouldSanitizeFileNameWhereNeeded(string httpFileName, string expectedFileName)
- {
- var contentDisposition = new ContentDispositionHeaderValue("inline");
- contentDisposition.SetHttpFileName(httpFileName);
- Assert.Equal(expectedFileName, contentDisposition.FileName);
- }
+ [Theory]
+ [InlineData("FileName.bat", "FileName.bat")]
+ [InlineData("FileÃName.bat", "File_Name.bat")]
+ [InlineData("File\nName.bat", "File_Name.bat")]
+ public void SetHttpFileName_ShouldSanitizeFileNameWhereNeeded(string httpFileName, string expectedFileName)
+ {
+ var contentDisposition = new ContentDispositionHeaderValue("inline");
+ contentDisposition.SetHttpFileName(httpFileName);
+ Assert.Equal(expectedFileName, contentDisposition.FileName);
+ }
- [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", CultureInfo.InvariantCulture);
+ [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", CultureInfo.InvariantCulture);
- var contentDisposition = new ContentDispositionHeaderValue("inline");
+ 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);
+ // 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);
+ 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);
+ 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);
- }
+ contentDisposition.Parameters.Remove(dateParameter);
+ Assert.Null(contentDisposition.CreationDate);
+ }
- [Fact]
- public void Dates_InvalidDates_PropertyFails()
- {
- string invalidDateString = "\"Tue, 15 Nov 94 08:12 GMT\"";
+ [Fact]
+ public void Dates_InvalidDates_PropertyFails()
+ {
+ string invalidDateString = "\"Tue, 15 Nov 94 08:12 GMT\"";
- var contentDisposition = new ContentDispositionHeaderValue("inline");
+ 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);
+ // 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);
+ Assert.Null(contentDisposition.ReadDate);
- contentDisposition.ReadDate = null;
- Assert.Null(contentDisposition.ReadDate);
- Assert.Equal(0, contentDisposition.Parameters.Count);
- }
+ 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_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 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());
+ [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.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.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.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.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.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.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.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());
- }
+ 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 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 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_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 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_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/");
- }
+ [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>()
+ 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") },
@@ -540,119 +540,118 @@ namespace Microsoft.Net.Http.Headers
{ @"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]
+ [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));
- }
+ [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";
+ [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);
+ var result = ContentDispositionHeaderValue.Parse(contentDispositionLine);
- Assert.Equal(expectedName, result.Name);
- Assert.Equal(expectedFileName, result.FileName);
- }
+ Assert.Equal(expectedName, result.Name);
+ Assert.Equal(expectedFileName, result.FileName);
+ }
- [Fact]
- public void FileNameWithSurrogatePairs_EncodedCorrectly()
- {
- var contentDisposition = new ContentDispositionHeaderValue("attachment");
+ [Fact]
+ public void FileNameWithSurrogatePairs_EncodedCorrectly()
+ {
+ var contentDisposition = new ContentDispositionHeaderValue("attachment");
- contentDisposition.SetHttpFileName("File 🤩 name.txt");
- Assert.Equal("File __ name.txt", contentDisposition.FileName);
- Assert.Equal(2, contentDisposition.Parameters.Count);
- Assert.Equal("UTF-8\'\'File%20%F0%9F%A4%A9%20name.txt", contentDisposition.Parameters[1].Value);
- }
+ contentDisposition.SetHttpFileName("File 🤩 name.txt");
+ Assert.Equal("File __ name.txt", contentDisposition.FileName);
+ Assert.Equal(2, contentDisposition.Parameters.Count);
+ Assert.Equal("UTF-8\'\'File%20%F0%9F%A4%A9%20name.txt", contentDisposition.Parameters[1].Value);
+ }
- public class ContentDispositionValue
+ public class ContentDispositionValue
+ {
+ public ContentDispositionValue(string value, string description, bool valid)
{
- public ContentDispositionValue(string value, string description, bool valid)
- {
- Value = value;
- Description = description;
- Valid = valid;
- }
+ Value = value;
+ Description = description;
+ Valid = valid;
+ }
- public string Value { get; }
+ public string Value { get; }
- public string Description { get; }
+ public string Description { get; }
- public bool Valid { get; }
- }
+ public bool Valid { get; }
+ }
- private void CheckValidParse(string? input, ContentDispositionHeaderValue expectedResult)
- {
- var result = ContentDispositionHeaderValue.Parse(input);
- Assert.Equal(expectedResult, result);
- }
+ 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 CheckInvalidParse(string? input)
+ {
+ Assert.Throws<FormatException>(() => ContentDispositionHeaderValue.Parse(input));
+ }
- private void CheckValidTryParse(string? input, ContentDispositionHeaderValue expectedResult)
- {
- Assert.True(ContentDispositionHeaderValue.TryParse(input, out var result), input);
- Assert.Equal(expectedResult, result);
- }
+ private void CheckValidTryParse(string? input, ContentDispositionHeaderValue expectedResult)
+ {
+ Assert.True(ContentDispositionHeaderValue.TryParse(input, out var result), input);
+ Assert.Equal(expectedResult, result);
+ }
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(ContentDispositionHeaderValue.TryParse(input, out var result), input);
- Assert.Null(result);
- }
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(ContentDispositionHeaderValue.TryParse(input, out var result), input);
+ Assert.Null(result);
+ }
- private static void AssertFormatException(string contentDisposition)
- {
- Assert.Throws<FormatException>(() => new ContentDispositionHeaderValue(contentDisposition));
- }
+ 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
index bbb5456878..76cc4ad214 100644
--- a/src/Http/Headers/test/ContentRangeHeaderValueTest.cs
+++ b/src/Http/Headers/test/ContentRangeHeaderValueTest.cs
@@ -4,266 +4,265 @@
using System;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class ContentRangeHeaderValueTest
{
- 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.
+ Assert.True(ContentRangeHeaderValue.TryParse("bytes */*", out var 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)
+ {
+ Assert.False(ContentRangeHeaderValue.TryParse(input, out var 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)
{
- [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.
- Assert.True(ContentRangeHeaderValue.TryParse("bytes */*", out var 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)
- {
- Assert.False(ContentRangeHeaderValue.TryParse(input, out var 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)
- {
- Assert.True(ContentRangeHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
+ Assert.True(ContentRangeHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
}
}
diff --git a/src/Http/Headers/test/CookieHeaderValueTest.cs b/src/Http/Headers/test/CookieHeaderValueTest.cs
index 67466caac3..fe04133375 100644
--- a/src/Http/Headers/test/CookieHeaderValueTest.cs
+++ b/src/Http/Headers/test/CookieHeaderValueTest.cs
@@ -6,49 +6,49 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class CookieHeaderValueTest
{
- public class CookieHeaderValueTest
+ public static TheoryData<CookieHeaderValue, string> CookieHeaderDataSet
{
- public static TheoryData<CookieHeaderValue, string> CookieHeaderDataSet
+ get
{
- 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 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 header2 = new CookieHeaderValue("name2", "");
+ dataset.Add(header2, "name2=");
- var header3 = new CookieHeaderValue("name3", "value3");
- dataset.Add(header3, "name3=value3");
+ var header3 = new CookieHeaderValue("name3", "value3");
+ dataset.Add(header3, "name3=value3");
- var header4 = new CookieHeaderValue("name4", "\"value4\"");
- dataset.Add(header4, "name4=\"value4\"");
+ var header4 = new CookieHeaderValue("name4", "\"value4\"");
+ dataset.Add(header4, "name4=\"value4\"");
- return dataset;
- }
+ return dataset;
}
+ }
- public static TheoryData<string> InvalidCookieHeaderDataSet
+ public static TheoryData<string> InvalidCookieHeaderDataSet
+ {
+ get
{
- get
- {
- return new TheoryData<string>
+ return new TheoryData<string>
{
"=value",
"name=value;",
"name=value,",
};
- }
}
+ }
- public static TheoryData<string> InvalidCookieNames
+ public static TheoryData<string> InvalidCookieNames
+ {
+ get
{
- get
- {
- return new TheoryData<string>
+ return new TheoryData<string>
{
"<acb>",
"{acb}",
@@ -59,14 +59,14 @@ namespace Microsoft.Net.Http.Headers
"a\\b",
"a b",
};
- }
}
+ }
- public static TheoryData<string> InvalidCookieValues
+ public static TheoryData<string> InvalidCookieValues
+ {
+ get
{
- get
- {
- return new TheoryData<string>
+ return new TheoryData<string>
{
{ "\"" },
{ "a,b" },
@@ -77,250 +77,249 @@ namespace Microsoft.Net.Http.Headers
{ "abc\"" },
{ "a b" },
};
- }
}
+ }
- public static TheoryData<IList<CookieHeaderValue>, string?[]> ListOfCookieHeaderDataSet
+ public static TheoryData<IList<CookieHeaderValue>, string?[]> ListOfCookieHeaderDataSet
+ {
+ get
{
- 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;
- }
+ 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
+ public static TheoryData<IList<CookieHeaderValue>?, string?[]> ListWithInvalidCookieHeaderDataSet
+ {
+ get
{
- 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;
- }
+ 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"));
- }
+ [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(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));
- }
+ [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);
- }
+ [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);
- }
+ [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);
+ [Fact]
+ public void CookieHeaderValue_Value()
+ {
+ var cookie = new CookieHeaderValue("name");
+ Assert.Equal(string.Empty, cookie.Value);
- cookie.Value = "value1";
- Assert.Equal("value1", 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_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);
+ [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());
- }
+ 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));
+ [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());
- }
+ 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_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(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);
+ [Theory]
+ [MemberData(nameof(ListOfCookieHeaderDataSet))]
+ public void CookieHeaderValue_ParseList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
+ {
+ var results = CookieHeaderValue.ParseList(input);
- Assert.Equal(cookies, results);
- }
+ Assert.Equal(cookies, results);
+ }
- [Theory]
- [MemberData(nameof(ListOfCookieHeaderDataSet))]
- public void CookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
- {
- var results = CookieHeaderValue.ParseStrictList(input);
+ [Theory]
+ [MemberData(nameof(ListOfCookieHeaderDataSet))]
+ public void CookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
+ {
+ var results = CookieHeaderValue.ParseStrictList(input);
- Assert.Equal(cookies, results);
- }
+ 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);
+ [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);
- }
+ 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);
+ [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);
- }
+ Assert.Equal(cookies, results);
+ }
- [Theory]
- [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
- public void CookieHeaderValue_ParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
- {
- var results = CookieHeaderValue.ParseList(input);
- // ParseList always 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_ParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
+ {
+ var results = CookieHeaderValue.ParseList(input);
+ // ParseList always 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_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(
+ [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));
- }
+ {
+ Assert.Throws<FormatException>(() => CookieHeaderValue.ParseStrictList(input));
+ }
- [Theory]
- [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
- public void CookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(
+ [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);
- }
+ {
+ 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
index be168e35b2..70b88aae15 100644
--- a/src/Http/Headers/test/DateParserTest.cs
+++ b/src/Http/Headers/test/DateParserTest.cs
@@ -5,52 +5,51 @@ using System;
using System.Collections.Generic;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class DateParserTest
{
- 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)
{
- [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)));
- }
+ 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
index fefb5d5ab8..9357944dfe 100644
--- a/src/Http/Headers/test/EntityTagHeaderValueTest.cs
+++ b/src/Http/Headers/test/EntityTagHeaderValueTest.cs
@@ -6,111 +6,111 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class EntityTagHeaderValueTest
{
- public class EntityTagHeaderValueTest
+ [Fact]
+ public void Ctor_ETagNull_Throw()
{
- [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));
- }
+ 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_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_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 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());
+ [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("\"e tag\"", true);
+ Assert.Equal("W/\"e tag\"", etag.ToString());
- etag = new EntityTagHeaderValue("\"\"", false);
- Assert.Equal("\"\"", 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 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 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));
- }
+ [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
+ public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> NotEquivalentUnderStrongComparison
+ {
+ get
{
- get
- {
- return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+ return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
{
{ new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"TAG\"") },
{ new EntityTagHeaderValue("\"tag\"", true), new EntityTagHeaderValue("\"tag\"", true) },
@@ -118,162 +118,162 @@ namespace Microsoft.Net.Http.Headers
{ 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));
- }
+ [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
+ public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> EquivalentUnderStrongComparison
+ {
+ get
{
- get
- {
- return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+ 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));
- }
+ [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
+ public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> NotEquivalentUnderWeakComparison
+ {
+ get
{
- get
- {
- return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+ 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));
- }
+ [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
+ public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> EquivalentUnderWeakComparison
+ {
+ get
{
- get
- {
- return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+ 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));
- }
+ [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_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 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_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 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);
+ [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[0]);
+ Assert.NotNull(result);
+ Assert.Equal(0, result.Count);
- result = EntityTagHeaderValue.ParseList(new string[] { "" });
- 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()
- {
- Assert.False(EntityTagHeaderValue.TryParseList(null, out var results));
- Assert.False(EntityTagHeaderValue.TryParseList(new string[0], out results));
- Assert.False(EntityTagHeaderValue.TryParseList(new string[] { "" }, out results));
- }
+ [Fact]
+ public void TryParseList_NullOrEmptyArray_ReturnsFalse()
+ {
+ Assert.False(EntityTagHeaderValue.TryParseList(null, out var results));
+ Assert.False(EntityTagHeaderValue.TryParseList(new string[0], out results));
+ Assert.False(EntityTagHeaderValue.TryParseList(new string[] { "" }, out results));
+ }
- [Fact]
- public void ParseList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void ParseList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\"",
"",
@@ -284,10 +284,10 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseList(inputs);
+ IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
@@ -299,14 +299,14 @@ namespace Microsoft.Net.Http.Headers
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\"",
"",
@@ -317,10 +317,10 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseStrictList(inputs);
+ IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseStrictList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
@@ -332,14 +332,14 @@ namespace Microsoft.Net.Http.Headers
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\"",
"",
@@ -350,9 +350,9 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- Assert.True(EntityTagHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ Assert.True(EntityTagHeaderValue.TryParseList(inputs, out var results));
+ var expectedResults = new[]
+ {
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
@@ -364,14 +364,14 @@ namespace Microsoft.Net.Http.Headers
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\"",
"",
@@ -382,9 +382,9 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- Assert.True(EntityTagHeaderValue.TryParseStrictList(inputs, out var results));
- var expectedResults = new[]
- {
+ Assert.True(EntityTagHeaderValue.TryParseStrictList(inputs, out var results));
+ var expectedResults = new[]
+ {
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
@@ -396,14 +396,14 @@ namespace Microsoft.Net.Http.Headers
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ [Fact]
+ public void ParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\", tag, \"tag\"",
"tag, \"tag\"",
@@ -414,9 +414,9 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- var results = EntityTagHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var results = EntityTagHeaderValue.ParseList(inputs);
+ var expectedResults = new[]
+ {
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
@@ -426,14 +426,14 @@ namespace Microsoft.Net.Http.Headers
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_WithSomeInvalidValues_Throws()
+ [Fact]
+ public void ParseStrictList_WithSomeInvalidValues_Throws()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\", tag, \"tag\"",
"tag, \"tag\"",
@@ -444,14 +444,14 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- Assert.Throws<FormatException>(() => EntityTagHeaderValue.ParseStrictList(inputs));
- }
+ Assert.Throws<FormatException>(() => EntityTagHeaderValue.ParseStrictList(inputs));
+ }
- [Fact]
- public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ [Fact]
+ public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\", tag, \"tag\"",
"tag, \"tag\"",
@@ -462,9 +462,9 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- Assert.True(EntityTagHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ Assert.True(EntityTagHeaderValue.TryParseList(inputs, out var results));
+ var expectedResults = new[]
+ {
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
new EntityTagHeaderValue("\"tag\""),
@@ -474,14 +474,14 @@ namespace Microsoft.Net.Http.Headers
new EntityTagHeaderValue("\"tag\"", true),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ [Fact]
+ public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"\"tag\", tag, \"tag\"",
"tag, \"tag\"",
@@ -492,35 +492,34 @@ namespace Microsoft.Net.Http.Headers
"\"tag\", \"tag\"",
"W/\"tag\"",
};
- Assert.False(EntityTagHeaderValue.TryParseStrictList(inputs, out var results));
- }
+ Assert.False(EntityTagHeaderValue.TryParseStrictList(inputs, out var results));
+ }
- private void CheckValidParse(string? input, EntityTagHeaderValue expectedResult)
- {
- var result = EntityTagHeaderValue.Parse(input);
- Assert.Equal(expectedResult, result);
- }
+ 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 CheckInvalidParse(string? input)
+ {
+ Assert.Throws<FormatException>(() => EntityTagHeaderValue.Parse(input));
+ }
- private void CheckValidTryParse(string? input, EntityTagHeaderValue expectedResult)
- {
- Assert.True(EntityTagHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
+ private void CheckValidTryParse(string? input, EntityTagHeaderValue expectedResult)
+ {
+ Assert.True(EntityTagHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(EntityTagHeaderValue.TryParse(input, out var result));
- Assert.Null(result);
- }
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(EntityTagHeaderValue.TryParse(input, out var result));
+ Assert.Null(result);
+ }
- private static void AssertFormatException(string tag)
- {
- Assert.Throws<FormatException>(() => new EntityTagHeaderValue(tag));
- }
+ 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
index a467bcaf77..dd9acd6b67 100644
--- a/src/Http/Headers/test/HeaderUtilitiesTest.cs
+++ b/src/Http/Headers/test/HeaderUtilitiesTest.cs
@@ -6,280 +6,279 @@ using System.Globalization;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class HeaderUtilitiesTest
{
- public class HeaderUtilitiesTest
+ private const string Rfc1123Format = "r";
+
+ [Theory]
+ [MemberData(nameof(TestValues))]
+ public void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
{
- private const string Rfc1123Format = "r";
+ var formatted = dateTime.ToString(Rfc1123Format);
+ var expected = quoted ? $"\"{formatted}\"" : formatted;
+ var actual = HeaderUtilities.FormatDate(dateTime, quoted);
- [Theory]
- [MemberData(nameof(TestValues))]
- public void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
+ Assert.Equal(expected, actual);
+ }
+
+ public static TheoryData<DateTimeOffset, bool> TestValues
+ {
+ get
{
- var formatted = dateTime.ToString(Rfc1123Format);
- var expected = quoted ? $"\"{formatted}\"" : formatted;
- var actual = HeaderUtilities.FormatDate(dateTime, quoted);
+ var data = new TheoryData<DateTimeOffset, bool>();
- Assert.Equal(expected, actual);
- }
+ var date = new DateTimeOffset(new DateTime(2018, 1, 1, 1, 1, 1));
- public static TheoryData<DateTimeOffset, bool> TestValues
- {
- get
+ foreach (var quoted in new[] { true, false })
{
- var data = new TheoryData<DateTimeOffset, bool>();
-
- var date = new DateTimeOffset(new DateTime(2018, 1, 1, 1, 1, 1));
+ data.Add(date, quoted);
- foreach (var quoted in new[] { true, false })
+ for (var i = 1; i < 60; i++)
{
- 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);
- }
+ data.Add(date.AddSeconds(i), quoted);
+ data.Add(date.AddMinutes(i), quoted);
+ }
- for (var i = 1; i < 11; i++)
- {
- data.Add(date.AddMonths(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 < 5; i++)
- {
- data.Add(date.AddYears(i), quoted);
- }
+ for (var i = 1; i < 11; i++)
+ {
+ data.Add(date.AddMonths(i), quoted);
}
- return data;
+ for (var i = 1; i < 5; i++)
+ {
+ data.Add(date.AddYears(i), quoted);
+ }
}
- }
- [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);
+ return data;
}
+ }
- [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("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(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("", "")]
+ [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(-1)]
- [InlineData(-1234567890)]
- [InlineData(long.MinValue)]
- public void FormatNonNegativeInt64_Throws_ForNegativeValues(long value)
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(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("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(-1)]
+ [InlineData(-1234567890)]
+ [InlineData(long.MinValue)]
+ public void FormatNonNegativeInt64_Throws_ForNegativeValues(long value)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(value));
+ }
- [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("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("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("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("")]
- [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("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("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("")]
+ [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("\"hello\"", "hello")]
- [InlineData("\"hello", "\"hello")]
- [InlineData("hello\"", "hello\"")]
- [InlineData("\"\"hello\"\"", "\"hello\"")]
- public void RemoveQuotes_BehaviorCheck(string input, string expected)
- {
- var actual = HeaderUtilities.RemoveQuotes(input);
+ [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);
+ }
- 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);
+ [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);
- }
+ 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);
- [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);
+ }
- 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);
- [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);
+ }
- 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);
- [Theory]
- [InlineData("\n")]
- [InlineData("\b")]
- [InlineData("\r")]
- public void SetAndEscapeValue_ControlCharactersThrowFormatException(string input)
- {
- Assert.Throws<FormatException>(() => { var actual = HeaderUtilities.EscapeAsQuotedString(input); });
- }
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public void SetAndEscapeValue_ThrowsFormatExceptionOnDelCharacter()
- {
- Assert.Throws<FormatException>(() => { var actual = HeaderUtilities.EscapeAsQuotedString($"{(char)0x7F}"); });
- }
+ [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
index e0e4fc3726..34899be64a 100644
--- a/src/Http/Headers/test/MediaTypeHeaderValueComparerTests.cs
+++ b/src/Http/Headers/test/MediaTypeHeaderValueComparerTests.cs
@@ -5,15 +5,15 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class MediaTypeHeaderValueComparerTests
{
- public class MediaTypeHeaderValueComparerTests
+ public static IEnumerable<object[]> SortValues
{
- public static IEnumerable<object[]> SortValues
+ get
{
- get
- {
- yield return new object[] {
+ yield return new object[] {
new string[]
{
"application/*",
@@ -57,19 +57,18 @@ namespace Microsoft.Net.Http.Headers
"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());
+ [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();
+ var actualSorted = unsortedValues.OrderByDescending(m => m, MediaTypeHeaderValueComparer.QualityComparer).ToList();
- Assert.Equal(expectedSortedValues, actualSorted);
- }
+ Assert.Equal(expectedSortedValues, actualSorted);
}
}
diff --git a/src/Http/Headers/test/MediaTypeHeaderValueTest.cs b/src/Http/Headers/test/MediaTypeHeaderValueTest.cs
index faf69d24aa..69d4945b2a 100644
--- a/src/Http/Headers/test/MediaTypeHeaderValueTest.cs
+++ b/src/Http/Headers/test/MediaTypeHeaderValueTest.cs
@@ -8,548 +8,548 @@ using Microsoft.Extensions.Primitives;
using NuGet.Frameworks;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class MediaTypeHeaderValueTest
{
- public class MediaTypeHeaderValueTest
+ [Fact]
+ public void Ctor_MediaTypeNull_Throw()
{
- [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));
- }
+ 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?>
- {
+ [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);
+ [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);
- }
+ 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>
- {
+ 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);
+ [Theory]
+ [MemberData(nameof(MediaTypesWithSuffixesAndSpaces))]
+ public void Parse_CanParseSuffixedMediaTypes(string mediaType, string expectedSubTypeWithoutSuffix, string expectedSubTypeSuffix)
+ {
+ var result = MediaTypeHeaderValue.Parse(mediaType);
- // Act
- var result = mediaType.MatchesAllSubTypesWithoutSuffix;
+ Assert.Equal(new StringSegment(expectedSubTypeWithoutSuffix), result.SubTypeWithoutSuffix); // TODO consider overloading to have SubTypeWithoutSuffix?
+ Assert.Equal(new StringSegment(expectedSubTypeSuffix), result.Suffix);
+ }
- // Assert
- Assert.Equal(expectedReturnValue, result);
- }
+ [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);
- [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!));
- }
+ // Act
+ var result = mediaType.MatchesAllSubTypesWithoutSuffix;
- [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);
+ // Assert
+ Assert.Equal(expectedReturnValue, result);
+ }
- mediaType.MediaType = "application/xml";
- Assert.Equal("application/xml", mediaType.MediaType);
- }
+ [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 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");
+ [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);
+ }
- // 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);
+ [Fact]
+ public void Parameters_AddNull_Throw()
+ {
+ var mediaType = new MediaTypeHeaderValue("text/plain");
+ Assert.Throws<ArgumentNullException>(() => mediaType.Parameters.Add(null!));
+ }
- mediaType.Charset = "new_charset";
- Assert.Equal("new_charset", mediaType.Charset);
- Assert.Equal(1, mediaType.Parameters.Count);
- Assert.Equal("CHARSET", mediaType.Parameters.First().Name);
+ [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);
+ }
- mediaType.Parameters.Remove(charset);
- Assert.Null(mediaType.Charset.Value);
- }
+ [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 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");
+ [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);
+ }
- 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 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);
+ }
- mediaType.Quality = 0.9;
- Assert.Equal(0.9, mediaType.Quality);
- Assert.Equal(1, mediaType.Parameters.Count);
- Assert.Equal("q", mediaType.Parameters.First().Name);
+ [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);
+ }
- mediaType.Parameters.Remove(quality);
- Assert.Null(mediaType.Quality);
- }
+ [Fact]
+ public void MediaType_SetAndGetMediaType_MatchExpectations()
+ {
+ var mediaType = new MediaTypeHeaderValue("text/plain");
+ Assert.Equal("text/plain", mediaType.MediaType);
- [Fact]
- public void Quality_AddQualityParameterUpperCase_CaseInsensitiveComparison()
- {
- var mediaType = new MediaTypeHeaderValue("text/plain");
+ mediaType.MediaType = "application/xml";
+ Assert.Equal("application/xml", mediaType.MediaType);
+ }
- 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 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 Quality_LessThanZero_Throw()
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => new MediaTypeHeaderValue("application/xml", -0.01));
- }
+ [Fact]
+ public void Charset_AddCharsetParameterThenUseProperty_ParametersEntryIsOverwritten()
+ {
+ var mediaType = new MediaTypeHeaderValue("text/plain");
- [Fact]
- public void Quality_GreaterThanOne_Throw()
- {
- var mediaType = new MediaTypeHeaderValue("application/xml");
- Assert.Throws<ArgumentOutOfRangeException>(() => mediaType.Quality = 1.01);
- }
+ // 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);
- [Fact]
- public void ToString_UseDifferentMediaTypes_AllSerializedCorrectly()
- {
- var mediaType = new MediaTypeHeaderValue("text/plain");
- Assert.Equal("text/plain", mediaType.ToString());
+ mediaType.Charset = "new_charset";
+ Assert.Equal("new_charset", mediaType.Charset);
+ Assert.Equal(1, mediaType.Parameters.Count);
+ Assert.Equal("CHARSET", mediaType.Parameters.First().Name);
- mediaType.Charset = "utf-8";
- Assert.Equal("text/plain; charset=utf-8", mediaType.ToString());
+ mediaType.Parameters.Remove(charset);
+ Assert.Null(mediaType.Charset.Value);
+ }
- mediaType.Parameters.Add(new NameValueHeaderValue("custom", "\"custom value\""));
- Assert.Equal("text/plain; charset=utf-8; custom=\"custom value\"", mediaType.ToString());
+ [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.
+ }
- mediaType.Charset = null;
- Assert.Equal("text/plain; custom=\"custom value\"", mediaType.ToString());
- }
+ [Fact]
+ public void Quality_AddQualityParameterThenUseProperty_ParametersEntryIsOverwritten()
+ {
+ var mediaType = new MediaTypeHeaderValue("text/plain");
- [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"));
+ 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);
+ }
- 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" });
+ [Fact]
+ public void ToString_UseDifferentMediaTypes_AllSerializedCorrectly()
+ {
+ var mediaType = new MediaTypeHeaderValue("text/plain");
+ Assert.Equal("text/plain", mediaType.ToString());
- CheckValidParse("text/plain; charset=iso-8859-1", new MediaTypeHeaderValue("text/plain") { Charset = "iso-8859-1" });
+ mediaType.Charset = "utf-8";
+ Assert.Equal("text/plain; charset=utf-8", mediaType.ToString());
- 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);
+ mediaType.Parameters.Add(new NameValueHeaderValue("custom", "\"custom value\""));
+ Assert.Equal("text/plain; charset=utf-8; custom=\"custom value\"", mediaType.ToString());
- expected = new MediaTypeHeaderValue("text/plain");
- expected.Parameters.Add(new NameValueHeaderValue("custom"));
- CheckValidParse(" text/plain; custom", expected);
+ mediaType.Charset = null;
+ Assert.Equal("text/plain; custom=\"custom value\"", mediaType.ToString());
+ }
- 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);
+ [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());
+ }
- expected = new MediaTypeHeaderValue("text/plain") { Charset = "utf-8" };
- expected.Parameters.Add(new NameValueHeaderValue("custom", "\"x\""));
- CheckValidParse("text/plain;custom=\"x\";charset=utf-8", expected);
+ [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.");
+ }
- expected = new MediaTypeHeaderValue("text/plain");
- CheckValidParse("text/plain;", expected);
+ [Fact]
+ public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ CheckValidParse("\r\n text/plain ", new MediaTypeHeaderValue("text/plain"));
+ CheckValidParse("text/plain", new MediaTypeHeaderValue("text/plain"));
- expected = new MediaTypeHeaderValue("text/plain");
- expected.Parameters.Add(new NameValueHeaderValue("name", ""));
- CheckValidParse("text/plain;name=", expected);
+ 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" });
- expected = new MediaTypeHeaderValue("text/plain");
- expected.Parameters.Add(new NameValueHeaderValue("name", "value"));
- CheckValidParse("text/plain;name=value;", expected);
+ CheckValidParse("text/plain; charset=iso-8859-1", new MediaTypeHeaderValue("text/plain") { Charset = "iso-8859-1" });
- 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);
+ 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("*/xml");
- expected.Charset = "utf-8";
- expected.Quality = 0.5;
- CheckValidParse("\r\n */xml; charset=utf-8; q=0.5", expected);
+ expected = new MediaTypeHeaderValue("text/plain");
+ expected.Parameters.Add(new NameValueHeaderValue("custom"));
+ CheckValidParse(" text/plain; custom", expected);
- expected = new MediaTypeHeaderValue("*/*");
- CheckValidParse("*/*", 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/*");
- expected.Charset = "utf-8";
- expected.Parameters.Add(new NameValueHeaderValue("foo", "bar"));
- CheckValidParse("text/*; charset=utf-8; foo=bar", 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");
- 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);
- }
+ expected = new MediaTypeHeaderValue("text/plain");
+ CheckValidParse("text/plain;", 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);
+ expected = new MediaTypeHeaderValue("text/plain");
+ expected.Parameters.Add(new NameValueHeaderValue("name", ""));
+ CheckValidParse("text/plain;name=", 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);
+ expected = new MediaTypeHeaderValue("text/plain");
+ expected.Parameters.Add(new NameValueHeaderValue("name", "value"));
+ CheckValidParse("text/plain;name=value;", expected);
- var value1 = new MediaTypeHeaderValue("text/plain");
- value1.Charset = "iso-8859-1";
- value1.Quality = 1.0;
+ 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);
- CheckValidTryParse("text/plain; charset=iso-8859-1; q=1.0", value1);
+ expected = new MediaTypeHeaderValue("*/xml");
+ expected.Charset = "utf-8";
+ expected.Quality = 0.5;
+ CheckValidParse("\r\n */xml; charset=utf-8; q=0.5", expected);
- var value2 = new MediaTypeHeaderValue("*/xml");
- value2.Charset = "utf-8";
- value2.Quality = 0.5;
+ expected = new MediaTypeHeaderValue("*/*");
+ CheckValidParse("*/*", expected);
- CheckValidTryParse("\r\n */xml; charset=utf-8; q=0.5", value2);
- }
+ expected = new MediaTypeHeaderValue("text/*");
+ expected.Charset = "utf-8";
+ expected.Parameters.Add(new NameValueHeaderValue("foo", "bar"));
+ CheckValidParse("text/*; charset=utf-8; foo=bar", expected);
- [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);
+ 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);
+ }
- results = MediaTypeHeaderValue.ParseList(new string[0]);
- Assert.NotNull(results);
- Assert.Equal(0, results.Count);
+ [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 , ");
+ }
- results = MediaTypeHeaderValue.ParseList(new string[] { "" });
- Assert.NotNull(results);
- Assert.Equal(0, results.Count);
- }
+ [Fact]
+ public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var expected = new MediaTypeHeaderValue("text/plain");
+ CheckValidTryParse("\r\n text/plain ", expected);
+ CheckValidTryParse("text/plain", expected);
- [Fact]
- public void TryParseList_NullOrEmptyArray_ReturnsFalse()
- {
- Assert.False(MediaTypeHeaderValue.TryParseList(null, out var results));
- Assert.False(MediaTypeHeaderValue.TryParseList(new string[0], out results));
- Assert.False(MediaTypeHeaderValue.TryParseList(new string[] { "" }, out results));
- }
+ // 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);
- [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 value1 = new MediaTypeHeaderValue("text/plain");
+ value1.Charset = "iso-8859-1";
+ value1.Quality = 1.0;
- var expectedResults = new[]
- {
+ 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()
+ {
+ Assert.False(MediaTypeHeaderValue.TryParseList(null, out var 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),
@@ -557,17 +557,17 @@ namespace Microsoft.Net.Http.Headers
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ 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);
+ [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[]
- {
+ var expectedResults = new[]
+ {
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("application/xml", 0.9),
@@ -575,17 +575,17 @@ namespace Microsoft.Net.Http.Headers
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ 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" };
- Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out var 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" };
+ Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("application/xml", 0.9),
@@ -593,17 +593,17 @@ namespace Microsoft.Net.Http.Headers
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ 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" };
+ Assert.True(MediaTypeHeaderValue.TryParseStrictList(inputs, out var results));
- [Fact]
- public void TryParseStrictList_SetOfValidValueStrings_ReturnsTrue()
+ var expectedResults = new[]
{
- var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
- Assert.True(MediaTypeHeaderValue.TryParseStrictList(inputs, out var results));
-
- var expectedResults = new[]
- {
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("application/xml", 0.9),
@@ -611,21 +611,21 @@ namespace Microsoft.Net.Http.Headers
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ [Fact]
+ public void ParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ {
+ var inputs = new[]
{
- 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 results = MediaTypeHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("ignore/this"),
@@ -634,33 +634,33 @@ namespace Microsoft.Net.Http.Headers
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_WithSomeInvalidValues_Throws()
+ [Fact]
+ public void ParseStrictList_WithSomeInvalidValues_Throws()
+ {
+ var inputs = new[]
{
- 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));
- }
+ Assert.Throws<FormatException>(() => MediaTypeHeaderValue.ParseStrictList(inputs));
+ }
- [Fact]
- public void TryParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ [Fact]
+ public void TryParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ {
+ var inputs = new[]
{
- 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"
};
- Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out var results));
+ Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new MediaTypeHeaderValue("text/html"),
new MediaTypeHeaderValue("application/xhtml+xml"),
new MediaTypeHeaderValue("ignore/this"),
@@ -669,235 +669,235 @@ namespace Microsoft.Net.Http.Headers
new MediaTypeHeaderValue("*/*", 0.8),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ [Fact]
+ public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ {
+ var inputs = new[]
{
- 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"
};
- Assert.False(MediaTypeHeaderValue.TryParseStrictList(inputs, out var results));
- }
-
- [Theory]
- [InlineData("*/*;", "*/*")]
- [InlineData("text/*", "text/*")]
- [InlineData("text/*", "text/plain")]
- [InlineData("*/*;", "text/plain")]
- [InlineData("text/plain", "text/plain")]
- [InlineData("text/plain;", "text/plain")]
- [InlineData("text/plain;", "TEXT/PLAIN")]
- public void MatchesMediaType_PositiveCases(string mediaType1, string mediaType2)
- {
- // Arrange
- var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
- var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
-
- // Act
- var matches = parsedMediaType1.MatchesMediaType(mediaType2);
- var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
-
- // Assert
- Assert.True(matches);
- //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
- Assert.Equal(matches, isSubsetOf);
- }
-
- [Theory]
- [InlineData("application/html", "text/*")]
- [InlineData("application/json", "application/html")]
- [InlineData("text/plain;", "*/*")]
- public void MatchesMediaType_NegativeCases(string mediaType1, string mediaType2)
- {
- // Arrange
- var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
- var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
-
- // Act
- var matches = parsedMediaType1.MatchesMediaType(mediaType2);
- var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
-
- // Assert
- Assert.False(matches);
- //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
- Assert.Equal(matches, isSubsetOf);
- }
-
- [Theory]
- [InlineData("application/entity+json", "application/entity+json")]
- [InlineData("application/json", "application/entity+json")]
- [InlineData("application/*+json", "application/entity+json")]
- [InlineData("application/*+json", "application/*+json")]
- [InlineData("application/json", "application/problem+json")]
- [InlineData("application/json", "application/vnd.restful+json")]
- [InlineData("application/*", "application/*+JSON")]
- [InlineData("application/*", "application/entity+JSON")]
- [InlineData("*/*", "application/entity+json")]
- public void MatchesMediaTypeWithSuffixes_PositiveCases(string mediaType1, string mediaType2)
- {
- // Arrange
- var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
- var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
-
- // Act
- var result = parsedMediaType1.MatchesMediaType(mediaType2);
- var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
-
- // Assert
- Assert.True(result);
- //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
- Assert.Equal(result, isSubsetOf);
- }
-
- [Theory]
- [InlineData("application/entity+json", "application/entity+txt")]
- [InlineData("application/entity+json", "application/json")]
- [InlineData("application/entity+json", "application/entity.v2+json")]
- [InlineData("application/*+json", "application/entity+txt")]
- [InlineData("application/*+*", "application/json")]
- [InlineData("application/entity", "application/entity+")]
- [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
- [InlineData("application/entity+json", "application/entity")]
- public void MatchesMediaTypeWithSuffixes_NegativeCases(string mediaType1, string mediaType2)
- {
- // Arrange
- var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
- var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
-
- // Arrange
- var result = parsedMediaType1.MatchesMediaType(mediaType2);
- var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
-
- // Assert
- Assert.False(result);
- //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
- Assert.Equal(result, isSubsetOf);
- }
-
- [Fact]
- public void MatchesMediaType_IgnoresParameters()
- {
- // Arrange
- var parsedMediaType1 = MediaTypeHeaderValue.Parse("application/json;param=1");
-
- // Arrange
- var result = parsedMediaType1.MatchesMediaType("application/json;param2=1");
-
- // Assert
- Assert.True(result);
- }
-
- [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/json", "application/problem+json")]
- [InlineData("application/json", "application/vnd.restful+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
- [InlineData("application/entity+json", "application/entity")]
- public void IsSubSetOfWithSuffixes_NegativeCases(string set, string subset)
- {
- // Arrange
- var setMediaType = MediaTypeHeaderValue.Parse(set);
- var subSetMediaType = MediaTypeHeaderValue.Parse(subset);
+ Assert.False(MediaTypeHeaderValue.TryParseStrictList(inputs, out var results));
+ }
+
+ [Theory]
+ [InlineData("*/*;", "*/*")]
+ [InlineData("text/*", "text/*")]
+ [InlineData("text/*", "text/plain")]
+ [InlineData("*/*;", "text/plain")]
+ [InlineData("text/plain", "text/plain")]
+ [InlineData("text/plain;", "text/plain")]
+ [InlineData("text/plain;", "TEXT/PLAIN")]
+ public void MatchesMediaType_PositiveCases(string mediaType1, string mediaType2)
+ {
+ // Arrange
+ var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
+ var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
+
+ // Act
+ var matches = parsedMediaType1.MatchesMediaType(mediaType2);
+ var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
+
+ // Assert
+ Assert.True(matches);
+ //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
+ Assert.Equal(matches, isSubsetOf);
+ }
+
+ [Theory]
+ [InlineData("application/html", "text/*")]
+ [InlineData("application/json", "application/html")]
+ [InlineData("text/plain;", "*/*")]
+ public void MatchesMediaType_NegativeCases(string mediaType1, string mediaType2)
+ {
+ // Arrange
+ var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
+ var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
+
+ // Act
+ var matches = parsedMediaType1.MatchesMediaType(mediaType2);
+ var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
+
+ // Assert
+ Assert.False(matches);
+ //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
+ Assert.Equal(matches, isSubsetOf);
+ }
+
+ [Theory]
+ [InlineData("application/entity+json", "application/entity+json")]
+ [InlineData("application/json", "application/entity+json")]
+ [InlineData("application/*+json", "application/entity+json")]
+ [InlineData("application/*+json", "application/*+json")]
+ [InlineData("application/json", "application/problem+json")]
+ [InlineData("application/json", "application/vnd.restful+json")]
+ [InlineData("application/*", "application/*+JSON")]
+ [InlineData("application/*", "application/entity+JSON")]
+ [InlineData("*/*", "application/entity+json")]
+ public void MatchesMediaTypeWithSuffixes_PositiveCases(string mediaType1, string mediaType2)
+ {
+ // Arrange
+ var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
+ var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
+
+ // Act
+ var result = parsedMediaType1.MatchesMediaType(mediaType2);
+ var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
+
+ // Assert
+ Assert.True(result);
+ //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
+ Assert.Equal(result, isSubsetOf);
+ }
+
+ [Theory]
+ [InlineData("application/entity+json", "application/entity+txt")]
+ [InlineData("application/entity+json", "application/json")]
+ [InlineData("application/entity+json", "application/entity.v2+json")]
+ [InlineData("application/*+json", "application/entity+txt")]
+ [InlineData("application/*+*", "application/json")]
+ [InlineData("application/entity", "application/entity+")]
+ [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
+ [InlineData("application/entity+json", "application/entity")]
+ public void MatchesMediaTypeWithSuffixes_NegativeCases(string mediaType1, string mediaType2)
+ {
+ // Arrange
+ var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
+ var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
+
+ // Arrange
+ var result = parsedMediaType1.MatchesMediaType(mediaType2);
+ var isSubsetOf = parsedMediaType2.IsSubsetOf(parsedMediaType1);
+
+ // Assert
+ Assert.False(result);
+ //Make sure that MatchesMediaType produces consistent result with IsSubsetOf
+ Assert.Equal(result, isSubsetOf);
+ }
+
+ [Fact]
+ public void MatchesMediaType_IgnoresParameters()
+ {
+ // Arrange
+ var parsedMediaType1 = MediaTypeHeaderValue.Parse("application/json;param=1");
+
+ // Arrange
+ var result = parsedMediaType1.MatchesMediaType("application/json;param2=1");
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [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/json", "application/problem+json")]
+ [InlineData("application/json", "application/vnd.restful+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
+ [InlineData("application/entity+json", "application/entity")]
+ 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);
+ // Act
+ var result = subSetMediaType.IsSubsetOf(setMediaType);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- public static TheoryData<string, List<StringSegment>> MediaTypesWithFacets =>
- new TheoryData<string, List<StringSegment>>
- {
+ public static TheoryData<string, List<StringSegment>> MediaTypesWithFacets =>
+ new TheoryData<string, List<StringSegment>>
+ {
{ "application/vdn.github",
new List<StringSegment>(){ "vdn", "github" } },
{ "application/vdn.github+json",
@@ -906,48 +906,47 @@ namespace Microsoft.Net.Http.Headers
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);
+ [Theory]
+ [MemberData(nameof(MediaTypesWithFacets))]
+ public void Facets_TestPositiveCases(string input, List<StringSegment> expected)
+ {
+ // Arrange
+ var mediaType = MediaTypeHeaderValue.Parse(input);
- // Act
- var result = mediaType.Facets;
+ // Act
+ var result = mediaType.Facets;
- // Assert
- Assert.Equal(expected, result);
- }
+ // Assert
+ Assert.Equal(expected, result);
+ }
- private void CheckValidParse(string? input, MediaTypeHeaderValue expectedResult)
- {
- var result = MediaTypeHeaderValue.Parse(input);
- Assert.Equal(expectedResult, 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 CheckInvalidParse(string? input)
+ {
+ Assert.Throws<FormatException>(() => MediaTypeHeaderValue.Parse(input));
+ }
- private void CheckValidTryParse(string? input, MediaTypeHeaderValue expectedResult)
- {
- Assert.True(MediaTypeHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
+ private void CheckValidTryParse(string? input, MediaTypeHeaderValue expectedResult)
+ {
+ Assert.True(MediaTypeHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(MediaTypeHeaderValue.TryParse(input, out var result));
- Assert.Null(result);
- }
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(MediaTypeHeaderValue.TryParse(input, out var result));
+ Assert.Null(result);
+ }
- private static void AssertFormatException(string mediaType)
- {
- Assert.Throws<FormatException>(() => new MediaTypeHeaderValue(mediaType));
- }
+ private static void AssertFormatException(string mediaType)
+ {
+ Assert.Throws<FormatException>(() => new MediaTypeHeaderValue(mediaType));
}
}
diff --git a/src/Http/Headers/test/NameValueHeaderValueTest.cs b/src/Http/Headers/test/NameValueHeaderValueTest.cs
index d12de484a3..fb4d857eee 100644
--- a/src/Http/Headers/test/NameValueHeaderValueTest.cs
+++ b/src/Http/Headers/test/NameValueHeaderValueTest.cs
@@ -6,336 +6,336 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class NameValueHeaderValueTest
{
- public class NameValueHeaderValueTest
+ [Fact]
+ public void Ctor_NameNull_Throw()
{
- [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));
- }
+ 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_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_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_SuccessfullyCopied()
- {
- 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_SuccessfullyCopied()
- {
- 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 Ctor_NameValidFormat_SuccessfullyCreated()
+ {
+ var nameValue = new NameValueHeaderValue("text", null);
+ Assert.Equal("text", nameValue.Name);
+ }
- [Fact]
- public void ToString_UseNoValueAndTokenAndQuotedStringValues_SerializedCorrectly()
- {
- var nameValue = new NameValueHeaderValue("text", "token");
- Assert.Equal("text=token", nameValue.ToString());
+ [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_SuccessfullyCopied()
+ {
+ 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);
+ }
- nameValue.Value = "\"quoted string\"";
- Assert.Equal("text=\"quoted string\"", nameValue.ToString());
+ [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"; });
+ }
- nameValue.Value = null;
- Assert.Equal("text", nameValue.ToString());
+ [Fact]
+ public void Copy_NameAndValue_SuccessfullyCopied()
+ {
+ 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);
+ }
- nameValue.Value = string.Empty;
- Assert.Equal("text", nameValue.ToString());
- }
+ [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 GetHashCode_ValuesUseDifferentValues_HashDiffersAccordingToRfc()
- {
- var nameValue1 = new NameValueHeaderValue("text");
- var nameValue2 = new NameValueHeaderValue("text");
+ [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);
+ }
- nameValue1.Value = null;
- nameValue2.Value = null;
- Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ [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"; });
+ }
- nameValue1.Value = "token";
- nameValue2.Value = null;
- Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ [Fact]
+ public void ToString_UseNoValueAndTokenAndQuotedStringValues_SerializedCorrectly()
+ {
+ var nameValue = new NameValueHeaderValue("text", "token");
+ Assert.Equal("text=token", nameValue.ToString());
- nameValue1.Value = "token";
- nameValue2.Value = string.Empty;
- Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ nameValue.Value = "\"quoted string\"";
+ Assert.Equal("text=\"quoted string\"", nameValue.ToString());
- nameValue1.Value = null;
- nameValue2.Value = string.Empty;
- Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ nameValue.Value = null;
+ Assert.Equal("text", nameValue.ToString());
- nameValue1.Value = "token";
- nameValue2.Value = "TOKEN";
- Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ nameValue.Value = string.Empty;
+ Assert.Equal("text", nameValue.ToString());
+ }
- nameValue1.Value = "token";
- nameValue2.Value = "token";
- Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ [Fact]
+ public void GetHashCode_ValuesUseDifferentValues_HashDiffersAccordingToRfc()
+ {
+ var nameValue1 = new NameValueHeaderValue("text");
+ var nameValue2 = new NameValueHeaderValue("text");
- nameValue1.Value = "\"quoted string\"";
- nameValue2.Value = "\"QUOTED STRING\"";
- Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ nameValue1.Value = null;
+ nameValue2.Value = null;
+ Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
- nameValue1.Value = "\"quoted string\"";
- nameValue2.Value = "\"quoted string\"";
- Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
- }
+ nameValue1.Value = "token";
+ nameValue2.Value = null;
+ Assert.NotEqual(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());
- }
+ nameValue1.Value = "token";
+ nameValue2.Value = string.Empty;
+ Assert.NotEqual(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 = string.Empty;
+ Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
- nameValue1.Value = null;
- nameValue2.Value = null;
- Assert.True(nameValue1.Equals(nameValue2), "<null> vs. <null>.");
+ nameValue1.Value = "token";
+ nameValue2.Value = "TOKEN";
+ Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
- nameValue1.Value = "token";
- nameValue2.Value = null;
- Assert.False(nameValue1.Equals(nameValue2), "token vs. <null>.");
+ nameValue1.Value = "token";
+ nameValue2.Value = "token";
+ Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
- nameValue1.Value = null;
- nameValue2.Value = "token";
- Assert.False(nameValue1.Equals(nameValue2), "<null> vs. token.");
+ nameValue1.Value = "\"quoted string\"";
+ nameValue2.Value = "\"QUOTED STRING\"";
+ Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
- nameValue1.Value = string.Empty;
- nameValue2.Value = "token";
- Assert.False(nameValue1.Equals(nameValue2), "string.Empty vs. token.");
+ nameValue1.Value = "\"quoted string\"";
+ nameValue2.Value = "\"quoted string\"";
+ Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ }
- nameValue1.Value = null;
- nameValue2.Value = string.Empty;
- Assert.True(nameValue1.Equals(nameValue2), "<null> vs. string.Empty.");
+ [Fact]
+ public void GetHashCode_NameUseDifferentCasing_HashDiffersAccordingToRfc()
+ {
+ var nameValue1 = new NameValueHeaderValue("text");
+ var nameValue2 = new NameValueHeaderValue("TEXT");
+ Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+ }
- nameValue1.Value = "token";
- nameValue2.Value = "TOKEN";
- Assert.True(nameValue1.Equals(nameValue2), "token vs. TOKEN.");
+ [Fact]
+ public void Equals_ValuesUseDifferentValues_ValuesAreEqualOrDifferentAccordingToRfc()
+ {
+ var nameValue1 = new NameValueHeaderValue("text");
+ var nameValue2 = new NameValueHeaderValue("text");
- nameValue1.Value = "token";
- nameValue2.Value = "token";
- Assert.True(nameValue1.Equals(nameValue2), "token vs. token.");
+ nameValue1.Value = null;
+ nameValue2.Value = null;
+ Assert.True(nameValue1.Equals(nameValue2), "<null> vs. <null>.");
- nameValue1.Value = "\"quoted string\"";
- nameValue2.Value = "\"QUOTED STRING\"";
- Assert.False(nameValue1.Equals(nameValue2), "\"quoted string\" vs. \"QUOTED STRING\".");
+ nameValue1.Value = "token";
+ nameValue2.Value = null;
+ Assert.False(nameValue1.Equals(nameValue2), "token vs. <null>.");
- nameValue1.Value = "\"quoted string\"";
- nameValue2.Value = "\"quoted string\"";
- Assert.True(nameValue1.Equals(nameValue2), "\"quoted string\" vs. \"quoted string\".");
+ nameValue1.Value = null;
+ nameValue2.Value = "token";
+ Assert.False(nameValue1.Equals(nameValue2), "<null> vs. token.");
- Assert.False(nameValue1.Equals(null), "\"quoted string\" vs. <null>.");
- }
+ nameValue1.Value = string.Empty;
+ nameValue2.Value = "token";
+ Assert.False(nameValue1.Equals(nameValue2), "string.Empty vs. token.");
- [Fact]
- public void Equals_NameUseDifferentCasing_ConsideredEqual()
- {
- var nameValue1 = new NameValueHeaderValue("text");
- var nameValue2 = new NameValueHeaderValue("TEXT");
- Assert.True(nameValue1.Equals(nameValue2), "text vs. TEXT.");
- }
+ nameValue1.Value = null;
+ nameValue2.Value = string.Empty;
+ Assert.True(nameValue1.Equals(nameValue2), "<null> vs. string.Empty.");
- [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()
+ 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[]
{
- var inputs = new[]
- {
"",
"name=value1",
"",
@@ -346,10 +346,10 @@ namespace Microsoft.Net.Http.Headers
"name=value6,name=value7",
"name=\"value 8\", name= \"value 9\"",
};
- var results = NameValueHeaderValue.ParseList(inputs);
+ var results = NameValueHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new NameValueHeaderValue("name", "value1"),
new NameValueHeaderValue("name", "value2"),
new NameValueHeaderValue("name", "value3"),
@@ -361,14 +361,14 @@ namespace Microsoft.Net.Http.Headers
new NameValueHeaderValue("name", "\"value 9\""),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name=value1",
"",
@@ -379,10 +379,10 @@ namespace Microsoft.Net.Http.Headers
"name=value6,name=value7",
"name=\"value 8\", name= \"value 9\"",
};
- var results = NameValueHeaderValue.ParseStrictList(inputs);
+ var results = NameValueHeaderValue.ParseStrictList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new NameValueHeaderValue("name", "value1"),
new NameValueHeaderValue("name", "value2"),
new NameValueHeaderValue("name", "value3"),
@@ -394,14 +394,14 @@ namespace Microsoft.Net.Http.Headers
new NameValueHeaderValue("name", "\"value 9\""),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name=value1",
"",
@@ -412,10 +412,10 @@ namespace Microsoft.Net.Http.Headers
"name=value6,name=value7",
"name=\"value 8\", name= \"value 9\"",
};
- Assert.True(NameValueHeaderValue.TryParseList(inputs, out var results));
+ Assert.True(NameValueHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new NameValueHeaderValue("name", "value1"),
new NameValueHeaderValue("name", "value2"),
new NameValueHeaderValue("name", "value3"),
@@ -427,14 +427,14 @@ namespace Microsoft.Net.Http.Headers
new NameValueHeaderValue("name", "\"value 9\""),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name=value1",
"",
@@ -445,10 +445,10 @@ namespace Microsoft.Net.Http.Headers
"name=value6,name=value7",
"name=\"value 8\", name= \"value 9\"",
};
- Assert.True(NameValueHeaderValue.TryParseStrictList(inputs, out var results));
+ Assert.True(NameValueHeaderValue.TryParseStrictList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new NameValueHeaderValue("name", "value1"),
new NameValueHeaderValue("name", "value2"),
new NameValueHeaderValue("name", "value3"),
@@ -460,14 +460,14 @@ namespace Microsoft.Net.Http.Headers
new NameValueHeaderValue("name", "\"value 9\""),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ [Fact]
+ public void ParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name1=value1",
"name2",
@@ -478,10 +478,10 @@ namespace Microsoft.Net.Http.Headers
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
- var results = NameValueHeaderValue.ParseList(inputs);
+ var results = NameValueHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new NameValueHeaderValue("name1", "value1"),
new NameValueHeaderValue("name2"),
new NameValueHeaderValue("name3", "3"),
@@ -496,14 +496,14 @@ namespace Microsoft.Net.Http.Headers
new NameValueHeaderValue("name11", "\"value 11\""),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_WithSomeInvalidValues_Throws()
+ [Fact]
+ public void ParseStrictList_WithSomeInvalidValues_Throws()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name1=value1",
"name2",
@@ -514,14 +514,14 @@ namespace Microsoft.Net.Http.Headers
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
- Assert.Throws<FormatException>(() => NameValueHeaderValue.ParseStrictList(inputs));
- }
+ Assert.Throws<FormatException>(() => NameValueHeaderValue.ParseStrictList(inputs));
+ }
- [Fact]
- public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ [Fact]
+ public void TryParseList_WithSomeInvalidValues_ExcludesInvalidValues()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name1=value1",
"name2",
@@ -532,10 +532,10 @@ namespace Microsoft.Net.Http.Headers
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
- Assert.True(NameValueHeaderValue.TryParseList(inputs, out var results));
+ Assert.True(NameValueHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new NameValueHeaderValue("name1", "value1"),
new NameValueHeaderValue("name2"),
new NameValueHeaderValue("name3", "3"),
@@ -550,14 +550,14 @@ namespace Microsoft.Net.Http.Headers
new NameValueHeaderValue("name11", "\"value 11\""),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ [Fact]
+ public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"name1=value1",
"name2",
@@ -568,126 +568,125 @@ namespace Microsoft.Net.Http.Headers
"name8=value8,name9=value9",
"name10=\"value 10\", name11= \"value 11\"",
};
- Assert.False(NameValueHeaderValue.TryParseStrictList(inputs, out var 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);
+ Assert.False(NameValueHeaderValue.TryParseStrictList(inputs, out var results));
+ }
- var actual = header.Value;
+ [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);
- Assert.Equal(expected, actual);
- }
+ var actual = header.GetUnescapedValue();
+ 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);
+ [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;
+ var actual = header.Value;
- Assert.Equal(input, actual);
- }
+ Assert.Equal(expected, 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;
+ [Theory]
+ [InlineData("\n")]
+ [InlineData("\b")]
+ [InlineData("\r")]
+ public void SetAndEscapeValue_ThrowsOnInvalidValues(string input)
+ {
+ var header = new NameValueHeaderValue("test");
+ Assert.Throws<FormatException>(() => header.SetAndEscapeValue(input));
+ }
- Assert.NotEqual(input, actual);
- }
+ [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;
- #region Helper methods
+ Assert.Equal(input, actual);
+ }
- private void CheckValidParse(string? input, NameValueHeaderValue expectedResult)
- {
- var result = NameValueHeaderValue.Parse(input);
- Assert.Equal(expectedResult, result);
- }
+ [Theory]
+ [InlineData("val\\nue")]
+ [InlineData("val\\bue")]
+ public void OverescapingValuesDoNotRoundTrip(string input)
+ {
+ var header = new NameValueHeaderValue("test");
+ header.SetAndEscapeValue(input);
+ var valueHeader = header.GetUnescapedValue();
- private void CheckInvalidParse(string? input)
- {
- Assert.Throws<FormatException>(() => NameValueHeaderValue.Parse(input));
- }
+ var actual = header.Value;
- private void CheckValidTryParse(string? input, NameValueHeaderValue expectedResult)
- {
- Assert.True(NameValueHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
+ Assert.NotEqual(input, actual);
+ }
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(NameValueHeaderValue.TryParse(input, out var result));
- Assert.Null(result);
- }
- private static void CheckValue(string? value)
- {
- var nameValue = new NameValueHeaderValue("text", value);
- Assert.Equal(value, nameValue.Value);
- }
+ #region Helper methods
- private static void AssertFormatException(string name, string? value)
- {
- Assert.Throws<FormatException>(() => new NameValueHeaderValue(name, value));
- }
+ 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)
+ {
+ Assert.True(NameValueHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
- #endregion
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(NameValueHeaderValue.TryParse(input, out var 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
index 24f7a3a7fd..cdf8f1489c 100644
--- a/src/Http/Headers/test/RangeConditionHeaderValueTest.cs
+++ b/src/Http/Headers/test/RangeConditionHeaderValueTest.cs
@@ -4,169 +4,168 @@
using System;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class RangeConditionHeaderValueTest
{
- 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()
{
- [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)
- {
- Assert.False(RangeConditionHeaderValue.TryParse(input, out var 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)
- {
- Assert.True(RangeConditionHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
-
- #endregion
+ 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)
+ {
+ Assert.False(RangeConditionHeaderValue.TryParse(input, out var 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)
+ {
+ Assert.True(RangeConditionHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
+
+ #endregion
}
diff --git a/src/Http/Headers/test/RangeHeaderValueTest.cs b/src/Http/Headers/test/RangeHeaderValueTest.cs
index 908e23b3f5..c0c92ceff5 100644
--- a/src/Http/Headers/test/RangeHeaderValueTest.cs
+++ b/src/Http/Headers/test/RangeHeaderValueTest.cs
@@ -5,177 +5,176 @@ using System;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class RangeHeaderValueTest
{
- 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)
{
- [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)
- {
- Assert.True(RangeHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
-
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(RangeHeaderValue.TryParse(input, out var result));
- Assert.Null(result);
- }
-
- #endregion
+ 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)
+ {
+ Assert.True(RangeHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
+
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(RangeHeaderValue.TryParse(input, out var result));
+ Assert.Null(result);
+ }
+
+ #endregion
}
diff --git a/src/Http/Headers/test/RangeItemHeaderValueTest.cs b/src/Http/Headers/test/RangeItemHeaderValueTest.cs
index 2522c81841..e0a173054e 100644
--- a/src/Http/Headers/test/RangeItemHeaderValueTest.cs
+++ b/src/Http/Headers/test/RangeItemHeaderValueTest.cs
@@ -6,157 +6,156 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class RangeItemHeaderValueTest
{
- public class RangeItemHeaderValueTest
+ [Fact]
+ public void Ctor_BothValuesNull_Throw()
{
- [Fact]
- public void Ctor_BothValuesNull_Throw()
- {
- Assert.Throws<ArgumentException>(() => new RangeItemHeaderValue(null, null));
- }
+ Assert.Throws<ArgumentException>(() => new RangeItemHeaderValue(null, null));
+ }
- [Fact]
- public void Ctor_FromValueNegative_Throw()
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => new RangeItemHeaderValue(-1, 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_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_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 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());
+ [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(5, null);
+ Assert.Equal("5-", rangeItem.ToString());
- rangeItem = new RangeItemHeaderValue(null, 10);
- Assert.Equal("-10", 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 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 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);
+ [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);
+ CheckValidTryParse(" 684684 - 123456789012345 ", 684684, 123456789012345);
- // The separator doesn't matter. It only parses until the first non-whitespace
- CheckValidTryParse(" 1 - 2 ,", 1, 2);
+ // 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));
- }
+ 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));
- }
+ [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);
+ 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 ranges = result.Ranges.ToArray();
+ Assert.Single(ranges);
- var range = ranges.First();
+ var range = ranges.First();
- Assert.Equal(expectedFrom, range.From);
- Assert.Equal(expectedTo, range.To);
- }
+ 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);
+ 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);
+ 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);
- }
+ 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
index ae4ac05b91..c21b0e1331 100644
--- a/src/Http/Headers/test/SetCookieHeaderValueTest.cs
+++ b/src/Http/Headers/test/SetCookieHeaderValueTest.cs
@@ -10,87 +10,87 @@ using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class SetCookieHeaderValueTest
{
- public class SetCookieHeaderValueTest
+ public static TheoryData<SetCookieHeaderValue, string> SetCookieHeaderDataSet
{
- public static TheoryData<SetCookieHeaderValue, string> SetCookieHeaderDataSet
+ get
{
- get
+ var dataset = new TheoryData<SetCookieHeaderValue, string>();
+ var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
{
- 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,
- };
- header1.Extensions.Add("extension1");
- header1.Extensions.Add("extension2=value");
- 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; extension1; extension2=value");
-
- 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");
+ 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,
+ };
+ header1.Extensions.Add("extension1");
+ header1.Extensions.Add("extension2=value");
+ 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; extension1; extension2=value");
+
+ 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 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 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; samesite=none");
+ var header7 = new SetCookieHeaderValue("name7", "value7")
+ {
+ SameSite = SameSiteMode.None,
+ };
+ dataset.Add(header7, "name7=value7; samesite=none");
- var header8 = new SetCookieHeaderValue("name8", "value8");
- header8.Extensions.Add("extension1");
- header8.Extensions.Add("extension2=value");
- dataset.Add(header8, "name8=value8; extension1; extension2=value");
+ var header8 = new SetCookieHeaderValue("name8", "value8");
+ header8.Extensions.Add("extension1");
+ header8.Extensions.Add("extension2=value");
+ dataset.Add(header8, "name8=value8; extension1; extension2=value");
- return dataset;
- }
+ return dataset;
}
+ }
- public static TheoryData<string> InvalidSetCookieHeaderDataSet
+ public static TheoryData<string> InvalidSetCookieHeaderDataSet
+ {
+ get
{
- get
- {
- return new TheoryData<string>
+ 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
+ public static TheoryData<string> InvalidCookieNames
+ {
+ get
{
- get
- {
- return new TheoryData<string>
+ return new TheoryData<string>
{
"<acb>",
"{acb}",
@@ -100,14 +100,14 @@ namespace Microsoft.Net.Http.Headers
"a;b",
"a\\b",
};
- }
}
+ }
- public static TheoryData<string> InvalidCookieValues
+ public static TheoryData<string> InvalidCookieValues
+ {
+ get
{
- get
- {
- return new TheoryData<string>
+ return new TheoryData<string>
{
{ "\"" },
{ "a,b" },
@@ -117,371 +117,370 @@ namespace Microsoft.Net.Http.Headers
{ "a\"bc" },
{ "abc\"" },
};
- }
}
+ }
- public static TheoryData<IList<SetCookieHeaderValue>, string?[]> ListOfSetCookieHeaderDataSet
+ public static TheoryData<IList<SetCookieHeaderValue>, string?[]> ListOfSetCookieHeaderDataSet
+ {
+ get
{
- get
+ var dataset = new TheoryData<IList<SetCookieHeaderValue>, string?[]>();
+ var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
{
- 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
- };
- header1.Extensions.Add("extension1");
- header1.Extensions.Add("extension2=value");
-
- 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; extension1; extension2=value";
-
- var header2 = new SetCookieHeaderValue("name2", "value2");
- var string2 = "name2=value2";
+ 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
+ };
+ header1.Extensions.Add("extension1");
+ header1.Extensions.Add("extension2=value");
+
+ 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; extension1; extension2=value";
+
+ 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 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 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 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 string6 = "name6=value6; samesite=Strict";
- var header6 = new SetCookieHeaderValue("name6", "value6")
- {
- SameSite = SameSiteMode.Strict
- };
- var string6 = "name6=value6; samesite=Strict";
+ var header7 = new SetCookieHeaderValue("name7", "value7")
+ {
+ SameSite = SameSiteMode.None
+ };
+ var string7 = "name7=value7; samesite=None";
- var header7 = new SetCookieHeaderValue("name7", "value7")
- {
- SameSite = SameSiteMode.None
- };
- var string7 = "name7=value7; samesite=None";
+ var header8 = new SetCookieHeaderValue("name8", "value8")
+ {
+ SameSite = SameSiteMode.Unspecified
+ };
+ var string8a = "name8=value8; samesite";
+ var string8b = "name8=value8; samesite=invalid";
+
+ var header9 = new SetCookieHeaderValue("name9", "value9");
+ header9.Extensions.Add("extension1");
+ header9.Extensions.Add("extension2=value");
+ var string9 = "name9=value9; extension1; extension2=value";
+
+
+ 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[] { string6 });
+ dataset.Add(new[] { header7 }.ToList(), new[] { string7 });
+ dataset.Add(new[] { header8 }.ToList(), new[] { string8a });
+ dataset.Add(new[] { header8 }.ToList(), new[] { string8b });
+ dataset.Add(new[] { header9 }.ToList(), new[] { string9 });
+
+ foreach (var item1 in SetCookieHeaderDataSet)
+ {
+ var pair_cookie1 = (SetCookieHeaderValue)item1[0];
+ var pair_string1 = item1[1].ToString();
- var header8 = new SetCookieHeaderValue("name8", "value8")
- {
- SameSite = SameSiteMode.Unspecified
- };
- var string8a = "name8=value8; samesite";
- var string8b = "name8=value8; samesite=invalid";
-
- var header9 = new SetCookieHeaderValue("name9", "value9");
- header9.Extensions.Add("extension1");
- header9.Extensions.Add("extension2=value");
- var string9 = "name9=value9; extension1; extension2=value";
-
-
- 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[] { string6 });
- dataset.Add(new[] { header7 }.ToList(), new[] { string7 });
- dataset.Add(new[] { header8 }.ToList(), new[] { string8a });
- dataset.Add(new[] { header8 }.ToList(), new[] { string8b });
- dataset.Add(new[] { header9 }.ToList(), new[] { string9 });
-
- foreach (var item1 in SetCookieHeaderDataSet)
+ foreach (var item2 in SetCookieHeaderDataSet)
{
- var pair_cookie1 = (SetCookieHeaderValue)item1[0];
- var pair_string1 = item1[1].ToString();
-
- foreach (var item2 in SetCookieHeaderDataSet)
- {
- var pair_cookie2 = (SetCookieHeaderValue)item2[0];
- var pair_string2 = item2[1].ToString();
+ var pair_cookie2 = (SetCookieHeaderValue)item2[0];
+ var pair_string2 = item2[1].ToString();
- dataset.Add(new[] { pair_cookie1, pair_cookie2 }.ToList(), new[] { string.Join(", ", pair_string1, pair_string2) });
+ dataset.Add(new[] { pair_cookie1, pair_cookie2 }.ToList(), new[] { string.Join(", ", pair_string1, pair_string2) });
- }
}
-
- return dataset;
}
+
+ return dataset;
}
+ }
- public static TheoryData<IList<SetCookieHeaderValue>?, string?[]> ListWithInvalidSetCookieHeaderDataSet
+ public static TheoryData<IList<SetCookieHeaderValue>?, string?[]> ListWithInvalidSetCookieHeaderDataSet
+ {
+ get
{
- get
+ var dataset = new TheoryData<IList<SetCookieHeaderValue>?, string?[]>();
+ var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
{
- 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";
+ 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 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 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 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;
- }
+ 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"));
- }
+ [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(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));
- }
+ [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);
- }
+ [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);
- }
+ [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);
+ [Fact]
+ public void SetCookieHeaderValue_Value()
+ {
+ var cookie = new SetCookieHeaderValue("name");
+ Assert.Equal(string.Empty, cookie.Value);
- cookie.Value = "value1";
- Assert.Equal("value1", 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_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();
+ [Theory]
+ [MemberData(nameof(SetCookieHeaderDataSet))]
+ public void SetCookieHeaderValue_AppendToStringBuilder(SetCookieHeaderValue input, string expectedValue)
+ {
+ var builder = new StringBuilder();
- input.AppendToStringBuilder(builder);
+ input.AppendToStringBuilder(builder);
- Assert.Equal(expectedValue, builder.ToString());
- }
+ Assert.Equal(expectedValue, builder.ToString());
+ }
- [Theory]
- [MemberData(nameof(SetCookieHeaderDataSet))]
- public void SetCookieHeaderValue_Parse_AcceptsValidValues(SetCookieHeaderValue cookie, string expectedValue)
- {
- var header = SetCookieHeaderValue.Parse(expectedValue);
+ [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());
- }
+ 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));
+ [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());
- }
+ 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_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(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);
+ [Theory]
+ [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
+ public void SetCookieHeaderValue_ParseList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+ {
+ var results = SetCookieHeaderValue.ParseList(input);
- Assert.Equal(cookies, results);
- }
+ 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);
+ [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);
- }
+ Assert.Equal(cookies, results);
+ }
- [Fact]
- public void SetCookieHeaderValue_TryParse_ExtensionOrderDoesntMatter()
- {
- string cookieHeaderValue1 = "cookiename=value; extensionname1=value; extensionname2=value;";
- string cookieHeaderValue2 = "cookiename=value; extensionname2=value; extensionname1=value;";
+ [Fact]
+ public void SetCookieHeaderValue_TryParse_ExtensionOrderDoesntMatter()
+ {
+ string cookieHeaderValue1 = "cookiename=value; extensionname1=value; extensionname2=value;";
+ string cookieHeaderValue2 = "cookiename=value; extensionname2=value; extensionname1=value;";
- SetCookieHeaderValue.TryParse(cookieHeaderValue1, out var setCookieHeaderValue1);
- SetCookieHeaderValue.TryParse(cookieHeaderValue2, out var setCookieHeaderValue2);
+ SetCookieHeaderValue.TryParse(cookieHeaderValue1, out var setCookieHeaderValue1);
+ SetCookieHeaderValue.TryParse(cookieHeaderValue2, out var setCookieHeaderValue2);
- Assert.Equal(setCookieHeaderValue1, setCookieHeaderValue2);
- }
+ Assert.Equal(setCookieHeaderValue1, setCookieHeaderValue2);
+ }
- [Theory]
- [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
- public void SetCookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
- {
- var results = SetCookieHeaderValue.ParseStrictList(input);
+ [Theory]
+ [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
+ public void SetCookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+ {
+ var results = SetCookieHeaderValue.ParseStrictList(input);
- Assert.Equal(cookies, results);
- }
+ 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);
+ [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);
- }
+ Assert.Equal(cookies, results);
+ }
- [Theory]
- [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
- public void SetCookieHeaderValue_ParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
- {
- var results = SetCookieHeaderValue.ParseList(input);
- // ParseList always 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_ParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+ {
+ var results = SetCookieHeaderValue.ParseList(input);
+ // ParseList always 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_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(
+ [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));
- }
+ {
+ Assert.Throws<FormatException>(() => SetCookieHeaderValue.ParseStrictList(input));
+ }
- [Theory]
- [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
- public void SetCookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(
+ [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);
- }
+ {
+ 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
index 12e5da6bec..bf9ecaa59f 100644
--- a/src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs
+++ b/src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs
@@ -5,15 +5,15 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class StringWithQualityHeaderValueComparerTest
{
- public class StringWithQualityHeaderValueComparerTest
+ public static TheoryData<string[], string[]> StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues
{
- public static TheoryData<string[], string[]> StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues
+ get
{
- get
- {
- return new TheoryData<string[], string[]>
+ return new TheoryData<string[], string[]>
{
{
new string[]
@@ -46,19 +46,18 @@ namespace Microsoft.Net.Http.Headers
}
}
};
- }
}
+ }
- [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());
+ [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();
+ var actualSorted = unsortedValues.OrderByDescending(k => k, StringWithQualityHeaderValueComparer.QualityComparer).ToList();
- Assert.True(expectedSortedValues.SequenceEqual(actualSorted));
- }
+ Assert.True(expectedSortedValues.SequenceEqual(actualSorted));
}
}
diff --git a/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs b/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs
index bf9770d3b6..1e7bb73f6c 100644
--- a/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs
+++ b/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs
@@ -6,208 +6,208 @@ using System.Collections.Generic;
using System.Linq;
using Xunit;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class StringWithQualityHeaderValueTest
{
- public class StringWithQualityHeaderValueTest
+ [Fact]
+ public void Ctor_StringOnlyOverload_MatchExpectation()
{
- [Fact]
- public void Ctor_StringOnlyOverload_MatchExpectation()
- {
- var value = new StringWithQualityHeaderValue("token");
- Assert.Equal("token", value.Value);
- Assert.Null(value.Quality);
+ 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"));
- }
+ 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);
+ [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<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));
- }
+ 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());
+ [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.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", 0);
+ Assert.Equal("token; q=0.0", value.ToString());
- value = new StringWithQualityHeaderValue("token", 1);
- Assert.Equal("token; q=1.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());
- }
+ // 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 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 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()
+ [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[]
{
- var inputs = new[]
- {
"",
"text1",
"text2,",
@@ -219,10 +219,10 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseList(inputs);
+ IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("textA"),
@@ -237,14 +237,14 @@ namespace Microsoft.Net.Http.Headers
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text2,",
@@ -256,10 +256,10 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseStrictList(inputs);
+ IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseStrictList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("textA"),
@@ -274,14 +274,14 @@ namespace Microsoft.Net.Http.Headers
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text2,",
@@ -293,10 +293,10 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out var results));
+ Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("textA"),
@@ -311,14 +311,14 @@ namespace Microsoft.Net.Http.Headers
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ [Fact]
+ public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text2,",
@@ -330,10 +330,10 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- Assert.True(StringWithQualityHeaderValue.TryParseStrictList(inputs, out var results));
+ Assert.True(StringWithQualityHeaderValue.TryParseStrictList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("text2"),
new StringWithQualityHeaderValue("textA"),
@@ -348,14 +348,14 @@ namespace Microsoft.Net.Http.Headers
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ [Fact]
+ public void ParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text 1",
@@ -368,10 +368,10 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- var results = StringWithQualityHeaderValue.ParseList(inputs);
+ var results = StringWithQualityHeaderValue.ParseList(inputs);
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("1"),
new StringWithQualityHeaderValue("text2"),
@@ -386,14 +386,14 @@ namespace Microsoft.Net.Http.Headers
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void ParseStrictList_WithSomeInvalidValues_Throws()
+ [Fact]
+ public void ParseStrictList_WithSomeInvalidValues_Throws()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text 1",
@@ -406,14 +406,14 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.ParseStrictList(inputs));
- }
+ Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.ParseStrictList(inputs));
+ }
- [Fact]
- public void TryParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ [Fact]
+ public void TryParseList_WithSomeInvalidValues_IgnoresInvalidValues()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text 1",
@@ -426,10 +426,10 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out var results));
+ Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out var results));
- var expectedResults = new[]
- {
+ var expectedResults = new[]
+ {
new StringWithQualityHeaderValue("text1"),
new StringWithQualityHeaderValue("1"),
new StringWithQualityHeaderValue("text2"),
@@ -444,14 +444,14 @@ namespace Microsoft.Net.Http.Headers
new StringWithQualityHeaderValue("text10", 0.5),
}.ToList();
- Assert.Equal(expectedResults, results);
- }
+ Assert.Equal(expectedResults, results);
+ }
- [Fact]
- public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ [Fact]
+ public void TryParseStrictList_WithSomeInvalidValues_ReturnsFalse()
+ {
+ var inputs = new[]
{
- var inputs = new[]
- {
"",
"text1",
"text 1",
@@ -464,29 +464,28 @@ namespace Microsoft.Net.Http.Headers
"text7,text8;q=0.5",
" text9 , text10 ; q = 0.5 ",
};
- Assert.False(StringWithQualityHeaderValue.TryParseStrictList(inputs, out var results));
- }
-
- #region Helper methods
+ Assert.False(StringWithQualityHeaderValue.TryParseStrictList(inputs, out var results));
+ }
- private void CheckValidParse(string? input, StringWithQualityHeaderValue expectedResult)
- {
- var result = StringWithQualityHeaderValue.Parse(input);
- Assert.Equal(expectedResult, result);
- }
+ #region Helper methods
- private void CheckValidTryParse(string? input, StringWithQualityHeaderValue expectedResult)
- {
- Assert.True(StringWithQualityHeaderValue.TryParse(input, out var result));
- Assert.Equal(expectedResult, result);
- }
+ private void CheckValidParse(string? input, StringWithQualityHeaderValue expectedResult)
+ {
+ var result = StringWithQualityHeaderValue.Parse(input);
+ Assert.Equal(expectedResult, result);
+ }
- private void CheckInvalidTryParse(string? input)
- {
- Assert.False(StringWithQualityHeaderValue.TryParse(input, out var result));
- Assert.Null(result);
- }
+ private void CheckValidTryParse(string? input, StringWithQualityHeaderValue expectedResult)
+ {
+ Assert.True(StringWithQualityHeaderValue.TryParse(input, out var result));
+ Assert.Equal(expectedResult, result);
+ }
- #endregion
+ private void CheckInvalidTryParse(string? input)
+ {
+ Assert.False(StringWithQualityHeaderValue.TryParse(input, out var result));
+ Assert.Null(result);
}
+
+ #endregion
}
diff --git a/src/Http/Http.Abstractions/perf/Microbenchmarks/GetHeaderSplitBenchmark.cs b/src/Http/Http.Abstractions/perf/Microbenchmarks/GetHeaderSplitBenchmark.cs
index 8f1c7b2e8f..55133975dc 100644
--- a/src/Http/Http.Abstractions/perf/Microbenchmarks/GetHeaderSplitBenchmark.cs
+++ b/src/Http/Http.Abstractions/perf/Microbenchmarks/GetHeaderSplitBenchmark.cs
@@ -8,55 +8,54 @@ using BenchmarkDotNet.Configs;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks
+namespace Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks;
+
+public class GetHeaderSplitBenchmark
{
- public class GetHeaderSplitBenchmark
- {
- HeaderDictionary _dictionary;
+ HeaderDictionary _dictionary;
- [GlobalSetup]
- public void GlobalSetup()
- {
- var dict = new Dictionary<string, StringValues>()
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ var dict = new Dictionary<string, StringValues>()
{
{ "singleValue", new StringValues("single") },
{ "singleValueQuoted", new StringValues("\"single\"") },
{ "doubleValue", new StringValues(new [] { "first", "second" }) },
{ "manyValue", new StringValues(new [] { "first", "second", "third", "fourth", "fifth", "sixth" }) }
};
- _dictionary = new HeaderDictionary(dict);
- }
-
- [Benchmark]
- public void SplitSingleHeader()
- {
- var values = ParsingHelpers.GetHeaderSplit(_dictionary, "singleValue");
- if (values.Count != 1)
- throw new Exception();
- }
-
- [Benchmark]
- public void SplitSingleQuotedHeader()
- {
- var values = ParsingHelpers.GetHeaderSplit(_dictionary, "singleValueQuoted");
- if (values.Count != 1)
- throw new Exception();
- }
-
- [Benchmark]
- public void SplitDoubleHeader()
- {
- var values = ParsingHelpers.GetHeaderSplit(_dictionary, "doubleValue");
- if (values.Count != 2)
- throw new Exception();
- }
-
- [Benchmark]
- public void SplitManyHeaders()
- {
- var values = ParsingHelpers.GetHeaderSplit(_dictionary, "manyValue");
- if (values.Count != 6)
- throw new Exception();
- }
+ _dictionary = new HeaderDictionary(dict);
+ }
+
+ [Benchmark]
+ public void SplitSingleHeader()
+ {
+ var values = ParsingHelpers.GetHeaderSplit(_dictionary, "singleValue");
+ if (values.Count != 1)
+ throw new Exception();
+ }
+
+ [Benchmark]
+ public void SplitSingleQuotedHeader()
+ {
+ var values = ParsingHelpers.GetHeaderSplit(_dictionary, "singleValueQuoted");
+ if (values.Count != 1)
+ throw new Exception();
+ }
+
+ [Benchmark]
+ public void SplitDoubleHeader()
+ {
+ var values = ParsingHelpers.GetHeaderSplit(_dictionary, "doubleValue");
+ if (values.Count != 2)
+ throw new Exception();
+ }
+
+ [Benchmark]
+ public void SplitManyHeaders()
+ {
+ var values = ParsingHelpers.GetHeaderSplit(_dictionary, "manyValue");
+ if (values.Count != 6)
+ throw new Exception();
}
}
diff --git a/src/Http/Http.Abstractions/perf/Microbenchmarks/PathStringBenchmark.cs b/src/Http/Http.Abstractions/perf/Microbenchmarks/PathStringBenchmark.cs
index 143644c0fc..0ef9871917 100644
--- a/src/Http/Http.Abstractions/perf/Microbenchmarks/PathStringBenchmark.cs
+++ b/src/Http/Http.Abstractions/perf/Microbenchmarks/PathStringBenchmark.cs
@@ -5,32 +5,31 @@ using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks
+namespace Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks;
+
+public class PathStringBenchmark
{
- public class PathStringBenchmark
- {
- private const string TestPath = "/api/a%2Fb/c";
- private const string LongTestPath = "/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a%20b";
- private const string LongTestPathEarlyPercent = "/t%20hisMustBeAVeryLongPath/SoLongButStillShorterToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeap/api/a%20b";
+ private const string TestPath = "/api/a%2Fb/c";
+ private const string LongTestPath = "/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a%20b";
+ private const string LongTestPathEarlyPercent = "/t%20hisMustBeAVeryLongPath/SoLongButStillShorterToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeap/api/a%20b";
- public IEnumerable<object> TestPaths => new[] { TestPath, LongTestPath, LongTestPathEarlyPercent };
+ public IEnumerable<object> TestPaths => new[] { TestPath, LongTestPath, LongTestPathEarlyPercent };
- public IEnumerable<object> TestUris => new[] { new Uri($"https://localhost:5001/{TestPath}"), new Uri($"https://localhost:5001/{LongTestPath}"), new Uri($"https://localhost:5001/{LongTestPathEarlyPercent}") };
+ public IEnumerable<object> TestUris => new[] { new Uri($"https://localhost:5001/{TestPath}"), new Uri($"https://localhost:5001/{LongTestPath}"), new Uri($"https://localhost:5001/{LongTestPathEarlyPercent}") };
- [Benchmark]
- [ArgumentsSource(nameof(TestPaths))]
- public string OnPathFromUriComponent(string testPath)
- {
- var pathString = PathString.FromUriComponent(testPath);
- return pathString.Value;
- }
+ [Benchmark]
+ [ArgumentsSource(nameof(TestPaths))]
+ public string OnPathFromUriComponent(string testPath)
+ {
+ var pathString = PathString.FromUriComponent(testPath);
+ return pathString.Value;
+ }
- [Benchmark]
- [ArgumentsSource(nameof(TestUris))]
- public string OnUriFromUriComponent(Uri testUri)
- {
- var pathString = PathString.FromUriComponent(testUri);
- return pathString.Value;
- }
+ [Benchmark]
+ [ArgumentsSource(nameof(TestUris))]
+ public string OnUriFromUriComponent(Uri testUri)
+ {
+ var pathString = PathString.FromUriComponent(testUri);
+ return pathString.Value;
}
}
diff --git a/src/Http/Http.Abstractions/src/BadHttpRequestException.cs b/src/Http/Http.Abstractions/src/BadHttpRequestException.cs
index b1d466b95f..57a6800859 100644
--- a/src/Http/Http.Abstractions/src/BadHttpRequestException.cs
+++ b/src/Http/Http.Abstractions/src/BadHttpRequestException.cs
@@ -4,60 +4,59 @@
using System;
using System.IO;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents an HTTP request error
+/// </summary>
+public class BadHttpRequestException : IOException
{
/// <summary>
- /// Represents an HTTP request error
+ /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class.
/// </summary>
- public class BadHttpRequestException : IOException
+ /// <param name="message">The message to associate with this exception.</param>
+ /// <param name="statusCode">The HTTP status code to associate with this exception.</param>
+ public BadHttpRequestException(string message, int statusCode)
+ : base(message)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class.
- /// </summary>
- /// <param name="message">The message to associate with this exception.</param>
- /// <param name="statusCode">The HTTP status code to associate with this exception.</param>
- public BadHttpRequestException(string message, int statusCode)
- : base(message)
- {
- StatusCode = statusCode;
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class with the <see cref="StatusCode"/> set to 400 Bad Request.
- /// </summary>
- /// <param name="message">The message to associate with this exception</param>
- public BadHttpRequestException(string message)
- : base(message)
- {
- StatusCode = StatusCodes.Status400BadRequest;
- }
+ StatusCode = statusCode;
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class.
- /// </summary>
- /// <param name="message">The message to associate with this exception.</param>
- /// <param name="statusCode">The HTTP status code to associate with this exception.</param>
- /// <param name="innerException">The inner exception to associate with this exception</param>
- public BadHttpRequestException(string message, int statusCode, Exception innerException)
- : base(message, innerException)
- {
- StatusCode = statusCode;
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class with the <see cref="StatusCode"/> set to 400 Bad Request.
+ /// </summary>
+ /// <param name="message">The message to associate with this exception</param>
+ public BadHttpRequestException(string message)
+ : base(message)
+ {
+ StatusCode = StatusCodes.Status400BadRequest;
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class with the <see cref="StatusCode"/> set to 400 Bad Request.
- /// </summary>
- /// <param name="message">The message to associate with this exception</param>
- /// <param name="innerException">The inner exception to associate with this exception</param>
- public BadHttpRequestException(string message, Exception innerException)
- : base(message, innerException)
- {
- StatusCode = StatusCodes.Status400BadRequest;
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class.
+ /// </summary>
+ /// <param name="message">The message to associate with this exception.</param>
+ /// <param name="statusCode">The HTTP status code to associate with this exception.</param>
+ /// <param name="innerException">The inner exception to associate with this exception</param>
+ public BadHttpRequestException(string message, int statusCode, Exception innerException)
+ : base(message, innerException)
+ {
+ StatusCode = statusCode;
+ }
- /// <summary>
- /// Gets the HTTP status code for this exception.
- /// </summary>
- public int StatusCode { get; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BadHttpRequestException"/> class with the <see cref="StatusCode"/> set to 400 Bad Request.
+ /// </summary>
+ /// <param name="message">The message to associate with this exception</param>
+ /// <param name="innerException">The inner exception to associate with this exception</param>
+ public BadHttpRequestException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ StatusCode = StatusCodes.Status400BadRequest;
}
+
+ /// <summary>
+ /// Gets the HTTP status code for this exception.
+ /// </summary>
+ public int StatusCode { get; }
}
diff --git a/src/Http/Http.Abstractions/src/ConnectionInfo.cs b/src/Http/Http.Abstractions/src/ConnectionInfo.cs
index 9327c58c20..2e208ddc68 100644
--- a/src/Http/Http.Abstractions/src/ConnectionInfo.cs
+++ b/src/Http/Http.Abstractions/src/ConnectionInfo.cs
@@ -6,55 +6,54 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the underlying connection for a request.
+/// </summary>
+public abstract class ConnectionInfo
{
/// <summary>
- /// Represents the underlying connection for a request.
+ /// Gets or sets a unique identifier to represent this connection.
+ /// </summary>
+ public abstract string Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the IP address of the remote target. Can be null.
+ /// </summary>
+ public abstract IPAddress? RemoteIpAddress { get; set; }
+
+ /// <summary>
+ /// Gets or sets the port of the remote target.
+ /// </summary>
+ public abstract int RemotePort { get; set; }
+
+ /// <summary>
+ /// Gets or sets the IP address of the local host.
/// </summary>
- public abstract class ConnectionInfo
+ public abstract IPAddress? LocalIpAddress { get; set; }
+
+ /// <summary>
+ /// Gets or sets the port of the local host.
+ /// </summary>
+ public abstract int LocalPort { get; set; }
+
+ /// <summary>
+ /// Gets or sets the client certificate.
+ /// </summary>
+ public abstract X509Certificate2? ClientCertificate { get; set; }
+
+ /// <summary>
+ /// Retrieves the client certificate.
+ /// </summary>
+ /// <returns>Asynchronously returns an <see cref="X509Certificate2" />. Can be null.</returns>
+ public abstract Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken = new CancellationToken());
+
+ /// <summary>
+ /// Close connection gracefully.
+ /// </summary>
+ public virtual void RequestClose()
{
- /// <summary>
- /// Gets or sets a unique identifier to represent this connection.
- /// </summary>
- public abstract string Id { get; set; }
-
- /// <summary>
- /// Gets or sets the IP address of the remote target. Can be null.
- /// </summary>
- public abstract IPAddress? RemoteIpAddress { get; set; }
-
- /// <summary>
- /// Gets or sets the port of the remote target.
- /// </summary>
- public abstract int RemotePort { get; set; }
-
- /// <summary>
- /// Gets or sets the IP address of the local host.
- /// </summary>
- public abstract IPAddress? LocalIpAddress { get; set; }
-
- /// <summary>
- /// Gets or sets the port of the local host.
- /// </summary>
- public abstract int LocalPort { get; set; }
-
- /// <summary>
- /// Gets or sets the client certificate.
- /// </summary>
- public abstract X509Certificate2? ClientCertificate { get; set; }
-
- /// <summary>
- /// Retrieves the client certificate.
- /// </summary>
- /// <returns>Asynchronously returns an <see cref="X509Certificate2" />. Can be null.</returns>
- public abstract Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken = new CancellationToken());
-
- /// <summary>
- /// Close connection gracefully.
- /// </summary>
- public virtual void RequestClose()
- {
-
- }
+
}
}
diff --git a/src/Http/Http.Abstractions/src/CookieBuilder.cs b/src/Http/Http.Abstractions/src/CookieBuilder.cs
index e92e2ffaa8..c34dd22090 100644
--- a/src/Http/Http.Abstractions/src/CookieBuilder.cs
+++ b/src/Http/Http.Abstractions/src/CookieBuilder.cs
@@ -4,111 +4,110 @@
using System;
using Microsoft.AspNetCore.Http.Abstractions;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Defines settings used to create a cookie.
+/// </summary>
+public class CookieBuilder
{
+ private string? _name;
+
/// <summary>
- /// Defines settings used to create a cookie.
+ /// The name of the cookie.
/// </summary>
- public class CookieBuilder
+ public virtual string? Name
{
- 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));
- }
+ 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 <see cref="CookieOptions.Path"/>.
- /// </remarks>
- public virtual string? Path { get; set; }
+ /// <summary>
+ /// The cookie path.
+ /// </summary>
+ /// <remarks>
+ /// Determines the value that will set on <see 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 <see cref="CookieOptions.Domain"/>.
- /// </remarks>
- public virtual string? Domain { get; set; }
+ /// <summary>
+ /// The domain to associate the cookie with.
+ /// </summary>
+ /// <remarks>
+ /// Determines the value that will set on <see 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 <see cref="CookieOptions.HttpOnly"/>.
- /// </remarks>
- public virtual bool HttpOnly { get; set; }
+ /// <summary>
+ /// Indicates whether a cookie is accessible by client-side script.
+ /// </summary>
+ /// <remarks>
+ /// Determines the value that will set on <see cref="CookieOptions.HttpOnly"/>.
+ /// </remarks>
+ public virtual bool HttpOnly { get; set; }
- /// <summary>
- /// The SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.Unspecified"/>
- /// </summary>
- /// <remarks>
- /// Determines the value that will set on <see cref="CookieOptions.SameSite"/>.
- /// </remarks>
- public virtual SameSiteMode SameSite { get; set; } = SameSiteMode.Unspecified;
+ /// <summary>
+ /// The SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.Unspecified"/>
+ /// </summary>
+ /// <remarks>
+ /// Determines the value that will set on <see cref="CookieOptions.SameSite"/>.
+ /// </remarks>
+ public virtual SameSiteMode SameSite { get; set; } = SameSiteMode.Unspecified;
- /// <summary>
- /// The policy that will be used to determine <see 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>
+ /// The policy that will be used to determine <see 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 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>
+ /// 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>
+ /// 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"/>.
+ /// </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 <see cref="CookieOptions.Expires" />.</param>
- /// <returns>The cookie options.</returns>
- public virtual CookieOptions Build(HttpContext context, DateTimeOffset expiresFrom)
+ /// <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 <see cref="CookieOptions.Expires" />.</param>
+ /// <returns>The cookie options.</returns>
+ public virtual CookieOptions Build(HttpContext context, DateTimeOffset expiresFrom)
+ {
+ if (context == null)
{
- 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.GetValueOrDefault()) : default(DateTimeOffset?)
- };
+ 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.GetValueOrDefault()) : default(DateTimeOffset?)
+ };
}
}
diff --git a/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs b/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs
index eec5f18dd0..91107fb163 100644
--- a/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs
+++ b/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs
@@ -1,34 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Determines how cookie security properties are set.
+/// </summary>
+public enum CookieSecurePolicy
{
/// <summary>
- /// Determines how cookie security properties are set.
+ /// 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 value 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>
- 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 value 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,
+ 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 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,
- }
+ /// <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/EndpointBuilder.cs b/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs
index 448b80c087..32e395098c 100644
--- a/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/EndpointBuilder.cs
@@ -4,32 +4,31 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// A base class for building an new <see cref="Endpoint"/>.
+/// </summary>
+public abstract class EndpointBuilder
{
/// <summary>
- /// A base class for building an new <see cref="Endpoint"/>.
+ /// Gets or sets the delegate used to process requests for the endpoint.
/// </summary>
- public abstract class EndpointBuilder
- {
- /// <summary>
- /// Gets or sets the delegate used to process requests for the endpoint.
- /// </summary>
- public RequestDelegate? RequestDelegate { get; set; }
+ public RequestDelegate? RequestDelegate { get; set; }
- /// <summary>
- /// Gets or sets the informational display name of this endpoint.
- /// </summary>
- public string? DisplayName { get; set; }
+ /// <summary>
+ /// Gets or sets the informational display name of this endpoint.
+ /// </summary>
+ public string? DisplayName { get; set; }
- /// <summary>
- /// Gets the collection of metadata associated with this endpoint.
- /// </summary>
- public IList<object> Metadata { get; } = new List<object>();
+ /// <summary>
+ /// Gets the collection of metadata associated with this endpoint.
+ /// </summary>
+ public IList<object> Metadata { get; } = new List<object>();
- /// <summary>
- /// Creates an instance of <see cref="Endpoint"/> from the <see cref="EndpointBuilder"/>.
- /// </summary>
- /// <returns>The created <see cref="Endpoint"/>.</returns>
- public abstract Endpoint Build();
- }
+ /// <summary>
+ /// Creates an instance of <see cref="Endpoint"/> from the <see cref="EndpointBuilder"/>.
+ /// </summary>
+ /// <returns>The created <see cref="Endpoint"/>.</returns>
+ public abstract Endpoint Build();
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs
index 598ae4ef39..e80298081c 100644
--- a/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs
@@ -3,59 +3,58 @@
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Contains extension methods for modifying an <see cref="IHeaderDictionary"/> instance.
+/// </summary>
+public static class HeaderDictionaryExtensions
{
/// <summary>
- /// Contains extension methods for modifying an <see cref="IHeaderDictionary"/> instance.
+ /// Add new values. Each item remains a separate array entry.
/// </summary>
- public static class HeaderDictionaryExtensions
+ /// <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)
{
- /// <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);
- }
+ 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>
+ /// 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)
- {
- // GetHeaderSplit will return only non-null elements of the given IHeaderDictionary.
+ /// <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)
+ {
+ // GetHeaderSplit will return only non-null elements of the given IHeaderDictionary.
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
- return ParsingHelpers.GetHeaderSplit(headers, key).ToArray();
+ return ParsingHelpers.GetHeaderSplit(headers, key).ToArray();
#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type.
- }
+ }
- /// <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);
- }
+ /// <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
index 1e0ec65c51..e95847ab2b 100644
--- a/src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.cs
@@ -8,143 +8,142 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Convenience methods for writing to the response.
+/// </summary>
+public static class HttpResponseWritingExtensions
{
+ private const int UTF8MaxByteLength = 6;
+
/// <summary>
- /// Convenience methods for writing to the response.
+ /// Writes the given text to the response body. UTF-8 encoding will be used.
/// </summary>
- public static class HttpResponseWritingExtensions
+ /// <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>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsync(this HttpResponse response, string text, CancellationToken cancellationToken = default(CancellationToken))
{
- private const int UTF8MaxByteLength = 6;
-
- /// <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>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsync(this HttpResponse response, string text, CancellationToken cancellationToken = default(CancellationToken))
+ if (response == null)
{
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
+ throw new ArgumentNullException(nameof(response));
+ }
- if (text == null)
- {
- throw new ArgumentNullException(nameof(text));
- }
+ if (text == null)
+ {
+ throw new ArgumentNullException(nameof(text));
+ }
+
+ return response.WriteAsync(text, Encoding.UTF8, cancellationToken);
+ }
- 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>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsync(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
}
- /// <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>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsync(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken))
+ if (text == null)
{
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
+ throw new ArgumentNullException(nameof(text));
+ }
- if (text == null)
- {
- throw new ArgumentNullException(nameof(text));
- }
+ if (encoding == null)
+ {
+ throw new ArgumentNullException(nameof(encoding));
+ }
- if (encoding == null)
+ // Need to call StartAsync before GetMemory/GetSpan
+ if (!response.HasStarted)
+ {
+ var startAsyncTask = response.StartAsync(cancellationToken);
+ if (!startAsyncTask.IsCompletedSuccessfully)
{
- throw new ArgumentNullException(nameof(encoding));
+ return StartAndWriteAsyncAwaited(response, text, encoding, cancellationToken, startAsyncTask);
}
+ }
- // Need to call StartAsync before GetMemory/GetSpan
- if (!response.HasStarted)
- {
- var startAsyncTask = response.StartAsync(cancellationToken);
- if (!startAsyncTask.IsCompletedSuccessfully)
- {
- return StartAndWriteAsyncAwaited(response, text, encoding, cancellationToken, startAsyncTask);
- }
- }
+ Write(response, text, encoding);
- Write(response, text, encoding);
+ var flushAsyncTask = response.BodyWriter.FlushAsync(cancellationToken);
+ if (flushAsyncTask.IsCompletedSuccessfully)
+ {
+ // Most implementations of ValueTask reset state in GetResult, so call it before returning a completed task.
+ flushAsyncTask.GetAwaiter().GetResult();
+ return Task.CompletedTask;
+ }
- var flushAsyncTask = response.BodyWriter.FlushAsync(cancellationToken);
- if (flushAsyncTask.IsCompletedSuccessfully)
- {
- // Most implementations of ValueTask reset state in GetResult, so call it before returning a completed task.
- flushAsyncTask.GetAwaiter().GetResult();
- return Task.CompletedTask;
- }
+ return flushAsyncTask.AsTask();
+ }
- return flushAsyncTask.AsTask();
- }
+ private static async Task StartAndWriteAsyncAwaited(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken, Task startAsyncTask)
+ {
+ await startAsyncTask;
+ Write(response, text, encoding);
+ await response.BodyWriter.FlushAsync(cancellationToken);
+ }
+
+ private static void Write(this HttpResponse response, string text, Encoding encoding)
+ {
+ var minimumByteSize = GetEncodingMaxByteSize(encoding);
+ var pipeWriter = response.BodyWriter;
+ var encodedLength = encoding.GetByteCount(text);
+ var destination = pipeWriter.GetSpan(minimumByteSize);
- private static async Task StartAndWriteAsyncAwaited(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken, Task startAsyncTask)
+ if (encodedLength <= destination.Length)
{
- await startAsyncTask;
- Write(response, text, encoding);
- await response.BodyWriter.FlushAsync(cancellationToken);
+ // Just call Encoding.GetBytes if everything will fit into a single segment.
+ var bytesWritten = encoding.GetBytes(text, destination);
+ pipeWriter.Advance(bytesWritten);
}
-
- private static void Write(this HttpResponse response, string text, Encoding encoding)
+ else
{
- var minimumByteSize = GetEncodingMaxByteSize(encoding);
- var pipeWriter = response.BodyWriter;
- var encodedLength = encoding.GetByteCount(text);
- var destination = pipeWriter.GetSpan(minimumByteSize);
-
- if (encodedLength <= destination.Length)
- {
- // Just call Encoding.GetBytes if everything will fit into a single segment.
- var bytesWritten = encoding.GetBytes(text, destination);
- pipeWriter.Advance(bytesWritten);
- }
- else
- {
- WriteMultiSegmentEncoded(pipeWriter, text, encoding, destination, encodedLength, minimumByteSize);
- }
+ WriteMultiSegmentEncoded(pipeWriter, text, encoding, destination, encodedLength, minimumByteSize);
}
+ }
- private static int GetEncodingMaxByteSize(Encoding encoding)
+ private static int GetEncodingMaxByteSize(Encoding encoding)
+ {
+ if (encoding == Encoding.UTF8)
{
- if (encoding == Encoding.UTF8)
- {
- return UTF8MaxByteLength;
- }
-
- return encoding.GetMaxByteCount(1);
+ return UTF8MaxByteLength;
}
- private static void WriteMultiSegmentEncoded(PipeWriter writer, string text, Encoding encoding, Span<byte> destination, int encodedLength, int minimumByteSize)
+ return encoding.GetMaxByteCount(1);
+ }
+
+ private static void WriteMultiSegmentEncoded(PipeWriter writer, string text, Encoding encoding, Span<byte> destination, int encodedLength, int minimumByteSize)
+ {
+ var encoder = encoding.GetEncoder();
+ var source = text.AsSpan();
+ var completed = false;
+ var totalBytesUsed = 0;
+
+ // This may be a bug, but encoder.Convert returns completed = true for UTF7 too early.
+ // Therefore, we check encodedLength - totalBytesUsed too.
+ while (!completed || encodedLength - totalBytesUsed != 0)
{
- var encoder = encoding.GetEncoder();
- var source = text.AsSpan();
- var completed = false;
- var totalBytesUsed = 0;
-
- // This may be a bug, but encoder.Convert returns completed = true for UTF7 too early.
- // Therefore, we check encodedLength - totalBytesUsed too.
- while (!completed || encodedLength - totalBytesUsed != 0)
- {
- // 'text' is a complete string, the converter should always flush its buffer.
- encoder.Convert(source, destination, flush: true, out var charsUsed, out var bytesUsed, out completed);
- totalBytesUsed += bytesUsed;
+ // 'text' is a complete string, the converter should always flush its buffer.
+ encoder.Convert(source, destination, flush: true, out var charsUsed, out var bytesUsed, out completed);
+ totalBytesUsed += bytesUsed;
- writer.Advance(bytesUsed);
- source = source.Slice(charsUsed);
+ writer.Advance(bytesUsed);
+ source = source.Slice(charsUsed);
- destination = writer.GetSpan(minimumByteSize);
- }
+ destination = writer.GetSpan(minimumByteSize);
}
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/IEndpointConventionBuilder.cs b/src/Http/Http.Abstractions/src/Extensions/IEndpointConventionBuilder.cs
index e8053aa5ac..5dba459582 100644
--- a/src/Http/Http.Abstractions/src/Extensions/IEndpointConventionBuilder.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/IEndpointConventionBuilder.cs
@@ -3,20 +3,19 @@
using System;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Builds conventions that will be used for customization of <see cref="EndpointBuilder"/> instances.
+/// </summary>
+/// <remarks>
+/// This interface is used at application startup to customize endpoints for the application.
+/// </remarks>
+public interface IEndpointConventionBuilder
{
/// <summary>
- /// Builds conventions that will be used for customization of <see cref="EndpointBuilder"/> instances.
+ /// Adds the specified convention to the builder. Conventions are used to customize <see cref="EndpointBuilder"/> instances.
/// </summary>
- /// <remarks>
- /// This interface is used at application startup to customize endpoints for the application.
- /// </remarks>
- public interface IEndpointConventionBuilder
- {
- /// <summary>
- /// Adds the specified convention to the builder. Conventions are used to customize <see cref="EndpointBuilder"/> instances.
- /// </summary>
- /// <param name="convention">The convention to add to the builder.</param>
- void Add(Action<EndpointBuilder> convention);
- }
+ /// <param name="convention">The convention to add to the builder.</param>
+ void Add(Action<EndpointBuilder> convention);
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs
index 7842dbdeaf..41da2b7b5d 100644
--- a/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs
@@ -2,80 +2,79 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder.Extensions;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder;
-namespace Microsoft.AspNetCore.Builder
+/// <summary>
+/// Extension methods for the <see cref="MapMiddleware"/>.
+/// </summary>
+public static class MapExtensions
{
/// <summary>
- /// Extension methods for the <see cref="MapMiddleware"/>.
+ /// 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, string pathMatch, Action<IApplicationBuilder> configuration)
+ {
+ return Map(app, pathMatch, preserveMatchedPathSegment: false, configuration);
+ }
+
+ /// <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)
+ {
+ return Map(app, pathMatch, preserveMatchedPathSegment: false, configuration);
+ }
+
+ /// <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>
- public static class MapExtensions
+ /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+ /// <param name="pathMatch">The request path to match.</param>
+ /// <param name="preserveMatchedPathSegment">if false, matched path would be removed from Request.Path and added to Request.PathBase.</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, bool preserveMatchedPathSegment, Action<IApplicationBuilder> configuration)
{
- /// <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, string pathMatch, Action<IApplicationBuilder> configuration)
+ if (app == null)
{
- return Map(app, pathMatch, preserveMatchedPathSegment: false, configuration);
+ throw new ArgumentNullException(nameof(app));
}
- /// <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 (configuration == null)
{
- return Map(app, pathMatch, preserveMatchedPathSegment: false, configuration);
+ throw new ArgumentNullException(nameof(configuration));
}
- /// <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="preserveMatchedPathSegment">if false, matched path would be removed from Request.Path and added to Request.PathBase.</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, bool preserveMatchedPathSegment, Action<IApplicationBuilder> configuration)
+ if (pathMatch.HasValue && pathMatch.Value!.EndsWith("/", StringComparison.Ordinal))
{
- 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));
- }
+ throw new ArgumentException("The path must not end with a '/'", nameof(pathMatch));
+ }
- // create branch
- var branchBuilder = app.New();
- configuration(branchBuilder);
- var branch = branchBuilder.Build();
+ // create branch
+ var branchBuilder = app.New();
+ configuration(branchBuilder);
+ var branch = branchBuilder.Build();
- var options = new MapOptions
- {
- Branch = branch,
- PathMatch = pathMatch,
- PreserveMatchedPathSegment = preserveMatchedPathSegment
- };
- return app.Use(next => new MapMiddleware(next, options).Invoke);
- }
+ var options = new MapOptions
+ {
+ Branch = branch,
+ PathMatch = pathMatch,
+ PreserveMatchedPathSegment = preserveMatchedPathSegment
+ };
+ return app.Use(next => new MapMiddleware(next, options).Invoke);
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs b/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs
index 35b25f40e6..b8d2db196d 100644
--- a/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs
@@ -5,83 +5,82 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+/// <summary>
+/// Represents 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>
- /// Represents a middleware that maps a request path to a sub-request pipeline.
+ /// Creates a new instance of <see cref="MapMiddleware"/>.
/// </summary>
- public class MapMiddleware
+ /// <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)
{
- private readonly RequestDelegate _next;
- private readonly MapOptions _options;
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
- /// <summary>
- /// Creates a new instance 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 (options == null)
{
- if (next == null)
- {
- throw new ArgumentNullException(nameof(next));
- }
+ throw new ArgumentNullException(nameof(options));
+ }
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ if (options.Branch == null)
+ {
+ throw new ArgumentException("Branch not set on options.", nameof(options));
+ }
- if (options.Branch == null)
- {
- throw new ArgumentException("Branch not set on options.", nameof(options));
- }
+ _next = next;
+ _options = 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 Task Invoke(HttpContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
}
- /// <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 Task Invoke(HttpContext context)
+ if (context.Request.Path.StartsWithSegments(_options.PathMatch, out var matchedPath, out var remainingPath))
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (context.Request.Path.StartsWithSegments(_options.PathMatch, out var matchedPath, out var remainingPath))
+ if (!_options.PreserveMatchedPathSegment)
{
- if (!_options.PreserveMatchedPathSegment)
- {
- return InvokeCore(context, matchedPath, remainingPath);
- }
- return _options.Branch!(context);
+ return InvokeCore(context, matchedPath, remainingPath);
}
- return _next(context);
+ return _options.Branch!(context);
}
+ return _next(context);
+ }
- private async Task InvokeCore(HttpContext context, string matchedPath, string remainingPath)
- {
- var path = context.Request.Path;
- var pathBase = context.Request.PathBase;
+ private async Task InvokeCore(HttpContext context, string matchedPath, string remainingPath)
+ {
+ var path = context.Request.Path;
+ var pathBase = context.Request.PathBase;
- // Update the path
- context.Request.PathBase = pathBase.Add(matchedPath);
- context.Request.Path = remainingPath;
+ // Update the path
+ context.Request.PathBase = pathBase.Add(matchedPath);
+ context.Request.Path = remainingPath;
- try
- {
- await _options.Branch!(context);
- }
- finally
- {
- context.Request.PathBase = pathBase;
- context.Request.Path = path;
- }
+ try
+ {
+ await _options.Branch!(context);
+ }
+ finally
+ {
+ context.Request.PathBase = pathBase;
+ context.Request.Path = path;
}
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs b/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs
index efc50a64b3..16afae0b0c 100644
--- a/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs
@@ -3,27 +3,26 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+/// <summary>
+/// Options for the <see cref="MapMiddleware"/>.
+/// </summary>
+public class MapOptions
{
/// <summary>
- /// Options for the <see cref="MapMiddleware"/>.
+ /// The path to match.
/// </summary>
- public class MapOptions
- {
- /// <summary>
- /// The path to match.
- /// </summary>
- public PathString PathMatch { get; set; }
+ public PathString PathMatch { get; set; }
- /// <summary>
- /// The branch taken for a positive match.
- /// </summary>
- public RequestDelegate? Branch { get; set; }
+ /// <summary>
+ /// The branch taken for a positive match.
+ /// </summary>
+ public RequestDelegate? Branch { get; set; }
- /// <summary>
- /// If false, matched path would be removed from Request.Path and added to Request.PathBase
- /// Defaults to false.
- /// </summary>
- public bool PreserveMatchedPathSegment { get; set; }
- }
+ /// <summary>
+ /// If false, matched path would be removed from Request.Path and added to Request.PathBase
+ /// Defaults to false.
+ /// </summary>
+ public bool PreserveMatchedPathSegment { get; set; }
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs
index d58a697ab6..04690cc6f3 100644
--- a/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs
@@ -2,55 +2,54 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder.Extensions;
+using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder
-{
- using Predicate = Func<HttpContext, bool>;
+namespace Microsoft.AspNetCore.Builder;
+
+using Predicate = Func<HttpContext, bool>;
+/// <summary>
+/// Extension methods for the <see cref="MapWhenMiddleware"/>.
+/// </summary>
+public static class MapWhenExtensions
+{
/// <summary>
- /// Extension methods for the <see cref="MapWhenMiddleware"/>.
+ /// Branches the request pipeline based on the result of the given predicate.
/// </summary>
- public static class MapWhenExtensions
+ /// <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)
{
- /// <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)
{
- 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);
+ 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
index 9764055696..b75837b88c 100644
--- a/src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs
@@ -5,64 +5,63 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+/// <summary>
+/// Represents 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>
- /// Represents a middleware that runs a sub-request pipeline when a given predicate is matched.
+ /// Creates a new instance of <see cref="MapWhenMiddleware"/>.
/// </summary>
- public class MapWhenMiddleware
+ /// <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)
{
- private readonly RequestDelegate _next;
- private readonly MapWhenOptions _options;
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
- /// <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 (options == null)
{
- if (next == null)
- {
- throw new ArgumentNullException(nameof(next));
- }
+ throw new ArgumentNullException(nameof(options));
+ }
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ if (options.Predicate == null)
+ {
+ throw new ArgumentException("Predicate not set on options.", nameof(options));
+ }
- if (options.Predicate == null)
- {
- throw new ArgumentException("Predicate not set on options.", nameof(options));
- }
+ if (options.Branch == null)
+ {
+ throw new ArgumentException("Branch not set on options.", nameof(options));
+ }
- if (options.Branch == null)
- {
- throw new ArgumentException("Branch not set on options.", nameof(options));
- }
+ _next = next;
+ _options = 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 Task Invoke(HttpContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
}
- /// <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 Task Invoke(HttpContext context)
+ if (_options.Predicate!(context))
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (_options.Predicate!(context))
- {
- return _options.Branch!(context);
- }
- return _next(context);
+ return _options.Branch!(context);
}
+ return _next(context);
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs b/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs
index 237a54b650..a06528eae8 100644
--- a/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs
@@ -4,38 +4,37 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+/// <summary>
+/// Options for the <see cref="MapWhenMiddleware"/>.
+/// </summary>
+public class MapWhenOptions
{
+ private Func<HttpContext, bool>? _predicate;
+
/// <summary>
- /// Options for the <see cref="MapWhenMiddleware"/>.
+ /// The user callback that determines if the branch should be taken.
/// </summary>
- public class MapWhenOptions
+ public Func<HttpContext, bool>? Predicate
{
- private Func<HttpContext, bool>? _predicate;
-
- /// <summary>
- /// The user callback that determines if the branch should be taken.
- /// </summary>
- public Func<HttpContext, bool>? Predicate
+ get
{
- get
+ return _predicate;
+ }
+ set
+ {
+ if (value == null)
{
- return _predicate;
+ throw new ArgumentNullException(nameof(value));
}
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
- _predicate = value;
- }
+ _predicate = value;
}
-
- /// <summary>
- /// The branch taken for a positive match.
- /// </summary>
- public RequestDelegate? Branch { get; set; }
}
+
+ /// <summary>
+ /// The branch taken for a positive match.
+ /// </summary>
+ public RequestDelegate? Branch { get; set; }
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs
index bd48fc8378..c1c638d04d 100644
--- a/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/RequestTrailerExtensions.cs
@@ -6,60 +6,59 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// HttpRequest extensions for working with request trailing headers.
+/// </summary>
+public static class RequestTrailerExtensions
{
/// <summary>
- /// HttpRequest extensions for working with request trailing headers.
+ /// Gets the request "Trailer" header that lists which trailers to expect after the body.
/// </summary>
- public static class RequestTrailerExtensions
+ /// <param name="request"></param>
+ /// <returns></returns>
+ public static StringValues GetDeclaredTrailers(this HttpRequest request)
{
- /// <summary>
- /// Gets the request "Trailer" header that lists which trailers to expect after the body.
- /// </summary>
- /// <param name="request"></param>
- /// <returns></returns>
- public static StringValues GetDeclaredTrailers(this HttpRequest request)
- {
- return request.Headers.GetCommaSeparatedValues(HeaderNames.Trailer);
- }
+ return request.Headers.GetCommaSeparatedValues(HeaderNames.Trailer);
+ }
- /// <summary>
- /// Indicates if the request supports receiving trailer headers.
- /// </summary>
- /// <param name="request"></param>
- /// <returns></returns>
- public static bool SupportsTrailers(this HttpRequest request)
- {
- return request.HttpContext.Features.Get<IHttpRequestTrailersFeature>() != null;
- }
+ /// <summary>
+ /// Indicates if the request supports receiving trailer headers.
+ /// </summary>
+ /// <param name="request"></param>
+ /// <returns></returns>
+ public static bool SupportsTrailers(this HttpRequest request)
+ {
+ return request.HttpContext.Features.Get<IHttpRequestTrailersFeature>() != null;
+ }
- /// <summary>
- /// Checks if the request supports trailers and they are available to be read now.
- /// This does not mean that there are any trailers to read.
- /// </summary>
- /// <param name="request"></param>
- /// <returns></returns>
- public static bool CheckTrailersAvailable(this HttpRequest request)
- {
- return request.HttpContext.Features.Get<IHttpRequestTrailersFeature>()?.Available == true;
- }
+ /// <summary>
+ /// Checks if the request supports trailers and they are available to be read now.
+ /// This does not mean that there are any trailers to read.
+ /// </summary>
+ /// <param name="request"></param>
+ /// <returns></returns>
+ public static bool CheckTrailersAvailable(this HttpRequest request)
+ {
+ return request.HttpContext.Features.Get<IHttpRequestTrailersFeature>()?.Available == true;
+ }
- /// <summary>
- /// Gets the requested trailing header from the response. Check <see cref="SupportsTrailers"/>
- /// or a NotSupportedException may be thrown.
- /// Check <see cref="CheckTrailersAvailable" /> or an InvalidOperationException may be thrown.
- /// </summary>
- /// <param name="request"></param>
- /// <param name="trailerName"></param>
- public static StringValues GetTrailer(this HttpRequest request, string trailerName)
+ /// <summary>
+ /// Gets the requested trailing header from the response. Check <see cref="SupportsTrailers"/>
+ /// or a NotSupportedException may be thrown.
+ /// Check <see cref="CheckTrailersAvailable" /> or an InvalidOperationException may be thrown.
+ /// </summary>
+ /// <param name="request"></param>
+ /// <param name="trailerName"></param>
+ public static StringValues GetTrailer(this HttpRequest request, string trailerName)
+ {
+ var feature = request.HttpContext.Features.Get<IHttpRequestTrailersFeature>();
+ if (feature == null)
{
- var feature = request.HttpContext.Features.Get<IHttpRequestTrailersFeature>();
- if (feature == null)
- {
- throw new NotSupportedException("This request does not support trailers.");
- }
-
- return feature.Trailers[trailerName];
+ throw new NotSupportedException("This request does not support trailers.");
}
+
+ return feature.Trailers[trailerName];
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs
index 704cce3389..af2cd58069 100644
--- a/src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/ResponseTrailerExtensions.cs
@@ -6,51 +6,50 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Contains extension methods for modifying the `Trailer` response header
+/// and trailing headers in an <see cref="HttpResponse" />.
+/// </summary>
+public static class ResponseTrailerExtensions
{
/// <summary>
- /// Contains extension methods for modifying the `Trailer` response header
- /// and trailing headers in an <see cref="HttpResponse" />.
+ /// Adds the given trailer name to the 'Trailer' response header. This must happen before the response headers are sent.
/// </summary>
- public static class ResponseTrailerExtensions
+ /// <param name="response"></param>
+ /// <param name="trailerName"></param>
+ public static void DeclareTrailer(this HttpResponse response, string trailerName)
{
- /// <summary>
- /// Adds the given trailer name to the 'Trailer' response header. This must happen before the response headers are sent.
- /// </summary>
- /// <param name="response"></param>
- /// <param name="trailerName"></param>
- public static void DeclareTrailer(this HttpResponse response, string trailerName)
- {
- response.Headers.AppendCommaSeparatedValues(HeaderNames.Trailer, trailerName);
- }
+ response.Headers.AppendCommaSeparatedValues(HeaderNames.Trailer, trailerName);
+ }
- /// <summary>
- /// Indicates if the server supports sending trailer headers for this response.
- /// </summary>
- /// <param name="response"></param>
- /// <returns></returns>
- public static bool SupportsTrailers(this HttpResponse response)
- {
- var feature = response.HttpContext.Features.Get<IHttpResponseTrailersFeature>();
- return feature?.Trailers != null && !feature.Trailers.IsReadOnly;
- }
+ /// <summary>
+ /// Indicates if the server supports sending trailer headers for this response.
+ /// </summary>
+ /// <param name="response"></param>
+ /// <returns></returns>
+ public static bool SupportsTrailers(this HttpResponse response)
+ {
+ var feature = response.HttpContext.Features.Get<IHttpResponseTrailersFeature>();
+ return feature?.Trailers != null && !feature.Trailers.IsReadOnly;
+ }
- /// <summary>
- /// Adds the given trailer header to the trailers collection to be sent at the end of the response body.
- /// Check <see cref="SupportsTrailers" /> or an InvalidOperationException may be thrown.
- /// </summary>
- /// <param name="response"></param>
- /// <param name="trailerName"></param>
- /// <param name="trailerValues"></param>
- public static void AppendTrailer(this HttpResponse response, string trailerName, StringValues trailerValues)
+ /// <summary>
+ /// Adds the given trailer header to the trailers collection to be sent at the end of the response body.
+ /// Check <see cref="SupportsTrailers" /> or an InvalidOperationException may be thrown.
+ /// </summary>
+ /// <param name="response"></param>
+ /// <param name="trailerName"></param>
+ /// <param name="trailerValues"></param>
+ public static void AppendTrailer(this HttpResponse response, string trailerName, StringValues trailerValues)
+ {
+ var feature = response.HttpContext.Features.Get<IHttpResponseTrailersFeature>();
+ if (feature?.Trailers == null || feature.Trailers.IsReadOnly)
{
- var feature = response.HttpContext.Features.Get<IHttpResponseTrailersFeature>();
- if (feature?.Trailers == null || feature.Trailers.IsReadOnly)
- {
- throw new InvalidOperationException("Trailers are not supported for this response.");
- }
-
- feature.Trailers.Append(trailerName, trailerValues);
+ throw new InvalidOperationException("Trailers are not supported for this response.");
}
+
+ feature.Trailers.Append(trailerName, trailerValues);
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs
index f4687b8972..cb6b899ff5 100644
--- a/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs
@@ -4,31 +4,30 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Extension methods for adding terminal middleware.
+/// </summary>
+public static class RunExtensions
{
/// <summary>
- /// Extension methods for adding terminal middleware.
+ /// Adds a terminal middleware delegate to the application's request pipeline.
/// </summary>
- public static class RunExtensions
+ /// <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)
{
- /// <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)
{
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
-
- if (handler == null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
+ throw new ArgumentNullException(nameof(app));
+ }
- app.Use(_ => handler);
+ 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
index 4e0b1a5784..b8b7381e89 100644
--- a/src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs
@@ -5,51 +5,50 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Extension methods for adding middleware.
+/// </summary>
+public static class UseExtensions
{
/// <summary>
- /// Extension methods for adding middleware.
+ /// Adds a middleware delegate defined in-line to the application's request pipeline.
+ /// If you aren't calling the next function, use <see cref="RunExtensions.Run(IApplicationBuilder, RequestDelegate)"/> instead.
+ /// <para>
+ /// Prefer using <see cref="Use(IApplicationBuilder, Func{HttpContext, RequestDelegate, Task})"/> for better performance as shown below:
+ /// <code>
+ /// app.Use((context, next) =>
+ /// {
+ /// return next(context);
+ /// });
+ /// </code>
+ /// </para>
/// </summary>
- public static class UseExtensions
+ /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+ /// <param name="middleware">A function that handles the request and 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)
{
- /// <summary>
- /// Adds a middleware delegate defined in-line to the application's request pipeline.
- /// If you aren't calling the next function, use <see cref="RunExtensions.Run(IApplicationBuilder, RequestDelegate)"/> instead.
- /// <para>
- /// Prefer using <see cref="Use(IApplicationBuilder, Func{HttpContext, RequestDelegate, Task})"/> for better performance as shown below:
- /// <code>
- /// app.Use((context, next) =>
- /// {
- /// return next(context);
- /// });
- /// </code>
- /// </para>
- /// </summary>
- /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
- /// <param name="middleware">A function that handles the request and 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 app.Use(next =>
+ return context =>
{
- return context =>
- {
- Func<Task> simpleNext = () => next(context);
- return middleware(context, simpleNext);
- };
- });
- }
+ Func<Task> simpleNext = () => next(context);
+ return middleware(context, simpleNext);
+ };
+ });
+ }
- /// <summary>
- /// Adds a middleware delegate defined in-line to the application's request pipeline.
- /// If you aren't calling the next function, use <see cref="RunExtensions.Run(IApplicationBuilder, RequestDelegate)"/> instead.
- /// </summary>
- /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
- /// <param name="middleware">A function that handles the request and calls the given next function.</param>
- /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
- public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, RequestDelegate, Task> middleware)
- {
- return app.Use(next => context => middleware(context, next));
- }
+ /// <summary>
+ /// Adds a middleware delegate defined in-line to the application's request pipeline.
+ /// If you aren't calling the next function, use <see cref="RunExtensions.Run(IApplicationBuilder, RequestDelegate)"/> instead.
+ /// </summary>
+ /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+ /// <param name="middleware">A function that handles the request and calls the given next function.</param>
+ /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
+ public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, RequestDelegate, Task> middleware)
+ {
+ return app.Use(next => context => middleware(context, next));
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs
index d6b4e2d944..e5e54131b9 100644
--- a/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs
@@ -11,218 +11,217 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Abstractions;
using Microsoft.Extensions.Internal;
-namespace Microsoft.AspNetCore.Builder
+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)!;
+
+ // We're going to keep all public constructors and public methods on middleware
+ private const DynamicallyAccessedMemberTypes MiddlewareAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;
+
+ /// <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<[DynamicallyAccessedMembers(MiddlewareAccessibility)] TMiddleware>(this IApplicationBuilder app, params object?[] args)
+ {
+ return app.UseMiddleware(typeof(TMiddleware), args);
+ }
+
/// <summary>
- /// Extension methods for adding typed middleware.
+ /// Adds a middleware type to the application's request pipeline.
/// </summary>
- public static class UseMiddlewareExtensions
+ /// <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, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
{
- internal const string InvokeMethodName = "Invoke";
- internal const string InvokeAsyncMethodName = "InvokeAsync";
-
- private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static)!;
-
- // We're going to keep all public constructors and public methods on middleware
- private const DynamicallyAccessedMemberTypes MiddlewareAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods;
-
- /// <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<[DynamicallyAccessedMembers(MiddlewareAccessibility)]TMiddleware>(this IApplicationBuilder app, params object?[] args)
+ if (typeof(IMiddleware).IsAssignableFrom(middleware))
{
- return app.UseMiddleware(typeof(TMiddleware), args);
+ // 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);
}
- /// <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, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)
+ var applicationServices = app.ApplicationServices;
+ return app.Use(next =>
{
- if (typeof(IMiddleware).IsAssignableFrom(middleware))
- {
- // 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)));
- }
+ 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();
- return UseMiddlewareInterface(app, middleware);
+ if (invokeMethods.Length > 1)
+ {
+ throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
}
- var applicationServices = app.ApplicationServices;
- return app.Use(next =>
+ if (invokeMethods.Length == 0)
{
- 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();
+ throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
+ }
- if (invokeMethods.Length > 1)
- {
- throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
- }
+ var methodInfo = invokeMethods[0];
+ if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
+ {
+ throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
+ }
- if (invokeMethods.Length == 0)
- {
- throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
- }
+ var parameters = methodInfo.GetParameters();
+ if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
+ {
+ throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
+ }
- var methodInfo = invokeMethods[0];
- if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
- {
- throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
- }
+ 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 parameters = methodInfo.GetParameters();
- if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
- {
- throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
- }
+ var factory = Compile<object>(methodInfo, parameters);
- 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 context =>
+ {
+ var serviceProvider = context.RequestServices ?? applicationServices;
+ if (serviceProvider == null)
{
- return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
+ throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
}
- 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);
- };
- });
- }
+ return factory(instance, context, serviceProvider);
+ };
+ });
+ }
- private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType)
+ private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type middlewareType)
+ {
+ return app.Use(next =>
{
- return app.Use(next =>
+ return async context =>
{
- return async context =>
+ var middlewareFactory = (IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));
+ if (middlewareFactory == null)
{
- 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)
- {
+ 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 loggerFactory)
- // {
- //
- // }
- // }
- //
-
- // 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)
+ try
{
- throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
+ await middleware.InvokeAsync(context, next);
}
-
- var parameterTypeExpression = new Expression[]
+ 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 loggerFactory)
+ // {
+ //
+ // }
+ // }
+ //
+
+ // 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);
- }
+ var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
+ methodArguments[i] = Expression.Convert(getServiceCall, parameterType);
+ }
- Expression middlewareInstanceArg = instanceArg;
- if (methodInfo.DeclaringType != null && methodInfo.DeclaringType != typeof(T))
- {
- middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);
- }
+ Expression middlewareInstanceArg = instanceArg;
+ if (methodInfo.DeclaringType != null && methodInfo.DeclaringType != typeof(T))
+ {
+ middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);
+ }
- var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments);
+ var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments);
- var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
+ var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
- return lambda.Compile();
- }
+ return lambda.Compile();
+ }
- private static object GetService(IServiceProvider sp, Type type, Type middleware)
+ private static object GetService(IServiceProvider sp, Type type, Type middleware)
+ {
+ var service = sp.GetService(type);
+ if (service == null)
{
- var service = sp.GetService(type);
- if (service == null)
- {
- throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));
- }
-
- return service;
+ 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
index 0bdf4fcfed..5575806bed 100644
--- a/src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs
@@ -2,37 +2,36 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder.Extensions;
+using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Extension methods for <see cref="IApplicationBuilder"/>.
+/// </summary>
+public static class UsePathBaseExtensions
{
/// <summary>
- /// Extension methods for <see cref="IApplicationBuilder"/>.
+ /// Adds a middleware that extracts the specified path base from request path and postpend it to the request path base.
/// </summary>
- public static class UsePathBaseExtensions
+ /// <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)
{
- /// <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)
{
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
-
- // Strip trailing slashes
- pathBase = pathBase.Value?.TrimEnd('/');
- if (!pathBase.HasValue)
- {
- return app;
- }
+ throw new ArgumentNullException(nameof(app));
+ }
- return app.UseMiddleware<UsePathBaseMiddleware>(pathBase);
+ // 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
index 34ffc5738d..c0e6377cc3 100644
--- a/src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs
@@ -5,72 +5,71 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder.Extensions
+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>
- /// Represents a middleware that extracts the specified path base from request path and postpend it to the request path base.
+ /// Creates a new instance of <see cref="UsePathBaseMiddleware"/>.
/// </summary>
- public class UsePathBaseMiddleware
+ /// <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)
{
- private readonly RequestDelegate _next;
- private readonly PathString _pathBase;
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
- /// <summary>
- /// Creates a new instance 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 (!pathBase.HasValue)
{
- if (next == null)
- {
- throw new ArgumentNullException(nameof(next));
- }
+ throw new ArgumentException($"{nameof(pathBase)} cannot be null or empty.");
+ }
- if (!pathBase.HasValue)
- {
- throw new ArgumentException($"{nameof(pathBase)} cannot be null or empty.");
- }
+ _next = next;
+ _pathBase = pathBase;
+ }
- _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 Task Invoke(HttpContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
}
- /// <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 Task Invoke(HttpContext context)
+ if (context.Request.Path.StartsWithSegments(_pathBase, out var matchedPath, out var remainingPath))
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (context.Request.Path.StartsWithSegments(_pathBase, out var matchedPath, out var remainingPath))
- {
- return InvokeCore(context, matchedPath, remainingPath);
- }
- return _next(context);
+ return InvokeCore(context, matchedPath, remainingPath);
}
+ return _next(context);
+ }
- private async Task InvokeCore(HttpContext context, string matchedPath, string remainingPath)
- {
- var originalPath = context.Request.Path;
- var originalPathBase = context.Request.PathBase;
- context.Request.Path = remainingPath;
- context.Request.PathBase = originalPathBase.Add(matchedPath);
+ private async Task InvokeCore(HttpContext context, string matchedPath, string 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;
- }
+ try
+ {
+ await _next(context);
+ }
+ finally
+ {
+ context.Request.Path = originalPath;
+ context.Request.PathBase = originalPathBase;
}
}
}
diff --git a/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs
index 03117f32ef..0c1097f4a8 100644
--- a/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs
@@ -4,64 +4,63 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Builder
-{
- using Predicate = Func<HttpContext, bool>;
+namespace Microsoft.AspNetCore.Builder;
+
+using Predicate = Func<HttpContext, bool>;
+/// <summary>
+/// Extension methods for <see cref="IApplicationBuilder"/>.
+/// </summary>
+public static class UseWhenExtensions
+{
/// <summary>
- /// Extension methods for <see cref="IApplicationBuilder"/>.
+ /// Conditionally creates a branch in the request pipeline that is rejoined to the main pipeline.
/// </summary>
- public static class UseWhenExtensions
+ /// <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)
{
- /// <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)
{
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
+ throw new ArgumentNullException(nameof(app));
+ }
- if (predicate == null)
- {
- throw new ArgumentNullException(nameof(predicate));
- }
+ if (predicate == null)
+ {
+ throw new ArgumentNullException(nameof(predicate));
+ }
- if (configuration == null)
- {
- throw new ArgumentNullException(nameof(configuration));
- }
+ 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);
+ // 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 =>
- {
+ 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();
+ var branch = branchBuilder.Build();
- return context =>
+ return context =>
+ {
+ if (predicate(context))
{
- if (predicate(context))
- {
- return branch(context);
- }
- else
- {
- return main(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
index 531e3c16be..137db51814 100644
--- a/src/Http/Http.Abstractions/src/FragmentString.cs
+++ b/src/Http/Http.Abstractions/src/FragmentString.cs
@@ -3,165 +3,164 @@
using System;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides correct handling for FragmentString value when needed to generate a URI string
+/// </summary>
+public readonly struct FragmentString : IEquatable<FragmentString>
{
/// <summary>
- /// Provides correct handling for FragmentString value when needed to generate a URI string
+ /// 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>
- public readonly struct FragmentString : IEquatable<FragmentString>
+ /// <param name="value">The fragment string to be assigned to the Value property.</param>
+ public FragmentString(string value)
{
- /// <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] != '#')
{
- if (!string.IsNullOrEmpty(value) && value[0] != '#')
- {
- throw new ArgumentException("The leading '#' must be included for a non-empty fragment.", nameof(value));
- }
- _value = value;
+ 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>
+ /// 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>
+ /// 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 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>
+ /// 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)
+ /// <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))
{
- if (String.IsNullOrEmpty(uriComponent))
- {
- return Empty;
- }
- return new FragmentString(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)
+ /// <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)
{
- 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);
+ throw new ArgumentNullException(nameof(uri));
}
- /// <summary>
- /// Evaluates if the current fragment is equal to another fragment <paramref name="other"/>.
- /// </summary>
- /// <param name="other">A <see cref="FragmentString" /> to compare.</param>
- /// <returns><see langword="true" /> if the fragments are equal.</returns>
- public bool Equals(FragmentString other)
+ string fragmentValue = uri.GetComponents(UriComponents.Fragment, UriFormat.UriEscaped);
+ if (!string.IsNullOrEmpty(fragmentValue))
{
- if (!HasValue && !other.HasValue)
- {
- return true;
- }
- return string.Equals(_value, other._value, StringComparison.Ordinal);
+ fragmentValue = "#" + fragmentValue;
}
+ return new FragmentString(fragmentValue);
+ }
- /// <summary>
- /// Evaluates if the current fragment is equal to an object <paramref name="obj"/>.
- /// </summary>
- /// <param name="obj">An object to compare.</param>
- /// <returns><see langword="true" /> if the fragments are equal.</returns>
- public override bool Equals(object? obj)
+ /// <summary>
+ /// Evaluates if the current fragment is equal to another fragment <paramref name="other"/>.
+ /// </summary>
+ /// <param name="other">A <see cref="FragmentString" /> to compare.</param>
+ /// <returns><see langword="true" /> if the fragments are equal.</returns>
+ public bool Equals(FragmentString other)
+ {
+ if (!HasValue && !other.HasValue)
{
- if (ReferenceEquals(null, obj))
- {
- return !HasValue;
- }
- return obj is FragmentString && Equals((FragmentString)obj);
+ return true;
}
+ return string.Equals(_value, other._value, StringComparison.Ordinal);
+ }
- /// <summary>
- /// Gets a hash code for the value.
- /// </summary>
- /// <returns>The hash code as an <see cref="int"/>.</returns>
- public override int GetHashCode()
+ /// <summary>
+ /// Evaluates if the current fragment is equal to an object <paramref name="obj"/>.
+ /// </summary>
+ /// <param name="obj">An object to compare.</param>
+ /// <returns><see langword="true" /> if the fragments are equal.</returns>
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
{
- return (HasValue ? _value.GetHashCode() : 0);
+ return !HasValue;
}
+ return obj is FragmentString && Equals((FragmentString)obj);
+ }
- /// <summary>
- /// Evaluates if one fragment is equal to another.
- /// </summary>
- /// <param name="left">A <see cref="FragmentString"/> instance.</param>
- /// <param name="right">A <see cref="FragmentString"/> instance.</param>
- /// <returns><see langword="true" /> if the fragments are equal.</returns>
- public static bool operator ==(FragmentString left, FragmentString right)
- {
- return left.Equals(right);
- }
+ /// <summary>
+ /// Gets a hash code for the value.
+ /// </summary>
+ /// <returns>The hash code as an <see cref="int"/>.</returns>
+ public override int GetHashCode()
+ {
+ return (HasValue ? _value.GetHashCode() : 0);
+ }
- /// <summary>
- /// Evalutes if one framgent is not equal to another.
- /// </summary>
- /// <param name="left">A <see cref="FragmentString"/> instance.</param>
- /// <param name="right">A <see cref="FragmentString"/> instance.</param>
- /// <returns><see langword="true" /> if the fragments are not equal.</returns>
- public static bool operator !=(FragmentString left, FragmentString right)
- {
- return !left.Equals(right);
- }
+ /// <summary>
+ /// Evaluates if one fragment is equal to another.
+ /// </summary>
+ /// <param name="left">A <see cref="FragmentString"/> instance.</param>
+ /// <param name="right">A <see cref="FragmentString"/> instance.</param>
+ /// <returns><see langword="true" /> if the fragments are equal.</returns>
+ public static bool operator ==(FragmentString left, FragmentString right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Evalutes if one framgent is not equal to another.
+ /// </summary>
+ /// <param name="left">A <see cref="FragmentString"/> instance.</param>
+ /// <param name="right">A <see cref="FragmentString"/> instance.</param>
+ /// <returns><see langword="true" /> if the fragments are not equal.</returns>
+ 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
index 27b0a2da0f..99e985154e 100644
--- a/src/Http/Http.Abstractions/src/HostString.cs
+++ b/src/Http/Http.Abstractions/src/HostString.cs
@@ -7,384 +7,383 @@ using System.Globalization;
using Microsoft.AspNetCore.Http.Abstractions;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+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 readonly struct HostString : IEquatable<HostString>
{
+ private readonly string _value;
+
/// <summary>
- /// Represents the host portion of a URI can be used to construct URI's properly formatted and encoded for use in
- /// HTTP headers.
+ /// 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>
- public readonly struct HostString : IEquatable<HostString>
+ /// <param name="value"></param>
+ public HostString(string value)
{
- 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;
- }
+ _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)
+ /// <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 (host == null)
{
- if (host == null)
- {
- throw new ArgumentNullException(nameof(host));
- }
-
- 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);
+ throw new ArgumentNullException(nameof(host));
}
- /// <summary>
- /// Returns the original value from the constructor.
- /// </summary>
- public string Value
+ if (port <= 0)
{
- get { return _value; }
+ throw new ArgumentOutOfRangeException(nameof(port), Resources.Exception_PortMustBeGreaterThanZero);
}
- /// <summary>
- /// Returns true if the host is set.
- /// </summary>
- public bool HasValue
+ int index;
+ if (host.IndexOf('[') == -1
+ && (index = host.IndexOf(':')) >= 0
+ && index < host.Length - 1
+ && host.IndexOf(':', index + 1) >= 0)
{
- get { return !string.IsNullOrEmpty(_value); }
+ // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+ host = $"[{host}]";
}
- /// <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>The host portion of the value.</returns>
- public string Host
+ _value = host + ":" + port.ToString(CultureInfo.InvariantCulture);
+ }
+
+ /// <summary>
+ /// Returns the original value from the constructor.
+ /// </summary>
+ public string Value
+ {
+ get { return _value; }
+ }
+
+ /// <summary>
+ /// Returns true if the host is set.
+ /// </summary>
+ 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>The host portion of the value.</returns>
+ public string Host
+ {
+ get
{
- get
- {
- GetParts(_value, out var host, out var port);
+ GetParts(_value, out var host, out var port);
- return host.ToString();
- }
+ return host.ToString();
}
+ }
- /// <summary>
- /// Returns the value of the port part of the host, or <value>null</value> if none is found.
- /// </summary>
- /// <returns>The port portion of the value.</returns>
- public int? Port
+ /// <summary>
+ /// Returns the value of the port part of the host, or <value>null</value> if none is found.
+ /// </summary>
+ /// <returns>The port portion of the value.</returns>
+ public int? Port
+ {
+ get
{
- get
- {
- GetParts(_value, out var host, out var port);
+ GetParts(_value, out var host, out var port);
- if (!StringSegment.IsNullOrEmpty(port)
- && int.TryParse(port.AsSpan(), NumberStyles.None, CultureInfo.InvariantCulture, out var p))
- {
- return p;
- }
-
- return null;
+ if (!StringSegment.IsNullOrEmpty(port)
+ && int.TryParse(port.AsSpan(), NumberStyles.None, CultureInfo.InvariantCulture, out var p))
+ {
+ return p;
}
+
+ return null;
}
+ }
- /// <summary>
- /// Returns the value as normalized by ToUriComponent().
- /// </summary>
- /// <returns>The value as normalized by <see cref="ToUriComponent"/>.</returns>
- public override string ToString()
+ /// <summary>
+ /// Returns the value as normalized by ToUriComponent().
+ /// </summary>
+ /// <returns>The value as normalized by <see cref="ToUriComponent"/>.</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>The <see cref="HostString"/> value formated for use in a URI or HTTP header.</returns>
+ public string ToUriComponent()
+ {
+ if (string.IsNullOrEmpty(_value))
{
- return ToUriComponent();
+ return string.Empty;
}
- /// <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>The <see cref="HostString"/> value formated for use in a URI or HTTP header.</returns>
- public string ToUriComponent()
+ int i;
+ for (i = 0; i < _value.Length; ++i)
{
- if (string.IsNullOrEmpty(_value))
+ if (!HostStringHelper.IsSafeHostStringChar(_value[i]))
{
- return string.Empty;
- }
-
- int i;
- for (i = 0; i < _value.Length; ++i)
- {
- if (!HostStringHelper.IsSafeHostStringChar(_value[i]))
- {
- break;
- }
+ 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);
+ if (i != _value.Length)
+ {
+ GetParts(_value, out var host, out var port);
- return StringSegment.IsNullOrEmpty(port)
- ? encoded
- : string.Concat(encoded, ":", port.ToString());
- }
+ var mapping = new IdnMapping();
+ var encoded = mapping.GetAscii(host.Buffer!, host.Offset, host.Length);
- return _value;
+ return StringSegment.IsNullOrEmpty(port)
+ ? encoded
+ : string.Concat(encoded, ":", port.ToString());
}
- /// <summary>
- /// Creates a new HostString from the given URI component.
- /// Any punycode will be converted to Unicode.
- /// </summary>
- /// <param name="uriComponent">The URI component string to create a <see cref="HostString"/> from.</param>
- /// <returns>The <see cref="HostString"/> that was created.</returns>
- public static HostString FromUriComponent(string uriComponent)
+ return _value;
+ }
+
+ /// <summary>
+ /// Creates a new HostString from the given URI component.
+ /// Any punycode will be converted to Unicode.
+ /// </summary>
+ /// <param name="uriComponent">The URI component string to create a <see cref="HostString"/> from.</param>
+ /// <returns>The <see cref="HostString"/> that was created.</returns>
+ public static HostString FromUriComponent(string uriComponent)
+ {
+ if (!string.IsNullOrEmpty(uriComponent))
{
- if (!string.IsNullOrEmpty(uriComponent))
+ int index;
+ if (uriComponent.IndexOf('[') >= 0)
{
- 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 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)
{
- // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+ // Has a port
+ string port = uriComponent.Substring(index);
+ var mapping = new IdnMapping();
+ uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port;
}
- else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0)
+ else
{
- // 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);
- }
+ var mapping = new IdnMapping();
+ uriComponent = mapping.GetUnicode(uriComponent);
}
}
- return new HostString(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">The <see cref="Uri"/> to create a <see cref="HostString"/> from.</param>
- /// <returns>The <see cref="HostString"/> that was created.</returns>
- public static HostString FromUriComponent(Uri uri)
+ /// <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">The <see cref="Uri"/> to create a <see cref="HostString"/> from.</param>
+ /// <returns>The <see cref="HostString"/> that was created.</returns>
+ public static HostString FromUriComponent(Uri uri)
+ {
+ if (uri == null)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ throw new ArgumentNullException(nameof(uri));
+ }
+
+ return new HostString(uri.GetComponents(
+ UriComponents.NormalizedHost | // Always convert punycode to Unicode.
+ UriComponents.HostAndPort, UriFormat.Unescaped));
+ }
- 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><see langword="true" /> if <paramref name="value"/> matches any of the patterns.</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));
}
- /// <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><see langword="true" /> if <paramref name="value"/> matches any of the patterns.</returns>
- public static bool MatchesAny(StringSegment value, IList<StringSegment> patterns)
+ // Drop the port
+ GetParts(value, out var host, out var port);
+
+ for (int i = 0; i < port.Length; i++)
{
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
- if (patterns == null)
+ if (port[i] < '0' || '9' < port[i])
{
- throw new ArgumentNullException(nameof(patterns));
+ throw new FormatException($"The given host value '{value}' has a malformed port.");
}
+ }
- // Drop the port
- GetParts(value, out var host, out var port);
+ var count = patterns.Count;
+ for (int i = 0; i < count; i++)
+ {
+ var pattern = patterns[i];
- for (int i = 0; i < port.Length; i++)
+ if (pattern == "*")
{
- if (port[i] < '0' || '9' < port[i])
- {
- throw new FormatException($"The given host value '{value}' has a malformed port.");
- }
+ return true;
}
- var count = patterns.Count;
- for (int i = 0; i < count; i++)
+ if (StringSegment.Equals(pattern, host, StringComparison.OrdinalIgnoreCase))
{
- var pattern = patterns[i];
+ return true;
+ }
- if (pattern == "*")
- {
- return true;
- }
+ // Sub-domain wildcards: *.example.com
+ if (pattern.StartsWith("*.", StringComparison.Ordinal) && host.Length >= pattern.Length)
+ {
+ // .example.com
+ var allowedRoot = pattern.Subsegment(1);
- if (StringSegment.Equals(pattern, host, StringComparison.OrdinalIgnoreCase))
+ var hostRoot = host.Subsegment(host.Length - allowedRoot.Length);
+ if (hostRoot.Equals(allowedRoot, 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">The <see cref="HostString"/> to compare against.</param>
- /// <returns><see langword="true" /> if they have the same value.</returns>
- public bool Equals(HostString other)
+ return false;
+ }
+
+ /// <summary>
+ /// Compares the equality of the Value property, ignoring case.
+ /// </summary>
+ /// <param name="other">The <see cref="HostString"/> to compare against.</param>
+ /// <returns><see langword="true" /> if they have the same value.</returns>
+ public bool Equals(HostString other)
+ {
+ if (!HasValue && !other.HasValue)
{
- if (!HasValue && !other.HasValue)
- {
- return true;
- }
- return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
+ 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">The <see cref="object"/> to compare against.</param>
- /// <returns><see langword="true" /> if they have the same value.</returns>
- public override bool Equals(object? obj)
+ /// <summary>
+ /// Compares against the given object only if it is a HostString.
+ /// </summary>
+ /// <param name="obj">The <see cref="object"/> to compare against.</param>
+ /// <returns><see langword="true" /> if they have the same value.</returns>
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
{
- if (ReferenceEquals(null, obj))
- {
- return !HasValue;
- }
- return obj is HostString && Equals((HostString)obj);
+ return !HasValue;
}
+ return obj is HostString && Equals((HostString)obj);
+ }
- /// <summary>
- /// Gets a hash code for the value.
- /// </summary>
- /// <returns>The hash code as an <see cref="int"/>.</returns>
- public override int GetHashCode()
+ /// <summary>
+ /// Gets a hash code for the value.
+ /// </summary>
+ /// <returns>The hash code as an <see cref="int"/>.</returns>
+ public override int GetHashCode()
+ {
+ return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0);
+ }
+
+ /// <summary>
+ /// Compares the two instances for equality.
+ /// </summary>
+ /// <param name="left">The left parameter.</param>
+ /// <param name="right">The right parameter.</param>
+ /// <returns><see langword="true" /> if both <see cref="HostString"/>'s have the same value.</returns>
+ public static bool operator ==(HostString left, HostString right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Compares the two instances for inequality.
+ /// </summary>
+ /// <param name="left">The left parameter.</param>
+ /// <param name="right">The right parameter.</param>
+ /// <returns><see langword="true" /> if both <see cref="HostString"/>'s values are not equal.</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>
+ /// <param name="value">The value to get the parts of.</param>
+ /// <param name="host">The portion of the <paramref name="value"/> which represents the host.</param>
+ /// <param name="port">The portion of the <paramref name="value"/> which represents the port.</param>
+ private static void GetParts(StringSegment value, out StringSegment host, out StringSegment port)
+ {
+ int index;
+ port = null;
+ host = null;
+
+ if (StringSegment.IsNullOrEmpty(value))
{
- return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0);
+ return;
}
-
- /// <summary>
- /// Compares the two instances for equality.
- /// </summary>
- /// <param name="left">The left parameter.</param>
- /// <param name="right">The right parameter.</param>
- /// <returns><see langword="true" /> if both <see cref="HostString"/>'s have the same value.</returns>
- public static bool operator ==(HostString left, HostString right)
+ 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)
{
- return left.Equals(right);
+ // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+ host = $"[{value}]";
+ port = null;
}
-
- /// <summary>
- /// Compares the two instances for inequality.
- /// </summary>
- /// <param name="left">The left parameter.</param>
- /// <param name="right">The right parameter.</param>
- /// <returns><see langword="true" /> if both <see cref="HostString"/>'s values are not equal.</returns>
- public static bool operator !=(HostString left, HostString right)
+ else if (index >= 0)
{
- return !left.Equals(right);
+ // Has a port
+ host = value.Subsegment(0, index);
+ port = value.Subsegment(index + 1);
}
-
- /// <summary>
- /// Parses the current value. IPv6 addresses will have brackets added if they are missing.
- /// </summary>
- /// <param name="value">The value to get the parts of.</param>
- /// <param name="host">The portion of the <paramref name="value"/> which represents the host.</param>
- /// <param name="port">The portion of the <paramref name="value"/> which represents the port.</param>
- private static void GetParts(StringSegment value, out StringSegment host, out StringSegment port)
+ else
{
- int index;
+ host = value;
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
index d229180234..eea64901b7 100644
--- a/src/Http/Http.Abstractions/src/HttpContext.cs
+++ b/src/Http/Http.Abstractions/src/HttpContext.cs
@@ -7,72 +7,71 @@ using System.Security.Claims;
using System.Threading;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Encapsulates all HTTP-specific information about an individual HTTP request.
+/// </summary>
+public abstract class HttpContext
{
/// <summary>
- /// Encapsulates all HTTP-specific information about an individual HTTP request.
+ /// Gets the collection of HTTP features provided by the server and middleware available on this 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; }
+ 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="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 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 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>
+ /// Gets an object that manages the establishment of WebSocket connections for this request.
+ /// </summary>
+ public abstract WebSocketManager WebSockets { get; }
- /// <summary>
- /// Gets or sets the user for this request.
- /// </summary>
- public abstract ClaimsPrincipal User { get; set; }
+ /// <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 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>
+ /// 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>
+ /// 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 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>
+ /// 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();
- }
+ /// <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
index b832792255..2676a58dd7 100644
--- a/src/Http/Http.Abstractions/src/HttpMethods.cs
+++ b/src/Http/Http.Abstractions/src/HttpMethods.cs
@@ -3,196 +3,195 @@
using System;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Contains methods to verify the request method of an HTTP request.
+/// </summary>
+public static class HttpMethods
{
+ // We are intentionally using 'static readonly' here instead of 'const'.
+ // 'const' values would be embedded into each assembly that used them
+ // and each consuming assembly would have a different 'string' instance.
+ // Using .'static readonly' means that all consumers get these exact same
+ // 'string' instance, which means the 'ReferenceEquals' checks below work
+ // and allow us to optimize comparisons when these constants are used.
+
+ // Please do NOT change these to 'const'
/// <summary>
- /// Contains methods to verify the request method of an HTTP request.
+ /// HTTP "CONNECT" method.
/// </summary>
- public static class HttpMethods
- {
- // We are intentionally using 'static readonly' here instead of 'const'.
- // 'const' values would be embedded into each assembly that used them
- // and each consuming assembly would have a different 'string' instance.
- // Using .'static readonly' means that all consumers get these exact same
- // 'string' instance, which means the 'ReferenceEquals' checks below work
- // and allow us to optimize comparisons when these constants are used.
-
- // Please do NOT change these to 'const'
- /// <summary>
- /// HTTP "CONNECT" method.
- /// </summary>
- public static readonly string Connect = "CONNECT";
- /// <summary>
- /// HTTP "DELETE" method.
- /// </summary>
- public static readonly string Delete = "DELETE";
- /// <summary>
- /// HTTP "GET" method.
- /// </summary>
- public static readonly string Get = "GET";
- /// <summary>
- /// HTTP "HEAD" method.
- /// </summary>
- public static readonly string Head = "HEAD";
- /// <summary>
- /// HTTP "OPTIONS" method.
- /// </summary>
- public static readonly string Options = "OPTIONS";
- /// <summary>
- /// HTTP "PATCH" method.
- /// </summary>
- public static readonly string Patch = "PATCH";
- /// <summary>
- /// HTTP "POST" method.
- /// </summary>
- public static readonly string Post = "POST";
- /// <summary>
- /// HTTP "PUT" method.
- /// </summary>
- public static readonly string Put = "PUT";
- /// <summary>
- /// HTTP "TRACE" method.
- /// </summary>
- public static readonly string Trace = "TRACE";
+ public static readonly string Connect = "CONNECT";
+ /// <summary>
+ /// HTTP "DELETE" method.
+ /// </summary>
+ public static readonly string Delete = "DELETE";
+ /// <summary>
+ /// HTTP "GET" method.
+ /// </summary>
+ public static readonly string Get = "GET";
+ /// <summary>
+ /// HTTP "HEAD" method.
+ /// </summary>
+ public static readonly string Head = "HEAD";
+ /// <summary>
+ /// HTTP "OPTIONS" method.
+ /// </summary>
+ public static readonly string Options = "OPTIONS";
+ /// <summary>
+ /// HTTP "PATCH" method.
+ /// </summary>
+ public static readonly string Patch = "PATCH";
+ /// <summary>
+ /// HTTP "POST" method.
+ /// </summary>
+ public static readonly string Post = "POST";
+ /// <summary>
+ /// HTTP "PUT" method.
+ /// </summary>
+ public static readonly string Put = "PUT";
+ /// <summary>
+ /// HTTP "TRACE" method.
+ /// </summary>
+ public static readonly string Trace = "TRACE";
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is CONNECT.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is CONNECT; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsConnect(string method)
- {
- return Equals(Connect, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is CONNECT.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is CONNECT; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsConnect(string method)
+ {
+ return Equals(Connect, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is DELETE.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is DELETE; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsDelete(string method)
- {
- return Equals(Delete, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is DELETE.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is DELETE; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsDelete(string method)
+ {
+ return Equals(Delete, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is GET.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is GET; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsGet(string method)
- {
- return Equals(Get, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is GET.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is GET; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsGet(string method)
+ {
+ return Equals(Get, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is HEAD.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is HEAD; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsHead(string method)
- {
- return Equals(Head, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is HEAD.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is HEAD; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsHead(string method)
+ {
+ return Equals(Head, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is OPTIONS.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is OPTIONS; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsOptions(string method)
- {
- return Equals(Options, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is OPTIONS.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is OPTIONS; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsOptions(string method)
+ {
+ return Equals(Options, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is PATCH.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is PATCH; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsPatch(string method)
- {
- return Equals(Patch, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is PATCH.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is PATCH; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsPatch(string method)
+ {
+ return Equals(Patch, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is POST.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is POST; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsPost(string method)
- {
- return Equals(Post, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is POST.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is POST; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsPost(string method)
+ {
+ return Equals(Post, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is PUT.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is PUT; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsPut(string method)
- {
- return Equals(Put, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is PUT.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is PUT; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsPut(string method)
+ {
+ return Equals(Put, method);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request method is TRACE.
- /// </summary>
- /// <param name="method">The HTTP request method.</param>
- /// <returns>
- /// <see langword="true" /> if the method is TRACE; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsTrace(string method)
- {
- return Equals(Trace, method);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request method is TRACE.
+ /// </summary>
+ /// <param name="method">The HTTP request method.</param>
+ /// <returns>
+ /// <see langword="true" /> if the method is TRACE; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsTrace(string method)
+ {
+ return Equals(Trace, method);
+ }
- /// <summary>
- /// Returns the equivalent static instance, or the original instance if none match. This conversion is optional but allows for performance optimizations when comparing method values elsewhere.
- /// </summary>
- /// <param name="method"></param>
- /// <returns></returns>
- public static string GetCanonicalizedValue(string method) => method switch
- {
- string _ when IsGet(method) => Get,
- string _ when IsPost(method) => Post,
- string _ when IsPut(method) => Put,
- string _ when IsDelete(method) => Delete,
- string _ when IsOptions(method) => Options,
- string _ when IsHead(method) => Head,
- string _ when IsPatch(method) => Patch,
- string _ when IsTrace(method) => Trace,
- string _ when IsConnect(method) => Connect,
- string _ => method
- };
+ /// <summary>
+ /// Returns the equivalent static instance, or the original instance if none match. This conversion is optional but allows for performance optimizations when comparing method values elsewhere.
+ /// </summary>
+ /// <param name="method"></param>
+ /// <returns></returns>
+ public static string GetCanonicalizedValue(string method) => method switch
+ {
+ string _ when IsGet(method) => Get,
+ string _ when IsPost(method) => Post,
+ string _ when IsPut(method) => Put,
+ string _ when IsDelete(method) => Delete,
+ string _ when IsOptions(method) => Options,
+ string _ when IsHead(method) => Head,
+ string _ when IsPatch(method) => Patch,
+ string _ when IsTrace(method) => Trace,
+ string _ when IsConnect(method) => Connect,
+ string _ => method
+ };
- /// <summary>
- /// Returns a value that indicates if the HTTP methods are the same.
- /// </summary>
- /// <param name="methodA">The first HTTP request method to compare.</param>
- /// <param name="methodB">The second HTTP request method to compare.</param>
- /// <returns>
- /// <see langword="true" /> if the methods are the same; otherwise, <see langword="false" />.
- /// </returns>
- public static bool Equals(string methodA, string methodB)
- {
- return object.ReferenceEquals(methodA, methodB) || StringComparer.OrdinalIgnoreCase.Equals(methodA, methodB);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP methods are the same.
+ /// </summary>
+ /// <param name="methodA">The first HTTP request method to compare.</param>
+ /// <param name="methodB">The second HTTP request method to compare.</param>
+ /// <returns>
+ /// <see langword="true" /> if the methods are the same; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool Equals(string methodA, string methodB)
+ {
+ return object.ReferenceEquals(methodA, methodB) || StringComparer.OrdinalIgnoreCase.Equals(methodA, methodB);
}
}
diff --git a/src/Http/Http.Abstractions/src/HttpProtocol.cs b/src/Http/Http.Abstractions/src/HttpProtocol.cs
index b21dee762d..f8b4c50ac4 100644
--- a/src/Http/Http.Abstractions/src/HttpProtocol.cs
+++ b/src/Http/Http.Abstractions/src/HttpProtocol.cs
@@ -3,128 +3,127 @@
using System;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Contains methods to verify the request protocol version of an HTTP request.
+/// </summary>
+public static class HttpProtocol
{
+ // We are intentionally using 'static readonly' here instead of 'const'.
+ // 'const' values would be embedded into each assembly that used them
+ // and each consuming assembly would have a different 'string' instance.
+ // Using .'static readonly' means that all consumers get these exact same
+ // 'string' instance, which means the 'ReferenceEquals' checks below work
+ // and allow us to optimize comparisons when these constants are used.
+
+ // Please do NOT change these to 'const'
+
+ /// <summary>
+ /// HTTP protocol version 0.9.
+ /// </summary>
+ public static readonly string Http09 = "HTTP/0.9";
+
+ /// <summary>
+ /// HTTP protocol version 1.0.
+ /// </summary>
+ public static readonly string Http10 = "HTTP/1.0";
+
+ /// <summary>
+ /// HTTP protocol version 1.1.
+ /// </summary>
+ public static readonly string Http11 = "HTTP/1.1";
+
/// <summary>
- /// Contains methods to verify the request protocol version of an HTTP request.
+ /// HTTP protocol version 2.
/// </summary>
- public static class HttpProtocol
+ public static readonly string Http2 = "HTTP/2";
+
+ /// <summary>
+ /// HTTP protcol version 3.
+ /// </summary>
+ public static readonly string Http3 = "HTTP/3";
+
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request protocol is HTTP/0.9.
+ /// </summary>
+ /// <param name="protocol">The HTTP request protocol.</param>
+ /// <returns>
+ /// <see langword="true" /> if the protocol is HTTP/0.9; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsHttp09(string protocol)
{
- // We are intentionally using 'static readonly' here instead of 'const'.
- // 'const' values would be embedded into each assembly that used them
- // and each consuming assembly would have a different 'string' instance.
- // Using .'static readonly' means that all consumers get these exact same
- // 'string' instance, which means the 'ReferenceEquals' checks below work
- // and allow us to optimize comparisons when these constants are used.
-
- // Please do NOT change these to 'const'
-
- /// <summary>
- /// HTTP protocol version 0.9.
- /// </summary>
- public static readonly string Http09 = "HTTP/0.9";
-
- /// <summary>
- /// HTTP protocol version 1.0.
- /// </summary>
- public static readonly string Http10 = "HTTP/1.0";
-
- /// <summary>
- /// HTTP protocol version 1.1.
- /// </summary>
- public static readonly string Http11 = "HTTP/1.1";
-
- /// <summary>
- /// HTTP protocol version 2.
- /// </summary>
- public static readonly string Http2 = "HTTP/2";
-
- /// <summary>
- /// HTTP protcol version 3.
- /// </summary>
- public static readonly string Http3 = "HTTP/3";
-
- /// <summary>
- /// Returns a value that indicates if the HTTP request protocol is HTTP/0.9.
- /// </summary>
- /// <param name="protocol">The HTTP request protocol.</param>
- /// <returns>
- /// <see langword="true" /> if the protocol is HTTP/0.9; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsHttp09(string protocol)
- {
- return object.ReferenceEquals(Http09, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http09, protocol);
- }
+ return object.ReferenceEquals(Http09, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http09, protocol);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request protocol is HTTP/1.0.
- /// </summary>
- /// <param name="protocol">The HTTP request protocol.</param>
- /// <returns>
- /// <see langword="true" /> if the protocol is HTTP/1.0; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsHttp10(string protocol)
- {
- return object.ReferenceEquals(Http10, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http10, protocol);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request protocol is HTTP/1.0.
+ /// </summary>
+ /// <param name="protocol">The HTTP request protocol.</param>
+ /// <returns>
+ /// <see langword="true" /> if the protocol is HTTP/1.0; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsHttp10(string protocol)
+ {
+ return object.ReferenceEquals(Http10, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http10, protocol);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request protocol is HTTP/1.1.
- /// </summary>
- /// <param name="protocol">The HTTP request protocol.</param>
- /// <returns>
- /// <see langword="true" /> if the protocol is HTTP/1.1; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsHttp11(string protocol)
- {
- return object.ReferenceEquals(Http11, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http11, protocol);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request protocol is HTTP/1.1.
+ /// </summary>
+ /// <param name="protocol">The HTTP request protocol.</param>
+ /// <returns>
+ /// <see langword="true" /> if the protocol is HTTP/1.1; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsHttp11(string protocol)
+ {
+ return object.ReferenceEquals(Http11, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http11, protocol);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request protocol is HTTP/2.
- /// </summary>
- /// <param name="protocol">The HTTP request protocol.</param>
- /// <returns>
- /// <see langword="true" /> if the protocol is HTTP/2; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsHttp2(string protocol)
- {
- return object.ReferenceEquals(Http2, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http2, protocol);
- }
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request protocol is HTTP/2.
+ /// </summary>
+ /// <param name="protocol">The HTTP request protocol.</param>
+ /// <returns>
+ /// <see langword="true" /> if the protocol is HTTP/2; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsHttp2(string protocol)
+ {
+ return object.ReferenceEquals(Http2, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http2, protocol);
+ }
+
+ /// <summary>
+ /// Returns a value that indicates if the HTTP request protocol is HTTP/3.
+ /// </summary>
+ /// <param name="protocol">The HTTP request protocol.</param>
+ /// <returns>
+ /// <see langword="true" /> if the protocol is HTTP/3; otherwise, <see langword="false" />.
+ /// </returns>
+ public static bool IsHttp3(string protocol)
+ {
+ return object.ReferenceEquals(Http3, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http3, protocol);
+ }
- /// <summary>
- /// Returns a value that indicates if the HTTP request protocol is HTTP/3.
- /// </summary>
- /// <param name="protocol">The HTTP request protocol.</param>
- /// <returns>
- /// <see langword="true" /> if the protocol is HTTP/3; otherwise, <see langword="false" />.
- /// </returns>
- public static bool IsHttp3(string protocol)
+ /// <summary>
+ /// Gets the HTTP request protocol for the specified <see cref="Version"/>.
+ /// </summary>
+ /// <param name="version">The version.</param>
+ /// <returns>A HTTP request protocol.</returns>
+ public static string GetHttpProtocol(Version version)
+ {
+ if (version == null)
{
- return object.ReferenceEquals(Http3, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http3, protocol);
+ throw new ArgumentNullException(nameof(version));
}
- /// <summary>
- /// Gets the HTTP request protocol for the specified <see cref="Version"/>.
- /// </summary>
- /// <param name="version">The version.</param>
- /// <returns>A HTTP request protocol.</returns>
- public static string GetHttpProtocol(Version version)
+ return version switch
{
- if (version == null)
- {
- throw new ArgumentNullException(nameof(version));
- }
-
- return version switch
- {
- { Major: 3, Minor: 0 } => Http3,
- { Major: 2, Minor: 0 } => Http2,
- { Major: 1, Minor: 1 } => Http11,
- { Major: 1, Minor: 0 } => Http10,
- { Major: 0, Minor: 9 } => Http09,
- _ => throw new ArgumentOutOfRangeException(nameof(version), "Version doesn't map to a known HTTP protocol.")
- };
- }
+ { Major: 3, Minor: 0 } => Http3,
+ { Major: 2, Minor: 0 } => Http2,
+ { Major: 1, Minor: 1 } => Http11,
+ { Major: 1, Minor: 0 } => Http10,
+ { Major: 0, Minor: 9 } => Http09,
+ _ => throw new ArgumentOutOfRangeException(nameof(version), "Version doesn't map to a known HTTP protocol.")
+ };
}
}
diff --git a/src/Http/Http.Abstractions/src/HttpRequest.cs b/src/Http/Http.Abstractions/src/HttpRequest.cs
index c0f78134ea..12bcb6f0e4 100644
--- a/src/Http/Http.Abstractions/src/HttpRequest.cs
+++ b/src/Http/Http.Abstractions/src/HttpRequest.cs
@@ -8,129 +8,128 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the incoming side of an individual HTTP request.
+/// </summary>
+public abstract class HttpRequest
{
/// <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 base path for the request. The path base should not end with a trailing slash.
- /// </summary>
- /// <returns>The base path for the request.</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 request protocol (e.g. HTTP/1.1).
- /// </summary>
- /// <returns>The request protocol.</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 request body <see cref="Stream"/>.
- /// </summary>
- /// <value>The request body <see cref="Stream"/>.</value>
- public abstract Stream Body { get; set; }
-
- /// <summary>
- /// Gets the request body <see cref="PipeReader"/>.
- /// </summary>
- /// <value>The request body <see cref="PipeReader"/>.</value>
- public virtual PipeReader BodyReader { get => throw new NotImplementedException(); }
-
- /// <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());
-
- /// <summary>
- /// Gets the collection of route values for this request.
- /// </summary>
- /// <returns>The collection of route values for this request.</returns>
- public virtual RouteValueDictionary RouteValues { get; set; } = null!;
- }
+ /// 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 base path for the request. The path base should not end with a trailing slash.
+ /// </summary>
+ /// <returns>The base path for the request.</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 request protocol (e.g. HTTP/1.1).
+ /// </summary>
+ /// <returns>The request protocol.</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 request body <see cref="Stream"/>.
+ /// </summary>
+ /// <value>The request body <see cref="Stream"/>.</value>
+ public abstract Stream Body { get; set; }
+
+ /// <summary>
+ /// Gets the request body <see cref="PipeReader"/>.
+ /// </summary>
+ /// <value>The request body <see cref="PipeReader"/>.</value>
+ public virtual PipeReader BodyReader { get => throw new NotImplementedException(); }
+
+ /// <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());
+
+ /// <summary>
+ /// Gets the collection of route values for this request.
+ /// </summary>
+ /// <returns>The collection of route values for this request.</returns>
+ public virtual RouteValueDictionary RouteValues { get; set; } = null!;
}
diff --git a/src/Http/Http.Abstractions/src/HttpResponse.cs b/src/Http/Http.Abstractions/src/HttpResponse.cs
index 4fbac26f51..8b2599cf81 100644
--- a/src/Http/Http.Abstractions/src/HttpResponse.cs
+++ b/src/Http/Http.Abstractions/src/HttpResponse.cs
@@ -7,150 +7,149 @@ using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the outgoing side of an individual HTTP request.
+/// </summary>
+public abstract class HttpResponse
{
- /// <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 = state =>
{
- private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();
- private static readonly Func<object, Task> _disposeDelegate = state =>
- {
// Prefer async dispose over dispose
if (state is IAsyncDisposable asyncDisposable)
- {
- return asyncDisposable.DisposeAsync().AsTask();
- }
- else if (state is IDisposable disposable)
- {
- 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 the response body <see cref="PipeWriter"/>
- /// </summary>
- /// <value>The response body <see cref="PipeWriter"/>.</value>
- public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); }
-
- /// <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.
- /// Callbacks registered here run in reverse order.
- /// </summary>
- /// <remarks>
- /// Callbacks registered here run in reverse order. The last one registered is invoked first.
- /// The reverse order is done to replicate the way middleware works, with the inner-most middleware looking at the
- /// response first.
- /// </remarks>
- /// <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.
- /// Callbacks registered here run in reverse order.
- /// </summary>
- /// <remarks>
- /// Callbacks registered here run in reverse order. The last one registered is invoked first.
- /// The reverse order is done to replicate the way middleware works, with the inner-most middleware looking at the
- /// response first.
- /// </remarks>
- /// <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>
- /// Registers an object for asynchronous disposal by the host once the request has finished processing.
- /// </summary>
- /// <param name="disposable">The object to be disposed asynchronously.</param>
- public virtual void RegisterForDisposeAsync(IAsyncDisposable 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);
-
- /// <summary>
- /// Starts the response by calling OnStarting() and making headers unmodifiable.
- /// </summary>
- /// <param name="cancellationToken"></param>
- public virtual Task StartAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); }
-
- /// <summary>
- /// Flush any remaining response headers, data, or trailers.
- /// This may throw if the response is in an invalid state such as a Content-Length mismatch.
- /// </summary>
- /// <returns></returns>
- public virtual Task CompleteAsync() { throw new NotImplementedException(); }
- }
+ {
+ return asyncDisposable.DisposeAsync().AsTask();
+ }
+ else if (state is IDisposable disposable)
+ {
+ 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 the response body <see cref="PipeWriter"/>
+ /// </summary>
+ /// <value>The response body <see cref="PipeWriter"/>.</value>
+ public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); }
+
+ /// <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.
+ /// Callbacks registered here run in reverse order.
+ /// </summary>
+ /// <remarks>
+ /// Callbacks registered here run in reverse order. The last one registered is invoked first.
+ /// The reverse order is done to replicate the way middleware works, with the inner-most middleware looking at the
+ /// response first.
+ /// </remarks>
+ /// <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.
+ /// Callbacks registered here run in reverse order.
+ /// </summary>
+ /// <remarks>
+ /// Callbacks registered here run in reverse order. The last one registered is invoked first.
+ /// The reverse order is done to replicate the way middleware works, with the inner-most middleware looking at the
+ /// response first.
+ /// </remarks>
+ /// <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>
+ /// Registers an object for asynchronous disposal by the host once the request has finished processing.
+ /// </summary>
+ /// <param name="disposable">The object to be disposed asynchronously.</param>
+ public virtual void RegisterForDisposeAsync(IAsyncDisposable 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);
+
+ /// <summary>
+ /// Starts the response by calling OnStarting() and making headers unmodifiable.
+ /// </summary>
+ /// <param name="cancellationToken"></param>
+ public virtual Task StartAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); }
+
+ /// <summary>
+ /// Flush any remaining response headers, data, or trailers.
+ /// This may throw if the response is in an invalid state such as a Content-Length mismatch.
+ /// </summary>
+ /// <returns></returns>
+ public virtual Task CompleteAsync() { throw new NotImplementedException(); }
}
diff --git a/src/Http/Http.Abstractions/src/IApplicationBuilder.cs b/src/Http/Http.Abstractions/src/IApplicationBuilder.cs
index 0b57f15720..6b7f2a3020 100644
--- a/src/Http/Http.Abstractions/src/IApplicationBuilder.cs
+++ b/src/Http/Http.Abstractions/src/IApplicationBuilder.cs
@@ -6,46 +6,45 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Defines a class that provides the mechanisms to configure an application's request pipeline.
+/// </summary>
+public interface IApplicationBuilder
{
/// <summary>
- /// Defines a class that provides the mechanisms to configure an application's request pipeline.
+ /// 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>
- 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();
- }
+ /// <returns>The request handling delegate.</returns>
+ RequestDelegate Build();
}
diff --git a/src/Http/Http.Abstractions/src/ICorsMetadata.cs b/src/Http/Http.Abstractions/src/ICorsMetadata.cs
index 727561663f..cc6ec493cb 100644
--- a/src/Http/Http.Abstractions/src/ICorsMetadata.cs
+++ b/src/Http/Http.Abstractions/src/ICorsMetadata.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Cors.Infrastructure
+namespace Microsoft.AspNetCore.Cors.Infrastructure;
+
+/// <summary>
+/// A marker interface which can be used to identify CORS metadata.
+/// </summary>
+public interface ICorsMetadata
{
- /// <summary>
- /// A marker interface which can be used to identify CORS metadata.
- /// </summary>
- public interface ICorsMetadata
- {
- }
}
diff --git a/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs b/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs
index 171146ccaf..33e6e23330 100644
--- a/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs
+++ b/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs
@@ -1,20 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides access to the current <see cref="HttpContext"/>, if one is available.
+/// </summary>
+/// <remarks>
+/// This interface should be used with caution. It relies on <see cref="System.Threading.AsyncLocal{T}" /> which can have a negative performance impact on async calls.
+/// It also creates a dependency on "ambient state" which can make testing more difficult.
+/// </remarks>
+public interface IHttpContextAccessor
{
/// <summary>
- /// Provides access to the current <see cref="HttpContext"/>, if one is available.
+ /// Gets or sets the current <see cref="HttpContext"/>. Returns <see langword="null" /> if there is no active <see cref="HttpContext" />.
/// </summary>
- /// <remarks>
- /// This interface should be used with caution. It relies on <see cref="System.Threading.AsyncLocal{T}" /> which can have a negative performance impact on async calls.
- /// It also creates a dependency on "ambient state" which can make testing more difficult.
- /// </remarks>
- public interface IHttpContextAccessor
- {
- /// <summary>
- /// Gets or sets the current <see cref="HttpContext"/>. Returns <see langword="null" /> if there is no active <see cref="HttpContext" />.
- /// </summary>
- HttpContext? HttpContext { get; set; }
- }
+ HttpContext? HttpContext { get; set; }
}
diff --git a/src/Http/Http.Abstractions/src/IHttpContextFactory.cs b/src/Http/Http.Abstractions/src/IHttpContextFactory.cs
index 4bc4ab7a17..625883d513 100644
--- a/src/Http/Http.Abstractions/src/IHttpContextFactory.cs
+++ b/src/Http/Http.Abstractions/src/IHttpContextFactory.cs
@@ -3,24 +3,23 @@
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides methods to create and dispose of <see cref="HttpContext"/> instances.
+/// </summary>
+public interface IHttpContextFactory
{
/// <summary>
- /// Provides methods to create and dispose of <see cref="HttpContext"/> instances.
+ /// Creates an <see cref="HttpContext"/> instance for the specified set of HTTP features.
/// </summary>
- public interface IHttpContextFactory
- {
- /// <summary>
- /// Creates an <see cref="HttpContext"/> instance for the specified set of HTTP features.
- /// </summary>
- /// <param name="featureCollection">The collection of HTTP features to set on the created instance.</param>
- /// <returns>The <see cref="HttpContext"/> instance.</returns>
- HttpContext Create(IFeatureCollection featureCollection);
+ /// <param name="featureCollection">The collection of HTTP features to set on the created instance.</param>
+ /// <returns>The <see cref="HttpContext"/> instance.</returns>
+ HttpContext Create(IFeatureCollection featureCollection);
- /// <summary>
- /// Releases resources held by the <see cref="HttpContext"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> to dispose.</param>
- void Dispose(HttpContext httpContext);
- }
+ /// <summary>
+ /// Releases resources held by the <see cref="HttpContext"/>.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="HttpContext"/> to dispose.</param>
+ void Dispose(HttpContext httpContext);
}
diff --git a/src/Http/Http.Abstractions/src/IMiddleware.cs b/src/Http/Http.Abstractions/src/IMiddleware.cs
index 9320c5e782..f5c51ff937 100644
--- a/src/Http/Http.Abstractions/src/IMiddleware.cs
+++ b/src/Http/Http.Abstractions/src/IMiddleware.cs
@@ -3,19 +3,18 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Defines middleware that can be added to the application's request pipeline.
+/// </summary>
+public interface IMiddleware
{
/// <summary>
- /// Defines middleware that can be added to the application's request pipeline.
+ /// Request handling method.
/// </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);
- }
+ /// <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
index 82a0dc0203..0b7c7b1710 100644
--- a/src/Http/Http.Abstractions/src/IMiddlewareFactory.cs
+++ b/src/Http/Http.Abstractions/src/IMiddlewareFactory.cs
@@ -3,24 +3,23 @@
using System;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides methods to create middleware.
+/// </summary>
+public interface IMiddlewareFactory
{
/// <summary>
- /// Provides methods to create middleware.
+ /// Creates a middleware instance for each request.
/// </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);
+ /// <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);
- }
+ /// <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/IResult.cs b/src/Http/Http.Abstractions/src/IResult.cs
index ce83c591b4..8e5b5bf0fe 100644
--- a/src/Http/Http.Abstractions/src/IResult.cs
+++ b/src/Http/Http.Abstractions/src/IResult.cs
@@ -3,18 +3,17 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Defines a contract that represents the result of an HTTP endpoint.
+/// </summary>
+public interface IResult
{
/// <summary>
- /// Defines a contract that represents the result of an HTTP endpoint.
+ /// Write an HTTP response reflecting the result.
/// </summary>
- public interface IResult
- {
- /// <summary>
- /// Write an HTTP response reflecting the result.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
- /// <returns>A task that represents the asynchronous execute operation.</returns>
- Task ExecuteAsync(HttpContext httpContext);
- }
+ /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+ /// <returns>A task that represents the asynchronous execute operation.</returns>
+ Task ExecuteAsync(HttpContext httpContext);
}
diff --git a/src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs b/src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs
index b81d26a620..cbd82c9440 100644
--- a/src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs
+++ b/src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs
@@ -4,63 +4,62 @@
using System;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal readonly struct HeaderSegment : IEquatable<HeaderSegment>
{
- internal readonly 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)
{
- private readonly StringSegment _formatting;
- private readonly StringSegment _data;
+ _formatting = formatting;
+ _data = 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 Formatting
- {
- get { return _formatting; }
- }
+ public StringSegment Data
+ {
+ get { return _data; }
+ }
- public StringSegment Data
- {
- get { return _data; }
- }
+ public bool Equals(HeaderSegment other)
+ {
+ return _formatting.Equals(other._formatting) && _data.Equals(other._data);
+ }
- public bool Equals(HeaderSegment other)
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
{
- return _formatting.Equals(other._formatting) && _data.Equals(other._data);
+ return false;
}
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj))
- {
- return false;
- }
-
- return obj is HeaderSegment && Equals((HeaderSegment)obj);
- }
+ return obj is HeaderSegment && Equals((HeaderSegment)obj);
+ }
- public override int GetHashCode()
+ public override int GetHashCode()
+ {
+ unchecked
{
- unchecked
- {
- return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode();
- }
+ 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);
+ }
- 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
index 433d7d35e2..114e3384a7 100644
--- a/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs
+++ b/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs
@@ -6,297 +6,295 @@ using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal readonly struct HeaderSegmentCollection : IEnumerable<HeaderSegment>, IEquatable<HeaderSegmentCollection>
{
- internal readonly struct HeaderSegmentCollection : IEnumerable<HeaderSegment>, IEquatable<HeaderSegmentCollection>
+ private readonly StringValues _headers;
+
+ public HeaderSegmentCollection(StringValues headers)
{
- private readonly StringValues _headers;
+ _headers = headers;
+ }
- public HeaderSegmentCollection(StringValues headers)
- {
- _headers = headers;
- }
+ public bool Equals(HeaderSegmentCollection other)
+ {
+ return StringValues.Equals(_headers, other._headers);
+ }
- public bool Equals(HeaderSegmentCollection other)
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
{
- return StringValues.Equals(_headers, other._headers);
+ return false;
}
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj))
- {
- return false;
- }
+ return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj);
+ }
- return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj);
- }
+ public override int GetHashCode()
+ {
+ return (!StringValues.IsNullOrEmpty(_headers) ? _headers.GetHashCode() : 0);
+ }
- public override int GetHashCode()
+ 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)
{
- return (!StringValues.IsNullOrEmpty(_headers) ? _headers.GetHashCode() : 0);
+ _headers = headers;
+ _header = string.Empty;
+ _headerLength = -1;
+ _index = -1;
+ _offset = -1;
+ _leadingStart = -1;
+ _leadingEnd = -1;
+ _valueStart = -1;
+ _valueEnd = -1;
+ _trailingStart = -1;
+ _mode = Mode.Leading;
}
- public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right)
+ private enum Mode
{
- return left.Equals(right);
+ Leading,
+ Value,
+ ValueQuoted,
+ Trailing,
+ Produce,
}
- public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right)
+ private enum Attr
{
- return !left.Equals(right);
+ Value,
+ Quote,
+ Delimiter,
+ Whitespace
}
- public Enumerator GetEnumerator()
+ public HeaderSegment Current
{
- return new Enumerator(_headers);
+ get
+ {
+ return new HeaderSegment(
+ new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart),
+ new StringSegment(_header, _valueStart, _valueEnd - _valueStart));
+ }
}
- IEnumerator<HeaderSegment> IEnumerable<HeaderSegment>.GetEnumerator()
+ object IEnumerator.Current
{
- return GetEnumerator();
+ get { return Current; }
}
- IEnumerator IEnumerable.GetEnumerator()
+ public void Dispose()
{
- return GetEnumerator();
}
- public struct Enumerator : IEnumerator<HeaderSegment>
+ public bool MoveNext()
{
- 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
+ while (true)
{
- get
+ if (_mode == Mode.Produce)
{
- return new HeaderSegment(
- new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart),
- new StringSegment(_header, _valueStart, _valueEnd - _valueStart));
- }
- }
-
- object IEnumerator.Current
- {
- get { return Current; }
- }
+ _leadingStart = _trailingStart;
+ _leadingEnd = -1;
+ _valueStart = -1;
+ _valueEnd = -1;
+ _trailingStart = -1;
- public void Dispose()
- {
- }
-
- public bool MoveNext()
- {
- while (true)
- {
- if (_mode == Mode.Produce)
+ if (_offset == _headerLength &&
+ _leadingStart != -1 &&
+ _leadingStart != _offset)
{
- _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;
+ // Also produce trailing whitespace
+ _leadingEnd = _offset;
+ return true;
}
+ _mode = Mode.Leading;
+ }
- // if end of a string
- if (_offset == _headerLength)
+ // 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)
{
- ++_index;
- _offset = -1;
- _leadingStart = 0;
- _leadingEnd = -1;
- _valueStart = -1;
- _valueEnd = -1;
- _trailingStart = -1;
+ // no more move nexts
+ return false;
+ }
- // 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;
- // grab the next string
- _header = _headers[_index] ?? string.Empty;
- _headerLength = _header.Length;
- }
- while (true)
+ switch (_mode)
{
- ++_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:
+ 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:
- // more
- break;
- case Attr.Whitespace:
+ }
+ break;
+ case Attr.Value:
+ case Attr.Whitespace:
+ // more
+ break;
+ }
+ break;
+ case Mode.Trailing:
+ switch (attr)
+ {
+ case Attr.Delimiter:
+ if (ch == (char)0)
+ {
_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:
- if (ch == (char)0)
- {
- _valueEnd = _offset;
- _trailingStart = _offset;
- }
- _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;
- }
+ }
+ _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;
- }
+ 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
index 55e1cfee72..4148b939db 100644
--- a/src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs
+++ b/src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs
@@ -1,15 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal class HostStringHelper
{
- internal class HostStringHelper
- {
- // Allowed Characters:
- // A-Z, a-z, 0-9, .,
- // -, %, [, ], :
- // Above for IPV6
- private static readonly bool[] SafeHostStringChars = {
+ // Allowed Characters:
+ // A-Z, a-z, 0-9, .,
+ // -, %, [, ], :
+ // Above for IPV6
+ private static readonly 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
@@ -28,9 +28,8 @@ namespace Microsoft.AspNetCore.Http
true, true, true, false, false, false, false, false, // 0x78 - 0x7F
};
- public static bool IsSafeHostStringChar(char c)
- {
- return c < SafeHostStringChars.Length && SafeHostStringChars[c];
- }
+ 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
index 150a460edf..361bf2a51c 100644
--- a/src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs
+++ b/src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs
@@ -5,160 +5,159 @@ using System;
using System.Linq;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal static class ParsingHelpers
{
- internal static class ParsingHelpers
+ public static StringValues GetHeader(IHeaderDictionary headers, string key)
{
- public static StringValues GetHeader(IHeaderDictionary headers, string key)
- {
- StringValues value;
- return headers.TryGetValue(key, out value) ? value : StringValues.Empty;
- }
+ StringValues value;
+ return headers.TryGetValue(key, out value) ? value : StringValues.Empty;
+ }
- public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key)
- {
- var values = GetHeaderUnmodified(headers, key);
+ public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key)
+ {
+ var values = GetHeaderUnmodified(headers, key);
- StringValues result = default;
+ StringValues result = default;
- foreach (var segment in new HeaderSegmentCollection(values))
+ foreach (var segment in new HeaderSegmentCollection(values))
+ {
+ if (!StringSegment.IsNullOrEmpty(segment.Data))
{
- if (!StringSegment.IsNullOrEmpty(segment.Data))
+ var value = DeQuote(segment.Data.Value);
+ if (!string.IsNullOrEmpty(value))
{
- var value = DeQuote(segment.Data.Value);
- if (!string.IsNullOrEmpty(value))
- {
- result = StringValues.Concat(in result, value);
- }
+ result = StringValues.Concat(in result, value);
}
}
-
- return result;
}
- public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key)
- {
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ return result;
+ }
- StringValues values;
- return headers.TryGetValue(key, out values) ? values : StringValues.Empty;
+ public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException(nameof(headers));
}
- public static void SetHeaderJoined(IHeaderDictionary headers, string key, StringValues value)
- {
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ StringValues values;
+ return headers.TryGetValue(key, out values) ? values : StringValues.Empty;
+ }
- 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)));
- }
+ public static void SetHeaderJoined(IHeaderDictionary headers, string key, StringValues value)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException(nameof(headers));
}
- // Quote items that contain commas and are not already quoted.
- private static string? QuoteIfNeeded(string? value)
+ if (string.IsNullOrEmpty(key))
{
- if (!string.IsNullOrEmpty(value) &&
- value.Contains(',') &&
- (value[0] != '"' || value[value.Length - 1] != '"'))
- {
- return $"\"{value}\"";
- }
- return value;
+ throw new ArgumentNullException(nameof(key));
}
-
- private static string? DeQuote(string? value)
+ if (StringValues.IsNullOrEmpty(value))
{
- if (!string.IsNullOrEmpty(value) &&
- (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"'))
- {
- value = value.Substring(1, value.Length - 2);
- }
+ headers.Remove(key);
+ }
+ else
+ {
+ headers[key] = string.Join(",", value.Select((s) => QuoteIfNeeded(s)));
+ }
+ }
- return value;
+ // 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;
+ }
- public static void SetHeaderUnmodified(IHeaderDictionary headers, string key, StringValues? values)
+ private static string? DeQuote(string? value)
+ {
+ if (!string.IsNullOrEmpty(value) &&
+ (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"'))
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ value = value.Substring(1, value.Length - 2);
+ }
- if (string.IsNullOrEmpty(key))
- {
- throw new ArgumentNullException(nameof(key));
- }
- if (!values.HasValue || StringValues.IsNullOrEmpty(values.GetValueOrDefault()))
- {
- headers.Remove(key);
- }
- else
- {
- headers[key] = values.GetValueOrDefault();
- }
+ return value;
+ }
+
+ public static void SetHeaderUnmodified(IHeaderDictionary headers, string key, StringValues? values)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException(nameof(headers));
}
- public static void AppendHeaderJoined(IHeaderDictionary headers, string key, params string[] values)
+ if (string.IsNullOrEmpty(key))
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ throw new ArgumentNullException(nameof(key));
+ }
+ if (!values.HasValue || StringValues.IsNullOrEmpty(values.GetValueOrDefault()))
+ {
+ headers.Remove(key);
+ }
+ else
+ {
+ headers[key] = values.GetValueOrDefault();
+ }
+ }
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
+ public static void AppendHeaderJoined(IHeaderDictionary headers, string key, params string[] values)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException(nameof(headers));
+ }
- if (values == null || values.Length == 0)
- {
- return;
- }
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
- string? existing = GetHeader(headers, key);
- if (existing == null)
- {
- SetHeaderJoined(headers, key, values);
- }
- else
- {
- headers[key] = existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value)));
- }
+ if (values == null || values.Length == 0)
+ {
+ return;
}
- public static void AppendHeaderUnmodified(IHeaderDictionary headers, string key, StringValues values)
+ string? existing = GetHeader(headers, key);
+ if (existing == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ SetHeaderJoined(headers, key, values);
+ }
+ else
+ {
+ headers[key] = existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value)));
+ }
+ }
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
+ public static void AppendHeaderUnmodified(IHeaderDictionary headers, string key, StringValues values)
+ {
+ if (headers == null)
+ {
+ throw new ArgumentNullException(nameof(headers));
+ }
- if (values.Count == 0)
- {
- return;
- }
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
- var existing = GetHeaderUnmodified(headers, key);
- SetHeaderUnmodified(headers, key, StringValues.Concat(existing, values));
+ 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
index 02fca45dda..7ef353255b 100644
--- a/src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs
+++ b/src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs
@@ -4,64 +4,63 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal static class PathStringHelper
{
- internal static class PathStringHelper
- {
- // uint[] bits uses 1 cache line (Array info + 16 bytes)
- // bool[] would use 3 cache lines (Array info + 128 bytes)
- // So we use 128 bits rather than 128 bytes/bools
- private static readonly uint[] ValidPathChars = {
+ // uint[] bits uses 1 cache line (Array info + 16 bytes)
+ // bool[] would use 3 cache lines (Array info + 128 bytes)
+ // So we use 128 bits rather than 128 bytes/bools
+ private static readonly uint[] ValidPathChars = {
0b_0000_0000__0000_0000__0000_0000__0000_0000, // 0x00 - 0x1F
0b_0010_1111__1111_1111__1111_1111__1101_0010, // 0x20 - 0x3F
0b_1000_0111__1111_1111__1111_1111__1111_1111, // 0x40 - 0x5F
0b_0100_0111__1111_1111__1111_1111__1111_1110, // 0x60 - 0x7F
};
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsValidPathChar(char c)
- {
- // Use local array and uint .Length compare to elide the bounds check on array access
- var validChars = ValidPathChars;
- var i = (int)c;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsValidPathChar(char c)
+ {
+ // Use local array and uint .Length compare to elide the bounds check on array access
+ var validChars = ValidPathChars;
+ var i = (int)c;
- // Array is in chunks of 32 bits, so get offset by dividing by 32
- var offset = i >> 5; // i / 32;
- // Significant bit position is the remainder of the above calc; i % 32 => i & 31
- var significantBit = 1u << (i & 31);
+ // Array is in chunks of 32 bits, so get offset by dividing by 32
+ var offset = i >> 5; // i / 32;
+ // Significant bit position is the remainder of the above calc; i % 32 => i & 31
+ var significantBit = 1u << (i & 31);
- // Check offset in bounds and check if significant bit set
- return (uint)offset < (uint)validChars.Length &&
- ((validChars[offset] & significantBit) != 0);
- }
+ // Check offset in bounds and check if significant bit set
+ return (uint)offset < (uint)validChars.Length &&
+ ((validChars[offset] & significantBit) != 0);
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsPercentEncodedChar(string str, int index)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsPercentEncodedChar(string str, int index)
+ {
+ var len = (uint)str.Length;
+ if (str[index] == '%' && index < len - 2)
{
- var len = (uint)str.Length;
- if (str[index] == '%' && index < len - 2)
- {
- return AreFollowingTwoCharsHex(str, index);
- }
-
- return false;
+ return AreFollowingTwoCharsHex(str, index);
}
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static bool AreFollowingTwoCharsHex(string str, int index)
- {
- Debug.Assert(index < str.Length - 2);
+ return false;
+ }
- var c1 = str[index + 1];
- var c2 = str[index + 2];
- return IsHexadecimalChar(c1) && IsHexadecimalChar(c2);
- }
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static bool AreFollowingTwoCharsHex(string str, int index)
+ {
+ Debug.Assert(index < str.Length - 2);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsHexadecimalChar(char c)
- {
- // Between 0 - 9 or uppercased between A - F
- return (uint)(c - '0') <= 9 || (uint)((c & ~0x20) - 'A') <= ('F' - 'A');
- }
+ var c1 = str[index + 1];
+ var c2 = str[index + 2];
+ return IsHexadecimalChar(c1) && IsHexadecimalChar(c2);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsHexadecimalChar(char c)
+ {
+ // Between 0 - 9 or uppercased between A - F
+ return (uint)(c - '0') <= 9 || (uint)((c & ~0x20) - 'A') <= ('F' - 'A');
}
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IAcceptsMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IAcceptsMetadata.cs
index ce20bb9fdc..482cabf76e 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IAcceptsMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IAcceptsMetadata.cs
@@ -4,27 +4,26 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Interface for accepting request media types.
+/// </summary>
+public interface IAcceptsMetadata
{
/// <summary>
- /// Interface for accepting request media types.
+ /// Gets a list of the allowed request content types.
+ /// If the incoming request does not have a <c>Content-Type</c> with one of these values, the request will be rejected with a 415 response.
/// </summary>
- public interface IAcceptsMetadata
- {
- /// <summary>
- /// Gets a list of the allowed request content types.
- /// If the incoming request does not have a <c>Content-Type</c> with one of these values, the request will be rejected with a 415 response.
- /// </summary>
- IReadOnlyList<string> ContentTypes { get; }
+ IReadOnlyList<string> ContentTypes { get; }
- /// <summary>
- /// Gets the type being read from the request.
- /// </summary>
- Type? RequestType { get; }
+ /// <summary>
+ /// Gets the type being read from the request.
+ /// </summary>
+ Type? RequestType { get; }
- /// <summary>
- /// Gets a value that determines if the request body is optional.
- /// </summary>
- bool IsOptional { get; }
- }
+ /// <summary>
+ /// Gets a value that determines if the request body is optional.
+ /// </summary>
+ bool IsOptional { get; }
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IFromBodyMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IFromBodyMetadata.cs
index 8654c25d94..70b92a8926 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IFromBodyMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IFromBodyMetadata.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Interface marking attributes that specify a parameter should be bound using the request body.
+/// </summary>
+public interface IFromBodyMetadata
{
/// <summary>
- /// Interface marking attributes that specify a parameter should be bound using the request body.
+ /// Gets whether empty input should be rejected or treated as valid.
/// </summary>
- public interface IFromBodyMetadata
- {
- /// <summary>
- /// Gets whether empty input should be rejected or treated as valid.
- /// </summary>
- bool AllowEmpty => false;
- }
+ bool AllowEmpty => false;
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IFromHeaderMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IFromHeaderMetadata.cs
index 2efde28809..21045907cf 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IFromHeaderMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IFromHeaderMetadata.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Interface marking attributes that specify a parameter should be bound using the request headers.
+/// </summary>
+public interface IFromHeaderMetadata
{
/// <summary>
- /// Interface marking attributes that specify a parameter should be bound using the request headers.
+ /// The request header name.
/// </summary>
- public interface IFromHeaderMetadata
- {
- /// <summary>
- /// The request header name.
- /// </summary>
- string? Name { get; }
- }
+ string? Name { get; }
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IFromQueryMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IFromQueryMetadata.cs
index 0afaa0b746..df9ce62040 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IFromQueryMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IFromQueryMetadata.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Interface marking attributes that specify a parameter should be bound using the request query string.
+/// </summary>
+public interface IFromQueryMetadata
{
/// <summary>
- /// Interface marking attributes that specify a parameter should be bound using the request query string.
+ /// The name of the query string field.
/// </summary>
- public interface IFromQueryMetadata
- {
- /// <summary>
- /// The name of the query string field.
- /// </summary>
- string? Name { get; }
- }
+ string? Name { get; }
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IFromRouteMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IFromRouteMetadata.cs
index 4f3b45ba24..ab53cbd77c 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IFromRouteMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IFromRouteMetadata.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Interface marking attributes that specify a parameter should be bound using route-data from the current request.
+/// </summary>
+public interface IFromRouteMetadata
{
/// <summary>
- /// Interface marking attributes that specify a parameter should be bound using route-data from the current request.
+ /// The <see cref="HttpRequest.RouteValues"/> name.
/// </summary>
- public interface IFromRouteMetadata
- {
- /// <summary>
- /// The <see cref="HttpRequest.RouteValues"/> name.
- /// </summary>
- string? Name { get; }
- }
+ string? Name { get; }
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IFromServiceMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IFromServiceMetadata.cs
index 6a995dec2e..c74fcd5997 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IFromServiceMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IFromServiceMetadata.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Interface marking attributes that specify a parameter should be bound using request services.
+/// </summary>
+public interface IFromServiceMetadata
{
- /// <summary>
- /// Interface marking attributes that specify a parameter should be bound using request services.
- /// </summary>
- public interface IFromServiceMetadata
- {
- }
}
diff --git a/src/Http/Http.Abstractions/src/Metadata/IProducesResponseTypeMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/IProducesResponseTypeMetadata.cs
index 7bbd8546d4..92bc264271 100644
--- a/src/Http/Http.Abstractions/src/Metadata/IProducesResponseTypeMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/IProducesResponseTypeMetadata.cs
@@ -1,26 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Defines a contract for outline the response type returned from an endpoint.
+/// </summary>
+public interface IProducesResponseTypeMetadata
{
/// <summary>
- /// Defines a contract for outline the response type returned from an endpoint.
+ /// Gets the optimistic return type of the action.
/// </summary>
- public interface IProducesResponseTypeMetadata
- {
- /// <summary>
- /// Gets the optimistic return type of the action.
- /// </summary>
- Type? Type { get; }
+ Type? Type { get; }
- /// <summary>
- /// Gets the HTTP status code of the response.
- /// </summary>
- int StatusCode { get; }
+ /// <summary>
+ /// Gets the HTTP status code of the response.
+ /// </summary>
+ int StatusCode { get; }
- /// <summary>
- /// Gets the content types supported by the metadata.
- /// </summary>
- IEnumerable<string> ContentTypes { get; }
- }
-} \ No newline at end of file
+ /// <summary>
+ /// Gets the content types supported by the metadata.
+ /// </summary>
+ IEnumerable<string> ContentTypes { get; }
+}
diff --git a/src/Http/Http.Abstractions/src/Metadata/ITagsMetadata.cs b/src/Http/Http.Abstractions/src/Metadata/ITagsMetadata.cs
index 47d119cc20..0fcd9ba31c 100644
--- a/src/Http/Http.Abstractions/src/Metadata/ITagsMetadata.cs
+++ b/src/Http/Http.Abstractions/src/Metadata/ITagsMetadata.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Metadata
+namespace Microsoft.AspNetCore.Http.Metadata;
+
+/// <summary>
+/// Defines a contract used to specify a collection of tags in <see cref="Endpoint.Metadata"/>.
+/// </summary>
+public interface ITagsMetadata
{
/// <summary>
- /// Defines a contract used to specify a collection of tags in <see cref="Endpoint.Metadata"/>.
+ /// Gets the collection of tags associated with the endpoint.
/// </summary>
- public interface ITagsMetadata
- {
- /// <summary>
- /// Gets the collection of tags associated with the endpoint.
- /// </summary>
- IReadOnlyList<string> Tags { get; }
- }
+ IReadOnlyList<string> Tags { get; }
}
diff --git a/src/Http/Http.Abstractions/src/PathString.cs b/src/Http/Http.Abstractions/src/PathString.cs
index fbcaa2bb87..1b5077cf99 100644
--- a/src/Http/Http.Abstractions/src/PathString.cs
+++ b/src/Http/Http.Abstractions/src/PathString.cs
@@ -9,490 +9,489 @@ using System.Text;
using Microsoft.AspNetCore.Http.Abstractions;
using Microsoft.AspNetCore.Internal;
-namespace Microsoft.AspNetCore.Http
+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 readonly struct PathString : IEquatable<PathString>
{
+ internal const int StackAllocThreshold = 128;
+
+ /// <summary>
+ /// Represents the empty path. This field is read-only.
+ /// </summary>
+ public static readonly PathString Empty = new(string.Empty);
+
/// <summary>
- /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string
+ /// Initialize 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>
- [TypeConverter(typeof(PathStringConverter))]
- public readonly struct PathString : IEquatable<PathString>
+ /// <param name="value">The unescaped path to be assigned to the Value property.</param>
+ public PathString(string? value)
{
- internal const int StackAllocThreshold = 128;
-
- /// <summary>
- /// Represents the empty path. This field is read-only.
- /// </summary>
- public static readonly PathString Empty = new(string.Empty);
-
- /// <summary>
- /// Initialize 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] != '/')
{
- if (!string.IsNullOrEmpty(value) && value[0] != '/')
- {
- throw new ArgumentException(Resources.FormatException_PathMustStartWithSlash(nameof(value)), nameof(value));
- }
- Value = value;
+ throw new ArgumentException(Resources.FormatException_PathMustStartWithSlash(nameof(value)), nameof(value));
}
+ Value = value;
+ }
- /// <summary>
- /// The unescaped path value
- /// </summary>
- public string? Value { get; }
+ /// <summary>
+ /// The unescaped path value
+ /// </summary>
+ public string? Value { get; }
- /// <summary>
- /// True if the path is not empty
- /// </summary>
- [MemberNotNullWhen(true, nameof(Value))]
- public bool HasValue
- {
- get { return !string.IsNullOrEmpty(Value); }
- }
+ /// <summary>
+ /// True if the path is not empty
+ /// </summary>
+ [MemberNotNullWhen(true, nameof(Value))]
+ 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 override string ToString()
+ /// <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 ToUriComponent();
+ return string.Empty;
}
- /// <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()
+ var value = Value;
+ var i = 0;
+ for (; i < value.Length; i++)
{
- if (!HasValue)
+ if (!PathStringHelper.IsValidPathChar(value[i]) || PathStringHelper.IsPercentEncodedChar(value, i))
{
- return string.Empty;
+ break;
}
+ }
- var value = Value;
- var i = 0;
- for (; i < value.Length; i++)
- {
- if (!PathStringHelper.IsValidPathChar(value[i]) || PathStringHelper.IsPercentEncodedChar(value, i))
- {
- break;
- }
- }
+ if (i < value.Length)
+ {
+ return ToEscapedUriComponent(value, i);
+ }
- if (i < value.Length)
- {
- return ToEscapedUriComponent(value, i);
- }
+ return value;
+ }
- return value;
- }
+ private static string ToEscapedUriComponent(string value, int i)
+ {
+ StringBuilder? buffer = null;
+
+ var start = 0;
+ var count = i;
+ var requiresEscaping = false;
- private static string ToEscapedUriComponent(string value, int i)
+ while (i < value.Length)
{
- StringBuilder? buffer = null;
+ var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(value, i);
+ if (PathStringHelper.IsValidPathChar(value[i]) || isPercentEncodedChar)
+ {
+ if (requiresEscaping)
+ {
+ // the current segment requires escape
+ buffer ??= new StringBuilder(value.Length * 3);
+ buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
- var start = 0;
- var count = i;
- var requiresEscaping = false;
+ requiresEscaping = false;
+ start = i;
+ count = 0;
+ }
- while (i < value.Length)
- {
- var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(value, i);
- if (PathStringHelper.IsValidPathChar(value[i]) || isPercentEncodedChar)
+ if (isPercentEncodedChar)
{
- if (requiresEscaping)
- {
- // the current segment requires escape
- 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++;
- }
+ count += 3;
+ i += 3;
}
else
{
- if (!requiresEscaping)
- {
- // the current segment doesn't require escape
- 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 (!requiresEscaping)
{
+ // the current segment doesn't require escape
buffer ??= new StringBuilder(value.Length * 3);
+ buffer.Append(value, start, count);
- if (requiresEscaping)
- {
- buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
- }
- else
- {
- buffer.Append(value, start, count);
- }
+ requiresEscaping = true;
+ start = i;
+ count = 0;
}
- return buffer?.ToString() ?? string.Empty;
+ count++;
+ i++;
}
}
- /// <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)
+ if (count == value.Length && !requiresEscaping)
{
- int position = uriComponent.IndexOf('%');
- if (position == -1)
- {
- return new PathString(uriComponent);
- }
- Span<char> pathBuffer = uriComponent.Length <= StackAllocThreshold ? stackalloc char[StackAllocThreshold] : new char[uriComponent.Length];
- uriComponent.CopyTo(pathBuffer);
- var length = UrlDecoder.DecodeInPlace(pathBuffer.Slice(position, uriComponent.Length - position));
- pathBuffer = pathBuffer.Slice(0, position + length);
- return new PathString(pathBuffer.ToString());
- }
-
- /// <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));
- }
- var uriComponent = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped);
- Span<char> pathBuffer = uriComponent.Length < StackAllocThreshold ? stackalloc char[StackAllocThreshold] : new char[uriComponent.Length + 1];
- pathBuffer[0] = '/';
- var length = UrlDecoder.DecodeRequestLine(uriComponent.AsSpan(), pathBuffer.Slice(1));
- pathBuffer = pathBuffer.Slice(0, length + 1);
- return new PathString(pathBuffer.ToString());
- }
-
- /// <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);
+ return value;
}
-
- /// <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)
+ else
{
- var value1 = Value ?? string.Empty;
- var value2 = other.Value ?? string.Empty;
- if (value1.StartsWith(value2, comparisonType))
+ if (count > 0)
{
- 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);
- }
+ buffer ??= new StringBuilder(value.Length * 3);
- /// <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] == '/')
+ if (requiresEscaping)
{
- remaining = new PathString(value1[value2.Length..]);
- return true;
+ buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
}
- }
- 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] == '/')
+ else
{
- matched = new PathString(value1.Substring(0, value2.Length));
- remaining = new PathString(value1[value2.Length..]);
- return true;
+ buffer.Append(value, start, count);
}
}
- remaining = Empty;
- matched = Empty;
- return false;
+
+ return buffer?.ToString() ?? string.Empty;
}
+ }
- /// <summary>
- /// Adds two PathString instances into a combined PathString value.
- /// </summary>
- /// <returns>The combined PathString value</returns>
- public PathString Add(PathString other)
+ /// <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)
+ {
+ int position = uriComponent.IndexOf('%');
+ if (position == -1)
{
- if (HasValue &&
- other.HasValue &&
- Value[^1] == '/')
- {
- // If the path string has a trailing slash and the other string has a leading slash, we need
- // to trim one of them.
- var combined = string.Concat(Value.AsSpan(), other.Value.AsSpan(1));
- return new PathString(combined);
- }
-
- return new PathString(Value + other.Value);
+ return new PathString(uriComponent);
}
+ Span<char> pathBuffer = uriComponent.Length <= StackAllocThreshold ? stackalloc char[StackAllocThreshold] : new char[uriComponent.Length];
+ uriComponent.CopyTo(pathBuffer);
+ var length = UrlDecoder.DecodeInPlace(pathBuffer.Slice(position, uriComponent.Length - position));
+ pathBuffer = pathBuffer.Slice(0, position + length);
+ return new PathString(pathBuffer.ToString());
+ }
- /// <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)
+ /// <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)
{
- return ToUriComponent() + other.ToUriComponent();
+ throw new ArgumentNullException(nameof(uri));
}
+ var uriComponent = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped);
+ Span<char> pathBuffer = uriComponent.Length < StackAllocThreshold ? stackalloc char[StackAllocThreshold] : new char[uriComponent.Length + 1];
+ pathBuffer[0] = '/';
+ var length = UrlDecoder.DecodeRequestLine(uriComponent.AsSpan(), pathBuffer.Slice(1));
+ pathBuffer = pathBuffer.Slice(0, length + 1);
+ return new PathString(pathBuffer.ToString());
+ }
- /// <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)
+ /// <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 Equals(other, StringComparison.OrdinalIgnoreCase);
+ 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>
- /// 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)
+ /// <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 (!HasValue && !other.HasValue)
+ if (value1.Length == value2.Length || value1[value2.Length] == '/')
{
+ remaining = new PathString(value1[value2.Length..]);
return true;
}
- return string.Equals(Value, other.Value, comparisonType);
}
+ remaining = Empty;
+ return false;
+ }
- /// <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)
+ /// <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 (obj is null)
+ if (value1.Length == value2.Length || value1[value2.Length] == '/')
{
- return !HasValue;
+ matched = new PathString(value1.Substring(0, value2.Length));
+ remaining = new PathString(value1[value2.Length..]);
+ return true;
}
- return obj is PathString pathString && Equals(pathString);
}
+ remaining = Empty;
+ matched = Empty;
+ return false;
+ }
- /// <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()
+ /// <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[^1] == '/')
{
- return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(Value) : 0);
+ // If the path string has a trailing slash and the other string has a leading slash, we need
+ // to trim one of them.
+ var combined = string.Concat(Value.AsSpan(), other.Value.AsSpan(1));
+ return new PathString(combined);
}
- /// <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);
- }
+ return new PathString(Value + other.Value);
+ }
- /// <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>
+ /// 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>
- /// </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>
+ /// 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>
- /// </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)
+ /// <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)
{
- // 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);
+ return true;
}
+ return string.Equals(Value, other.Value, comparisonType);
+ }
- /// <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)
+ /// <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 (obj is null)
{
- return left.Add(right);
+ return !HasValue;
}
+ return obj is PathString pathString && Equals(pathString);
+ }
- /// <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>
+ /// 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>
- /// 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);
+ /// <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);
}
- internal sealed class PathStringConverter : TypeConverter
+ /// <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)
{
- public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
- => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ return !left.Equals(right);
+ }
- public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
- => value is string @string
- ? PathString.ConvertFromString(@string)
- : base.ConvertFrom(context, culture, value);
+ /// <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());
+ }
- public override object? ConvertTo(ITypeDescriptorContext? context,
- CultureInfo? culture, object? value, Type destinationType)
- {
- if (destinationType == null)
- {
- throw new ArgumentNullException(nameof(destinationType));
- }
+ /// <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);
+ }
- return destinationType == typeof(string)
- ? value?.ToString() ?? string.Empty
- : base.ConvertTo(context, culture, value, destinationType);
+ /// <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 sealed class PathStringConverter : TypeConverter
+{
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ => value is string @string
+ ? PathString.ConvertFromString(@string)
+ : base.ConvertFrom(context, culture, value);
+
+ public override object? ConvertTo(ITypeDescriptorContext? context,
+ CultureInfo? culture, object? value, Type destinationType)
+ {
+ if (destinationType == null)
+ {
+ throw new ArgumentNullException(nameof(destinationType));
}
+
+ return destinationType == typeof(string)
+ ? value?.ToString() ?? string.Empty
+ : base.ConvertTo(context, culture, value, destinationType);
}
}
diff --git a/src/Http/Http.Abstractions/src/QueryString.cs b/src/Http/Http.Abstractions/src/QueryString.cs
index 5f03accd77..646f2756d4 100644
--- a/src/Http/Http.Abstractions/src/QueryString.cs
+++ b/src/Http/Http.Abstractions/src/QueryString.cs
@@ -8,291 +8,290 @@ using System.Text;
using System.Text.Encodings.Web;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string
+/// </summary>
+public readonly struct QueryString : IEquatable<QueryString>
{
/// <summary>
- /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string
+ /// Represents the empty query string. This field is read-only.
/// </summary>
- public readonly 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);
+ public static readonly QueryString Empty = new QueryString(string.Empty);
- /// <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)
+ /// <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] != '?')
{
- if (!string.IsNullOrEmpty(value) && value[0] != '?')
- {
- throw new ArgumentException("The leading '?' must be included for a non-empty query.", nameof(value));
- }
- Value = value;
+ 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; }
+ /// <summary>
+ /// The escaped query string with the leading '?' character
+ /// </summary>
+ public string? Value { get; }
- /// <summary>
- /// True if the query string is not empty
- /// </summary>
- public bool HasValue => !string.IsNullOrEmpty(Value);
+ /// <summary>
+ /// True if the query string is not empty
+ /// </summary>
+ public bool HasValue => !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 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 !string.IsNullOrEmpty(Value) ? Value!.Replace("#", "%23") : string.Empty;
+ }
- /// <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()
+ /// <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))
{
- // Escape things properly so System.Uri doesn't mis-interpret the data.
- return !string.IsNullOrEmpty(Value) ? Value!.Replace("#", "%23") : string.Empty;
+ return new QueryString(string.Empty);
}
+ return new QueryString(uriComponent);
+ }
- /// <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)
+ /// <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)
{
- if (string.IsNullOrEmpty(uriComponent))
- {
- return new QueryString(string.Empty);
- }
- return new QueryString(uriComponent);
+ throw new ArgumentNullException(nameof(uri));
}
- /// <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)
+ string queryValue = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped);
+ if (!string.IsNullOrEmpty(queryValue))
{
- 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);
+ 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)
+ /// <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)
{
- 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}");
+ throw new ArgumentNullException(nameof(name));
}
- /// <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)
+ if (!string.IsNullOrEmpty(value))
{
- var builder = new StringBuilder();
- var first = true;
- foreach (var pair in parameters)
- {
- AppendKeyValuePair(builder, pair.Key, pair.Value, first);
- first = false;
- }
-
- return new QueryString(builder.ToString());
+ 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, StringValues>> parameters)
+ /// <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();
+ var first = true;
+ foreach (var pair in parameters)
{
- var builder = new StringBuilder();
- var 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());
+ AppendKeyValuePair(builder, pair.Key, pair.Value, first);
+ first = false;
}
- /// <summary>
- /// Concatenates <paramref name="other"/> to the current query string.
- /// </summary>
- /// <param name="other">The <see cref="QueryString"/> to concatenate.</param>
- /// <returns>The concatenated <see cref="QueryString"/>.</returns>
- public QueryString Add(QueryString other)
- {
- if (!HasValue || Value!.Equals("?", StringComparison.Ordinal))
- {
- return other;
- }
- if (!other.HasValue || other.Value!.Equals("?", StringComparison.Ordinal))
- {
- return this;
- }
+ return new QueryString(builder.ToString());
+ }
- // ?name1=value1 Add ?name2=value2 returns ?name1=value1&name2=value2
- return new QueryString(string.Concat(Value, "&", other.Value.AsSpan(1)));
- }
+ /// <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();
+ var first = true;
- /// <summary>
- /// Concatenates a query string with <paramref name="name"/> and <paramref name="value"/>
- /// to the current query string.
- /// </summary>
- /// <param name="name">The name of the query string to concatenate.</param>
- /// <param name="value">The value of the query string to concatenate.</param>
- /// <returns>The concatenated <see cref="QueryString"/>.</returns>
- public QueryString Add(string name, string value)
+ foreach (var pair in parameters)
{
- if (name == null)
+ // If nothing in this pair.Values, append null value and continue
+ if (StringValues.IsNullOrEmpty(pair.Value))
{
- throw new ArgumentNullException(nameof(name));
+ AppendKeyValuePair(builder, pair.Key, null, first);
+ first = false;
+ continue;
}
-
- if (!HasValue || Value!.Equals("?", StringComparison.Ordinal))
+ // Otherwise, loop through values in pair.Value
+ foreach (var value in pair.Value)
{
- return Create(name, value);
+ AppendKeyValuePair(builder, pair.Key, value, first);
+ first = false;
}
-
- var builder = new StringBuilder(Value);
- AppendKeyValuePair(builder, name, value, first: false);
- return new QueryString(builder.ToString());
}
- /// <summary>
- /// Evalutes if the current query string is equal to <paramref name="other"/>.
- /// </summary>
- /// <param name="other">The <see cref="QueryString"/> to compare.</param>
- /// <returns><see langword="true"/> if the query strings are equal.</returns>
- public bool Equals(QueryString other)
+ return new QueryString(builder.ToString());
+ }
+
+ /// <summary>
+ /// Concatenates <paramref name="other"/> to the current query string.
+ /// </summary>
+ /// <param name="other">The <see cref="QueryString"/> to concatenate.</param>
+ /// <returns>The concatenated <see cref="QueryString"/>.</returns>
+ public QueryString Add(QueryString other)
+ {
+ if (!HasValue || Value!.Equals("?", StringComparison.Ordinal))
{
- if (!HasValue && !other.HasValue)
- {
- return true;
- }
- return string.Equals(Value, other.Value, StringComparison.Ordinal);
+ return other;
}
-
- /// <summary>
- /// Evaluates if the current query string is equal to an object <paramref name="obj"/>.
- /// </summary>
- /// <param name="obj">An object to compare.</param>
- /// <returns><see langword="true" /> if the query strings are equal.</returns>
- public override bool Equals(object? obj)
+ if (!other.HasValue || other.Value!.Equals("?", StringComparison.Ordinal))
{
- if (ReferenceEquals(null, obj))
- {
- return !HasValue;
- }
- return obj is QueryString && Equals((QueryString)obj);
+ return this;
}
- /// <summary>
- /// Gets a hash code for the value.
- /// </summary>
- /// <returns>The hash code as an <see cref="int"/>.</returns>
- public override int GetHashCode()
+ // ?name1=value1 Add ?name2=value2 returns ?name1=value1&name2=value2
+ return new QueryString(string.Concat(Value, "&", other.Value.AsSpan(1)));
+ }
+
+ /// <summary>
+ /// Concatenates a query string with <paramref name="name"/> and <paramref name="value"/>
+ /// to the current query string.
+ /// </summary>
+ /// <param name="name">The name of the query string to concatenate.</param>
+ /// <param name="value">The value of the query string to concatenate.</param>
+ /// <returns>The concatenated <see cref="QueryString"/>.</returns>
+ public QueryString Add(string name, string value)
+ {
+ if (name == null)
{
- return (HasValue ? Value!.GetHashCode() : 0);
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Evaluates if one query string is equal to another.
- /// </summary>
- /// <param name="left">A <see cref="QueryString"/> instance.</param>
- /// <param name="right">A <see cref="QueryString"/> instance.</param>
- /// <returns><see langword="true" /> if the query strings are equal.</returns>
- public static bool operator ==(QueryString left, QueryString right)
+ if (!HasValue || Value!.Equals("?", StringComparison.Ordinal))
{
- return left.Equals(right);
+ return Create(name, value);
}
- /// <summary>
- /// Evaluates if one query string is not equal to another.
- /// </summary>
- /// <param name="left">A <see cref="QueryString"/> instance.</param>
- /// <param name="right">A <see cref="QueryString"/> instance.</param>
- /// <returns><see langword="true" /> if the query strings are not equal.</returns>
- public static bool operator !=(QueryString left, QueryString right)
+ var builder = new StringBuilder(Value);
+ AppendKeyValuePair(builder, name, value, first: false);
+ return new QueryString(builder.ToString());
+ }
+
+ /// <summary>
+ /// Evalutes if the current query string is equal to <paramref name="other"/>.
+ /// </summary>
+ /// <param name="other">The <see cref="QueryString"/> to compare.</param>
+ /// <returns><see langword="true"/> if the query strings are equal.</returns>
+ public bool Equals(QueryString other)
+ {
+ if (!HasValue && !other.HasValue)
{
- return !left.Equals(right);
+ return true;
}
+ return string.Equals(Value, other.Value, StringComparison.Ordinal);
+ }
- /// <summary>
- /// Concatenates <paramref name="left"/> and <paramref name="right"/> into a single query string.
- /// </summary>
- /// <param name="left">A <see cref="QueryString"/> instance.</param>
- /// <param name="right">A <see cref="QueryString"/> instance.</param>
- /// <returns>The concatenated <see cref="QueryString"/>.</returns>
- public static QueryString operator +(QueryString left, QueryString right)
+ /// <summary>
+ /// Evaluates if the current query string is equal to an object <paramref name="obj"/>.
+ /// </summary>
+ /// <param name="obj">An object to compare.</param>
+ /// <returns><see langword="true" /> if the query strings are equal.</returns>
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
{
- return left.Add(right);
+ return !HasValue;
}
+ return obj is QueryString && Equals((QueryString)obj);
+ }
+
+ /// <summary>
+ /// Gets a hash code for the value.
+ /// </summary>
+ /// <returns>The hash code as an <see cref="int"/>.</returns>
+ public override int GetHashCode()
+ {
+ return (HasValue ? Value!.GetHashCode() : 0);
+ }
+
+ /// <summary>
+ /// Evaluates if one query string is equal to another.
+ /// </summary>
+ /// <param name="left">A <see cref="QueryString"/> instance.</param>
+ /// <param name="right">A <see cref="QueryString"/> instance.</param>
+ /// <returns><see langword="true" /> if the query strings are equal.</returns>
+ public static bool operator ==(QueryString left, QueryString right)
+ {
+ return left.Equals(right);
+ }
+
+ /// <summary>
+ /// Evaluates if one query string is not equal to another.
+ /// </summary>
+ /// <param name="left">A <see cref="QueryString"/> instance.</param>
+ /// <param name="right">A <see cref="QueryString"/> instance.</param>
+ /// <returns><see langword="true" /> if the query strings are not equal.</returns>
+ public static bool operator !=(QueryString left, QueryString right)
+ {
+ return !left.Equals(right);
+ }
- private static void AppendKeyValuePair(StringBuilder builder, string key, string? value, bool first)
+ /// <summary>
+ /// Concatenates <paramref name="left"/> and <paramref name="right"/> into a single query string.
+ /// </summary>
+ /// <param name="left">A <see cref="QueryString"/> instance.</param>
+ /// <param name="right">A <see cref="QueryString"/> instance.</param>
+ /// <returns>The concatenated <see cref="QueryString"/>.</returns>
+ 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(first ? '?' : '&');
- builder.Append(UrlEncoder.Default.Encode(key));
- builder.Append('=');
- if (!string.IsNullOrEmpty(value))
- {
- builder.Append(UrlEncoder.Default.Encode(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
index ad4254bd1b..6fb2e5a432 100644
--- a/src/Http/Http.Abstractions/src/RequestDelegate.cs
+++ b/src/Http/Http.Abstractions/src/RequestDelegate.cs
@@ -3,12 +3,11 @@
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
+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);
diff --git a/src/Http/Http.Abstractions/src/RequestDelegateResult.cs b/src/Http/Http.Abstractions/src/RequestDelegateResult.cs
index 88ddd28a41..5ea60fe338 100644
--- a/src/Http/Http.Abstractions/src/RequestDelegateResult.cs
+++ b/src/Http/Http.Abstractions/src/RequestDelegateResult.cs
@@ -3,31 +3,29 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// The result of creating a <see cref="RequestDelegate" /> from a <see cref="Delegate" />
+/// </summary>
+public sealed class RequestDelegateResult
{
/// <summary>
- /// The result of creating a <see cref="RequestDelegate" /> from a <see cref="Delegate" />
+ /// Creates a new instance of <see cref="RequestDelegateResult"/>.
/// </summary>
- public sealed class RequestDelegateResult
+ public RequestDelegateResult(RequestDelegate requestDelegate, IReadOnlyList<object> metadata)
{
- /// <summary>
- /// Creates a new instance of <see cref="RequestDelegateResult"/>.
- /// </summary>
- public RequestDelegateResult(RequestDelegate requestDelegate, IReadOnlyList<object> metadata)
- {
- RequestDelegate = requestDelegate;
- EndpointMetadata = metadata;
- }
-
- /// <summary>
- /// Gets the <see cref="RequestDelegate" />
- /// </summary>
- public RequestDelegate RequestDelegate { get;}
-
- /// <summary>
- /// Gets endpoint metadata inferred from creating the <see cref="RequestDelegate" />
- /// </summary>
- public IReadOnlyList<object> EndpointMetadata { get;}
+ RequestDelegate = requestDelegate;
+ EndpointMetadata = metadata;
}
+ /// <summary>
+ /// Gets the <see cref="RequestDelegate" />
+ /// </summary>
+ public RequestDelegate RequestDelegate { get; }
+
+ /// <summary>
+ /// Gets endpoint metadata inferred from creating the <see cref="RequestDelegate" />
+ /// </summary>
+ public IReadOnlyList<object> EndpointMetadata { get; }
}
diff --git a/src/Http/Http.Abstractions/src/Routing/Endpoint.cs b/src/Http/Http.Abstractions/src/Routing/Endpoint.cs
index bf1e2fa076..a3b1718687 100644
--- a/src/Http/Http.Abstractions/src/Routing/Endpoint.cs
+++ b/src/Http/Http.Abstractions/src/Routing/Endpoint.cs
@@ -1,52 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents a logical endpoint in an application.
+/// </summary>
+public class Endpoint
{
/// <summary>
- /// Represents a logical endpoint in an application.
+ /// Creates a new instance of <see cref="Endpoint"/>.
/// </summary>
- public class Endpoint
+ /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
+ /// <param name="metadata">
+ /// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
+ /// </param>
+ /// <param name="displayName">
+ /// The informational display name of the endpoint. May be null.
+ /// </param>
+ public Endpoint(
+ RequestDelegate? requestDelegate,
+ EndpointMetadataCollection? metadata,
+ string? displayName)
{
- /// <summary>
- /// Creates a new instance of <see cref="Endpoint"/>.
- /// </summary>
- /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
- /// <param name="metadata">
- /// The endpoint <see cref="EndpointMetadataCollection"/>. May be null.
- /// </param>
- /// <param name="displayName">
- /// The informational display name of the endpoint. May be null.
- /// </param>
- public Endpoint(
- RequestDelegate? requestDelegate,
- EndpointMetadataCollection? metadata,
- string? displayName)
- {
- // All are allowed to be null
- RequestDelegate = requestDelegate;
- Metadata = metadata ?? EndpointMetadataCollection.Empty;
- DisplayName = displayName;
- }
+ // All are allowed to be null
+ RequestDelegate = requestDelegate;
+ Metadata = metadata ?? EndpointMetadataCollection.Empty;
+ DisplayName = displayName;
+ }
- /// <summary>
- /// Gets the informational display name of this endpoint.
- /// </summary>
- public string? DisplayName { get; }
+ /// <summary>
+ /// Gets the informational display name of this endpoint.
+ /// </summary>
+ public string? DisplayName { get; }
- /// <summary>
- /// Gets the collection of metadata associated with this endpoint.
- /// </summary>
- public EndpointMetadataCollection Metadata { get; }
+ /// <summary>
+ /// Gets the collection of metadata associated with this endpoint.
+ /// </summary>
+ public EndpointMetadataCollection Metadata { get; }
- /// <summary>
- /// Gets the delegate used to process requests for the endpoint.
- /// </summary>
- public RequestDelegate? RequestDelegate { get; }
+ /// <summary>
+ /// Gets the delegate used to process requests for the endpoint.
+ /// </summary>
+ public RequestDelegate? RequestDelegate { get; }
- /// <summary>
- /// Returns a string representation of the endpoint.
- /// </summary>
- public override string? ToString() => DisplayName ?? base.ToString();
- }
+ /// <summary>
+ /// Returns a string representation of the endpoint.
+ /// </summary>
+ public override string? ToString() => DisplayName ?? base.ToString();
}
diff --git a/src/Http/Http.Abstractions/src/Routing/EndpointHttpContextExtensions.cs b/src/Http/Http.Abstractions/src/Routing/EndpointHttpContextExtensions.cs
index 2c03bde3d3..77aa9af3bf 100644
--- a/src/Http/Http.Abstractions/src/Routing/EndpointHttpContextExtensions.cs
+++ b/src/Http/Http.Abstractions/src/Routing/EndpointHttpContextExtensions.cs
@@ -4,67 +4,66 @@
using System;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods to expose Endpoint on HttpContext.
+/// </summary>
+public static class EndpointHttpContextExtensions
{
/// <summary>
- /// Extension methods to expose Endpoint on HttpContext.
+ /// Extension method for getting the <see cref="Endpoint"/> for the current request.
/// </summary>
- public static class EndpointHttpContextExtensions
+ /// <param name="context">The <see cref="HttpContext"/> context.</param>
+ /// <returns>The <see cref="Endpoint"/>.</returns>
+ public static Endpoint? GetEndpoint(this HttpContext context)
{
- /// <summary>
- /// Extension method for getting the <see cref="Endpoint"/> for the current request.
- /// </summary>
- /// <param name="context">The <see cref="HttpContext"/> context.</param>
- /// <returns>The <see cref="Endpoint"/>.</returns>
- public static Endpoint? GetEndpoint(this HttpContext context)
+ if (context == null)
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return context.Features.Get<IEndpointFeature>()?.Endpoint;
+ }
- return context.Features.Get<IEndpointFeature>()?.Endpoint;
+ /// <summary>
+ /// Extension method for setting the <see cref="Endpoint"/> for the current request.
+ /// </summary>
+ /// <param name="context">The <see cref="HttpContext"/> context.</param>
+ /// <param name="endpoint">The <see cref="Endpoint"/>.</param>
+ public static void SetEndpoint(this HttpContext context, Endpoint? endpoint)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
}
- /// <summary>
- /// Extension method for setting the <see cref="Endpoint"/> for the current request.
- /// </summary>
- /// <param name="context">The <see cref="HttpContext"/> context.</param>
- /// <param name="endpoint">The <see cref="Endpoint"/>.</param>
- public static void SetEndpoint(this HttpContext context, Endpoint? endpoint)
+ var feature = context.Features.Get<IEndpointFeature>();
+
+ if (endpoint != null)
{
- if (context == null)
+ if (feature == null)
{
- throw new ArgumentNullException(nameof(context));
+ feature = new EndpointFeature();
+ context.Features.Set(feature);
}
- var feature = context.Features.Get<IEndpointFeature>();
-
- if (endpoint != null)
+ feature.Endpoint = endpoint;
+ }
+ else
+ {
+ if (feature == null)
{
- if (feature == null)
- {
- feature = new EndpointFeature();
- context.Features.Set(feature);
- }
-
- feature.Endpoint = endpoint;
+ // No endpoint to set and no feature on context. Do nothing
+ return;
}
- else
- {
- if (feature == null)
- {
- // No endpoint to set and no feature on context. Do nothing
- return;
- }
- feature.Endpoint = null;
- }
+ feature.Endpoint = null;
}
+ }
- private class EndpointFeature : IEndpointFeature
- {
- public Endpoint? Endpoint { get; set; }
- }
+ private class EndpointFeature : IEndpointFeature
+ {
+ public Endpoint? Endpoint { get; set; }
}
}
diff --git a/src/Http/Http.Abstractions/src/Routing/EndpointMetadataCollection.cs b/src/Http/Http.Abstractions/src/Routing/EndpointMetadataCollection.cs
index bab598ec6d..70bfff4508 100644
--- a/src/Http/Http.Abstractions/src/Routing/EndpointMetadataCollection.cs
+++ b/src/Http/Http.Abstractions/src/Routing/EndpointMetadataCollection.cs
@@ -8,201 +8,200 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A collection of arbitrary metadata associated with an endpoint.
+/// </summary>
+/// <remarks>
+/// <see cref="EndpointMetadataCollection"/> instances contain a list of metadata items
+/// of arbitrary types. The metadata items are stored as an ordered collection with
+/// items arranged in ascending order of precedence.
+/// </remarks>
+public sealed class EndpointMetadataCollection : IReadOnlyList<object>
{
/// <summary>
- /// A collection of arbitrary metadata associated with an endpoint.
+ /// An empty <see cref="EndpointMetadataCollection"/>.
/// </summary>
- /// <remarks>
- /// <see cref="EndpointMetadataCollection"/> instances contain a list of metadata items
- /// of arbitrary types. The metadata items are stored as an ordered collection with
- /// items arranged in ascending order of precedence.
- /// </remarks>
- public sealed class EndpointMetadataCollection : IReadOnlyList<object>
- {
- /// <summary>
- /// An empty <see cref="EndpointMetadataCollection"/>.
- /// </summary>
- public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty<object>());
+ public static readonly EndpointMetadataCollection Empty = new EndpointMetadataCollection(Array.Empty<object>());
- private readonly object[] _items;
- private readonly ConcurrentDictionary<Type, object[]> _cache;
+ private readonly object[] _items;
+ private readonly ConcurrentDictionary<Type, object[]> _cache;
- /// <summary>
- /// Creates a new <see cref="EndpointMetadataCollection"/>.
- /// </summary>
- /// <param name="items">The metadata items.</param>
- public EndpointMetadataCollection(IEnumerable<object> items)
- {
- if (items == null)
- {
- throw new ArgumentNullException(nameof(items));
- }
-
- _items = items.ToArray();
- _cache = new ConcurrentDictionary<Type, object[]>();
- }
-
- /// <summary>
- /// Creates a new <see cref="EndpointMetadataCollection"/>.
- /// </summary>
- /// <param name="items">The metadata items.</param>
- public EndpointMetadataCollection(params object[] items)
- : this((IEnumerable<object>)items)
+ /// <summary>
+ /// Creates a new <see cref="EndpointMetadataCollection"/>.
+ /// </summary>
+ /// <param name="items">The metadata items.</param>
+ public EndpointMetadataCollection(IEnumerable<object> items)
+ {
+ if (items == null)
{
+ throw new ArgumentNullException(nameof(items));
}
- /// <summary>
- /// Gets the item at <paramref name="index"/>.
- /// </summary>
- /// <param name="index">The index of the item to retrieve.</param>
- /// <returns>The item at <paramref name="index"/>.</returns>
- public object this[int index] => _items[index];
+ _items = items.ToArray();
+ _cache = new ConcurrentDictionary<Type, object[]>();
+ }
- /// <summary>
- /// Gets the count of metadata items.
- /// </summary>
- public int Count => _items.Length;
+ /// <summary>
+ /// Creates a new <see cref="EndpointMetadataCollection"/>.
+ /// </summary>
+ /// <param name="items">The metadata items.</param>
+ public EndpointMetadataCollection(params object[] items)
+ : this((IEnumerable<object>)items)
+ {
+ }
- /// <summary>
- /// Gets the most significant metadata item of type <typeparamref name="T"/>.
- /// </summary>
- /// <typeparam name="T">The type of metadata to retrieve.</typeparam>
- /// <returns>
- /// The most significant metadata of type <typeparamref name="T"/> or <c>null</c>.
- /// </returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public T? GetMetadata<T>() where T : class
- {
- if (_cache.TryGetValue(typeof(T), out var obj))
- {
- var result = (T[])obj;
- var length = result.Length;
- return length > 0 ? result[length - 1] : default;
- }
+ /// <summary>
+ /// Gets the item at <paramref name="index"/>.
+ /// </summary>
+ /// <param name="index">The index of the item to retrieve.</param>
+ /// <returns>The item at <paramref name="index"/>.</returns>
+ public object this[int index] => _items[index];
- return GetMetadataSlow<T>();
- }
+ /// <summary>
+ /// Gets the count of metadata items.
+ /// </summary>
+ public int Count => _items.Length;
- private T? GetMetadataSlow<T>() where T : class
+ /// <summary>
+ /// Gets the most significant metadata item of type <typeparamref name="T"/>.
+ /// </summary>
+ /// <typeparam name="T">The type of metadata to retrieve.</typeparam>
+ /// <returns>
+ /// The most significant metadata of type <typeparamref name="T"/> or <c>null</c>.
+ /// </returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T? GetMetadata<T>() where T : class
+ {
+ if (_cache.TryGetValue(typeof(T), out var obj))
{
- var result = GetOrderedMetadataSlow<T>();
+ var result = (T[])obj;
var length = result.Length;
return length > 0 ? result[length - 1] : default;
}
- /// <summary>
- /// Gets the metadata items of type <typeparamref name="T"/> in ascending
- /// order of precedence.
- /// </summary>
- /// <typeparam name="T">The type of metadata.</typeparam>
- /// <returns>A sequence of metadata items of <typeparamref name="T"/>.</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public IReadOnlyList<T> GetOrderedMetadata<T>() where T : class
- {
- if (_cache.TryGetValue(typeof(T), out var result))
- {
- return (T[])result;
- }
+ return GetMetadataSlow<T>();
+ }
- return GetOrderedMetadataSlow<T>();
- }
+ private T? GetMetadataSlow<T>() where T : class
+ {
+ var result = GetOrderedMetadataSlow<T>();
+ var length = result.Length;
+ return length > 0 ? result[length - 1] : default;
+ }
- private T[] GetOrderedMetadataSlow<T>() where T : class
+ /// <summary>
+ /// Gets the metadata items of type <typeparamref name="T"/> in ascending
+ /// order of precedence.
+ /// </summary>
+ /// <typeparam name="T">The type of metadata.</typeparam>
+ /// <returns>A sequence of metadata items of <typeparamref name="T"/>.</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IReadOnlyList<T> GetOrderedMetadata<T>() where T : class
+ {
+ if (_cache.TryGetValue(typeof(T), out var result))
{
- // Perf: avoid allocations totally for the common case where there are no matching metadata.
- List<T>? matches = null;
+ return (T[])result;
+ }
+
+ return GetOrderedMetadataSlow<T>();
+ }
+
+ private T[] GetOrderedMetadataSlow<T>() where T : class
+ {
+ // Perf: avoid allocations totally for the common case where there are no matching metadata.
+ List<T>? matches = null;
- var items = _items;
- for (var i = 0; i < items.Length; i++)
+ var items = _items;
+ for (var i = 0; i < items.Length; i++)
+ {
+ if (items[i] is T item)
{
- if (items[i] is T item)
- {
- matches ??= new List<T>();
- matches.Add(item);
- }
+ matches ??= new List<T>();
+ matches.Add(item);
}
-
- var results = matches == null ? Array.Empty<T>() : matches.ToArray();
- _cache.TryAdd(typeof(T), results);
- return results;
}
- /// <summary>
- /// Gets an <see cref="IEnumerator"/> of all metadata items.
- /// </summary>
- /// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
- public Enumerator GetEnumerator() => new Enumerator(this);
+ var results = matches == null ? Array.Empty<T>() : matches.ToArray();
+ _cache.TryAdd(typeof(T), results);
+ return results;
+ }
+
+ /// <summary>
+ /// Gets an <see cref="IEnumerator"/> of all metadata items.
+ /// </summary>
+ /// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
+ public Enumerator GetEnumerator() => new Enumerator(this);
+
+ /// <summary>
+ /// Gets an <see cref="IEnumerator{Object}"/> of all metadata items.
+ /// </summary>
+ /// <returns>An <see cref="IEnumerator{Object}"/> of all metadata items.</returns>
+ IEnumerator<object> IEnumerable<object>.GetEnumerator() => GetEnumerator();
+
+ /// <summary>
+ /// Gets an <see cref="IEnumerator"/> of all metadata items.
+ /// </summary>
+ /// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ /// <summary>
+ /// Enumerates the elements of an <see cref="EndpointMetadataCollection"/>.
+ /// </summary>
+ public struct Enumerator : IEnumerator<object?>
+ {
+#pragma warning disable IDE0044
+ // Intentionally not readonly to prevent defensive struct copies
+ private object[] _items;
+#pragma warning restore IDE0044
+ private int _index;
+
+ internal Enumerator(EndpointMetadataCollection collection)
+ {
+ _items = collection._items;
+ _index = 0;
+ Current = null;
+ }
/// <summary>
- /// Gets an <see cref="IEnumerator{Object}"/> of all metadata items.
+ /// Gets the element at the current position of the enumerator
/// </summary>
- /// <returns>An <see cref="IEnumerator{Object}"/> of all metadata items.</returns>
- IEnumerator<object> IEnumerable<object>.GetEnumerator() => GetEnumerator();
+ public object? Current { get; private set; }
/// <summary>
- /// Gets an <see cref="IEnumerator"/> of all metadata items.
+ /// Releases all resources used by the <see cref="Enumerator"/>.
/// </summary>
- /// <returns>An <see cref="IEnumerator"/> of all metadata items.</returns>
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ public void Dispose()
+ {
+ }
/// <summary>
- /// Enumerates the elements of an <see cref="EndpointMetadataCollection"/>.
+ /// Advances the enumerator to the next element of the <see cref="Enumerator"/>.
/// </summary>
- public struct Enumerator : IEnumerator<object?>
+ /// <returns>
+ /// <c>true</c> if the enumerator was successfully advanced to the next element;
+ /// <c>false</c> if the enumerator has passed the end of the collection.
+ /// </returns>
+ public bool MoveNext()
{
-#pragma warning disable IDE0044
- // Intentionally not readonly to prevent defensive struct copies
- private object[] _items;
-#pragma warning restore IDE0044
- private int _index;
-
- internal Enumerator(EndpointMetadataCollection collection)
+ if (_index < _items.Length)
{
- _items = collection._items;
- _index = 0;
- Current = null;
+ Current = _items[_index++];
+ return true;
}
- /// <summary>
- /// Gets the element at the current position of the enumerator
- /// </summary>
- public object? Current { get; private set; }
-
- /// <summary>
- /// Releases all resources used by the <see cref="Enumerator"/>.
- /// </summary>
- public void Dispose()
- {
- }
-
- /// <summary>
- /// Advances the enumerator to the next element of the <see cref="Enumerator"/>.
- /// </summary>
- /// <returns>
- /// <c>true</c> if the enumerator was successfully advanced to the next element;
- /// <c>false</c> if the enumerator has passed the end of the collection.
- /// </returns>
- public bool MoveNext()
- {
- if (_index < _items.Length)
- {
- Current = _items[_index++];
- return true;
- }
-
- Current = null;
- return false;
- }
+ Current = null;
+ return false;
+ }
- /// <summary>
- /// Sets the enumerator to its initial position, which is before the first element in the collection.
- /// </summary>
- public void Reset()
- {
- _index = 0;
- Current = null;
- }
+ /// <summary>
+ /// Sets the enumerator to its initial position, which is before the first element in the collection.
+ /// </summary>
+ public void Reset()
+ {
+ _index = 0;
+ Current = null;
}
}
}
diff --git a/src/Http/Http.Abstractions/src/Routing/IEndpointFeature.cs b/src/Http/Http.Abstractions/src/Routing/IEndpointFeature.cs
index c7e0da5019..64bd861f62 100644
--- a/src/Http/Http.Abstractions/src/Routing/IEndpointFeature.cs
+++ b/src/Http/Http.Abstractions/src/Routing/IEndpointFeature.cs
@@ -1,18 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// A feature interface for endpoint routing. Use <see cref="HttpContext.Features"/>
+/// to access an instance associated with the current request.
+/// </summary>
+public interface IEndpointFeature
{
/// <summary>
- /// A feature interface for endpoint routing. Use <see cref="HttpContext.Features"/>
- /// to access an instance associated with the current request.
+ /// Gets or sets the selected <see cref="Http.Endpoint"/> for the current
+ /// request.
/// </summary>
- public interface IEndpointFeature
- {
- /// <summary>
- /// Gets or sets the selected <see cref="Http.Endpoint"/> for the current
- /// request.
- /// </summary>
- Endpoint? Endpoint { get; set; }
- }
+ Endpoint? Endpoint { get; set; }
}
diff --git a/src/Http/Http.Abstractions/src/Routing/IRouteValuesFeature.cs b/src/Http/Http.Abstractions/src/Routing/IRouteValuesFeature.cs
index 686c3b56c5..f03b818489 100644
--- a/src/Http/Http.Abstractions/src/Routing/IRouteValuesFeature.cs
+++ b/src/Http/Http.Abstractions/src/Routing/IRouteValuesFeature.cs
@@ -3,18 +3,17 @@
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// A feature interface for routing values. Use <see cref="HttpContext.Features"/>
+/// to access the values associated with the current request.
+/// </summary>
+public interface IRouteValuesFeature
{
/// <summary>
- /// A feature interface for routing values. Use <see cref="HttpContext.Features"/>
- /// to access the values associated with the current request.
+ /// Gets or sets the <see cref="RouteValueDictionary"/> associated with the current
+ /// request.
/// </summary>
- public interface IRouteValuesFeature
- {
- /// <summary>
- /// Gets or sets the <see cref="RouteValueDictionary"/> associated with the current
- /// request.
- /// </summary>
- RouteValueDictionary RouteValues { get; set; }
- }
+ RouteValueDictionary RouteValues { get; set; }
}
diff --git a/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs b/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs
index 37b92913e5..9ee9ab5548 100644
--- a/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs
+++ b/src/Http/Http.Abstractions/src/Routing/RouteValueDictionary.cs
@@ -11,775 +11,774 @@ using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http.Abstractions;
using Microsoft.Extensions.Internal;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// An <see cref="IDictionary{String, Object}"/> type for route values.
+/// </summary>
+public class RouteValueDictionary : IDictionary<string, object?>, IReadOnlyDictionary<string, object?>
{
+ // 4 is a good default capacity here because that leaves enough space for area/controller/action/id
+ private readonly int DefaultCapacity = 4;
+
+ internal KeyValuePair<string, object?>[] _arrayStorage;
+ internal PropertyStorage? _propertyStorage;
+ private int _count;
+
/// <summary>
- /// An <see cref="IDictionary{String, Object}"/> type for route values.
+ /// Creates a new <see cref="RouteValueDictionary"/> from the provided array.
+ /// The new instance will take ownership of the array, and may mutate it.
/// </summary>
- public class RouteValueDictionary : IDictionary<string, object?>, IReadOnlyDictionary<string, object?>
+ /// <param name="items">The items array.</param>
+ /// <returns>A new <see cref="RouteValueDictionary"/>.</returns>
+ public static RouteValueDictionary FromArray(KeyValuePair<string, object?>[] items)
{
- // 4 is a good default capacity here because that leaves enough space for area/controller/action/id
- private readonly int DefaultCapacity = 4;
+ if (items == null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
- internal KeyValuePair<string, object?>[] _arrayStorage;
- internal PropertyStorage? _propertyStorage;
- private int _count;
+ // We need to compress the array by removing non-contiguous items. We
+ // typically have a very small number of items to process. We don't need
+ // to preserve order.
+ var start = 0;
+ var end = items.Length - 1;
- /// <summary>
- /// Creates a new <see cref="RouteValueDictionary"/> from the provided array.
- /// The new instance will take ownership of the array, and may mutate it.
- /// </summary>
- /// <param name="items">The items array.</param>
- /// <returns>A new <see cref="RouteValueDictionary"/>.</returns>
- public static RouteValueDictionary FromArray(KeyValuePair<string, object?>[] items)
+ // We walk forwards from the beginning of the array and fill in 'null' slots.
+ // We walk backwards from the end of the array end move items in non-null' slots
+ // into whatever start is pointing to. O(n)
+ while (start <= end)
{
- if (items == null)
+ if (items[start].Key != null)
{
- throw new ArgumentNullException(nameof(items));
+ start++;
}
-
- // We need to compress the array by removing non-contiguous items. We
- // typically have a very small number of items to process. We don't need
- // to preserve order.
- var start = 0;
- var end = items.Length - 1;
-
- // We walk forwards from the beginning of the array and fill in 'null' slots.
- // We walk backwards from the end of the array end move items in non-null' slots
- // into whatever start is pointing to. O(n)
- while (start <= end)
+ else if (items[end].Key != null)
{
- if (items[start].Key != null)
- {
- start++;
- }
- else if (items[end].Key != null)
- {
- // Swap this item into start and advance
- items[start] = items[end];
- items[end] = default;
- start++;
- end--;
- }
- else
- {
- // Both null, we need to hold on 'start' since we
- // still need to fill it with something.
- end--;
- }
+ // Swap this item into start and advance
+ items[start] = items[end];
+ items[end] = default;
+ start++;
+ end--;
}
-
- return new RouteValueDictionary()
+ else
{
- _arrayStorage = items!,
- _count = start,
- };
+ // Both null, we need to hold on 'start' since we
+ // still need to fill it with something.
+ end--;
+ }
}
- /// <summary>
- /// Creates an empty <see cref="RouteValueDictionary"/>.
- /// </summary>
- public RouteValueDictionary()
+ return new RouteValueDictionary()
{
- _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
- }
-
- /// <summary>
- /// Creates a <see cref="RouteValueDictionary"/> initialized with the specified <paramref name="values"/>.
- /// </summary>
- /// <param name="values">An object to initialize the dictionary. The value can be of type
- /// <see cref="IDictionary{TKey, TValue}"/> or <see cref="IReadOnlyDictionary{TKey, TValue}"/>
- /// or an object with public properties as key-value pairs.
- /// </param>
- /// <remarks>
- /// If the value is a dictionary or other <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{String, Object}"/>,
- /// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the
- /// property names are keys, and property values are the values, and copied into the dictionary.
- /// Only public instance non-index properties are considered.
- /// </remarks>
- public RouteValueDictionary(object? values)
- {
- if (values is RouteValueDictionary dictionary)
- {
- if (dictionary._propertyStorage != null)
- {
- // PropertyStorage is immutable so we can just copy it.
- _propertyStorage = dictionary._propertyStorage;
- _count = dictionary._count;
- _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
- return;
- }
-
- var count = dictionary._count;
- if (count > 0)
- {
- var other = dictionary._arrayStorage;
- var storage = new KeyValuePair<string, object?>[count];
- Array.Copy(other, 0, storage, 0, count);
- _arrayStorage = storage;
- _count = count;
- }
- else
- {
- _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
- }
-
- return;
- }
-
- if (values is IEnumerable<KeyValuePair<string, object>> keyValueEnumerable)
- {
- _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
-
- foreach (var kvp in keyValueEnumerable)
- {
- Add(kvp.Key, kvp.Value);
- }
+ _arrayStorage = items!,
+ _count = start,
+ };
+ }
- return;
- }
+ /// <summary>
+ /// Creates an empty <see cref="RouteValueDictionary"/>.
+ /// </summary>
+ public RouteValueDictionary()
+ {
+ _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
+ }
- if (values is IEnumerable<KeyValuePair<string, string>> stringValueEnumerable)
+ /// <summary>
+ /// Creates a <see cref="RouteValueDictionary"/> initialized with the specified <paramref name="values"/>.
+ /// </summary>
+ /// <param name="values">An object to initialize the dictionary. The value can be of type
+ /// <see cref="IDictionary{TKey, TValue}"/> or <see cref="IReadOnlyDictionary{TKey, TValue}"/>
+ /// or an object with public properties as key-value pairs.
+ /// </param>
+ /// <remarks>
+ /// If the value is a dictionary or other <see cref="IEnumerable{T}"/> of <see cref="KeyValuePair{String, Object}"/>,
+ /// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the
+ /// property names are keys, and property values are the values, and copied into the dictionary.
+ /// Only public instance non-index properties are considered.
+ /// </remarks>
+ public RouteValueDictionary(object? values)
+ {
+ if (values is RouteValueDictionary dictionary)
+ {
+ if (dictionary._propertyStorage != null)
{
+ // PropertyStorage is immutable so we can just copy it.
+ _propertyStorage = dictionary._propertyStorage;
+ _count = dictionary._count;
_arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
-
- foreach (var kvp in stringValueEnumerable)
- {
- Add(kvp.Key, kvp.Value);
- }
-
return;
}
- if (values != null)
+ var count = dictionary._count;
+ if (count > 0)
{
- var storage = new PropertyStorage(values);
- _propertyStorage = storage;
- _count = storage.Properties.Length;
- _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
+ var other = dictionary._arrayStorage;
+ var storage = new KeyValuePair<string, object?>[count];
+ Array.Copy(other, 0, storage, 0, count);
+ _arrayStorage = storage;
+ _count = count;
}
else
{
_arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
}
+
+ return;
}
- /// <inheritdoc />
- public object? this[string key]
+ if (values is IEnumerable<KeyValuePair<string, object>> keyValueEnumerable)
{
- get
- {
- if (key == null)
- {
- ThrowArgumentNullExceptionForKey();
- }
-
- TryGetValue(key, out var value);
- return value;
- }
+ _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
- set
+ foreach (var kvp in keyValueEnumerable)
{
- if (key == null)
- {
- ThrowArgumentNullExceptionForKey();
- }
-
- // We're calling this here for the side-effect of converting from properties
- // to array. We need to create the array even if we just set an existing value since
- // property storage is immutable.
- EnsureCapacity(_count);
-
- var index = FindIndex(key);
- if (index < 0)
- {
- EnsureCapacity(_count + 1);
- _arrayStorage[_count++] = new KeyValuePair<string, object?>(key, value);
- }
- else
- {
- _arrayStorage[index] = new KeyValuePair<string, object?>(key, value);
- }
+ Add(kvp.Key, kvp.Value);
}
- }
-
- /// <summary>
- /// Gets the comparer for this dictionary.
- /// </summary>
- /// <remarks>
- /// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
- /// </remarks>
- public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
-
- /// <inheritdoc />
- public int Count => _count;
- /// <inheritdoc />
- bool ICollection<KeyValuePair<string, object?>>.IsReadOnly => false;
+ return;
+ }
- /// <inheritdoc />
- public ICollection<string> Keys
+ if (values is IEnumerable<KeyValuePair<string, string>> stringValueEnumerable)
{
- get
- {
- EnsureCapacity(_count);
-
- var array = _arrayStorage;
- var keys = new string[_count];
- for (var i = 0; i < keys.Length; i++)
- {
- keys[i] = array[i].Key;
- }
+ _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
- return keys;
+ foreach (var kvp in stringValueEnumerable)
+ {
+ Add(kvp.Key, kvp.Value);
}
+
+ return;
}
- IEnumerable<string> IReadOnlyDictionary<string, object?>.Keys => Keys;
+ if (values != null)
+ {
+ var storage = new PropertyStorage(values);
+ _propertyStorage = storage;
+ _count = storage.Properties.Length;
+ _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
+ }
+ else
+ {
+ _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
+ }
+ }
- /// <inheritdoc />
- public ICollection<object?> Values
+ /// <inheritdoc />
+ public object? this[string key]
+ {
+ get
{
- get
+ if (key == null)
{
- EnsureCapacity(_count);
-
- var array = _arrayStorage;
- var values = new object?[_count];
- for (var i = 0; i < values.Length; i++)
- {
- values[i] = array[i].Value;
- }
-
- return values;
+ ThrowArgumentNullExceptionForKey();
}
- }
-
- IEnumerable<object?> IReadOnlyDictionary<string, object?>.Values => Values;
- /// <inheritdoc />
- void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item)
- {
- Add(item.Key, item.Value);
+ TryGetValue(key, out var value);
+ return value;
}
- /// <inheritdoc />
- public void Add(string key, object? value)
+ set
{
if (key == null)
{
ThrowArgumentNullExceptionForKey();
}
- EnsureCapacity(_count + 1);
+ // We're calling this here for the side-effect of converting from properties
+ // to array. We need to create the array even if we just set an existing value since
+ // property storage is immutable.
+ EnsureCapacity(_count);
- if (ContainsKeyArray(key))
+ var index = FindIndex(key);
+ if (index < 0)
{
- var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
- throw new ArgumentException(message, nameof(key));
+ EnsureCapacity(_count + 1);
+ _arrayStorage[_count++] = new KeyValuePair<string, object?>(key, value);
+ }
+ else
+ {
+ _arrayStorage[index] = new KeyValuePair<string, object?>(key, value);
}
-
- _arrayStorage[_count] = new KeyValuePair<string, object?>(key, value);
- _count++;
}
+ }
- /// <inheritdoc />
- public void Clear()
+ /// <summary>
+ /// Gets the comparer for this dictionary.
+ /// </summary>
+ /// <remarks>
+ /// This will always be a reference to <see cref="StringComparer.OrdinalIgnoreCase"/>
+ /// </remarks>
+ public IEqualityComparer<string> Comparer => StringComparer.OrdinalIgnoreCase;
+
+ /// <inheritdoc />
+ public int Count => _count;
+
+ /// <inheritdoc />
+ bool ICollection<KeyValuePair<string, object?>>.IsReadOnly => false;
+
+ /// <inheritdoc />
+ public ICollection<string> Keys
+ {
+ get
{
- if (_count == 0)
- {
- return;
- }
+ EnsureCapacity(_count);
- if (_propertyStorage != null)
+ var array = _arrayStorage;
+ var keys = new string[_count];
+ for (var i = 0; i < keys.Length; i++)
{
- _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
- _propertyStorage = null;
- _count = 0;
- return;
+ keys[i] = array[i].Key;
}
- Array.Clear(_arrayStorage, 0, _count);
- _count = 0;
+ return keys;
}
+ }
- /// <inheritdoc />
- bool ICollection<KeyValuePair<string, object?>>.Contains(KeyValuePair<string, object?> item)
- {
- return TryGetValue(item.Key, out var value) && EqualityComparer<object>.Default.Equals(value, item.Value);
- }
+ IEnumerable<string> IReadOnlyDictionary<string, object?>.Keys => Keys;
- /// <inheritdoc />
- public bool ContainsKey(string key)
+ /// <inheritdoc />
+ public ICollection<object?> Values
+ {
+ get
{
- if (key == null)
+ EnsureCapacity(_count);
+
+ var array = _arrayStorage;
+ var values = new object?[_count];
+ for (var i = 0; i < values.Length; i++)
{
- ThrowArgumentNullExceptionForKey();
+ values[i] = array[i].Value;
}
- return ContainsKeyCore(key);
+ return values;
}
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool ContainsKeyCore(string key)
+ IEnumerable<object?> IReadOnlyDictionary<string, object?>.Values => Values;
+
+ /// <inheritdoc />
+ void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ /// <inheritdoc />
+ public void Add(string key, object? value)
+ {
+ if (key == null)
{
- if (_propertyStorage == null)
- {
- return ContainsKeyArray(key);
- }
+ ThrowArgumentNullExceptionForKey();
+ }
+
+ EnsureCapacity(_count + 1);
- return ContainsKeyProperties(key);
+ if (ContainsKeyArray(key))
+ {
+ var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary));
+ throw new ArgumentException(message, nameof(key));
}
- /// <inheritdoc />
- void ICollection<KeyValuePair<string, object?>>.CopyTo(
- KeyValuePair<string, object?>[] array,
- int arrayIndex)
+ _arrayStorage[_count] = new KeyValuePair<string, object?>(key, value);
+ _count++;
+ }
+
+ /// <inheritdoc />
+ public void Clear()
+ {
+ if (_count == 0)
{
- if (array == null)
- {
- throw new ArgumentNullException(nameof(array));
- }
+ return;
+ }
- if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
- {
- throw new ArgumentOutOfRangeException(nameof(arrayIndex));
- }
+ if (_propertyStorage != null)
+ {
+ _arrayStorage = Array.Empty<KeyValuePair<string, object?>>();
+ _propertyStorage = null;
+ _count = 0;
+ return;
+ }
- if (Count == 0)
- {
- return;
- }
+ Array.Clear(_arrayStorage, 0, _count);
+ _count = 0;
+ }
- EnsureCapacity(Count);
+ /// <inheritdoc />
+ bool ICollection<KeyValuePair<string, object?>>.Contains(KeyValuePair<string, object?> item)
+ {
+ return TryGetValue(item.Key, out var value) && EqualityComparer<object>.Default.Equals(value, item.Value);
+ }
- var storage = _arrayStorage;
- Array.Copy(storage, 0, array, arrayIndex, _count);
+ /// <inheritdoc />
+ public bool ContainsKey(string key)
+ {
+ if (key == null)
+ {
+ ThrowArgumentNullExceptionForKey();
}
- /// <inheritdoc />
- public Enumerator GetEnumerator()
+ return ContainsKeyCore(key);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool ContainsKeyCore(string key)
+ {
+ if (_propertyStorage == null)
{
- return new Enumerator(this);
+ return ContainsKeyArray(key);
}
- /// <inheritdoc />
- IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator()
+ return ContainsKeyProperties(key);
+ }
+
+ /// <inheritdoc />
+ void ICollection<KeyValuePair<string, object?>>.CopyTo(
+ KeyValuePair<string, object?>[] array,
+ int arrayIndex)
+ {
+ if (array == null)
{
- return GetEnumerator();
+ throw new ArgumentNullException(nameof(array));
}
- /// <inheritdoc />
- IEnumerator IEnumerable.GetEnumerator()
+ if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count)
{
- return GetEnumerator();
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
- /// <inheritdoc />
- bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item)
+ if (Count == 0)
{
- if (Count == 0)
- {
- return false;
- }
+ return;
+ }
- Debug.Assert(_arrayStorage != null);
+ EnsureCapacity(Count);
- EnsureCapacity(Count);
+ var storage = _arrayStorage;
+ Array.Copy(storage, 0, array, arrayIndex, _count);
+ }
- var index = FindIndex(item.Key);
- var array = _arrayStorage;
- if (index >= 0 && EqualityComparer<object>.Default.Equals(array[index].Value, item.Value))
- {
- Array.Copy(array, index + 1, array, index, _count - index);
- _count--;
- array[_count] = default;
- return true;
- }
+ /// <inheritdoc />
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ /// <inheritdoc />
+ IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ /// <inheritdoc />
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ /// <inheritdoc />
+ bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item)
+ {
+ if (Count == 0)
+ {
return false;
}
- /// <inheritdoc />
- public bool Remove(string key)
- {
- if (key == null)
- {
- ThrowArgumentNullExceptionForKey();
- }
+ Debug.Assert(_arrayStorage != null);
- if (Count == 0)
- {
- return false;
- }
+ EnsureCapacity(Count);
- // Ensure property storage is converted to array storage as we'll be
- // applying the lookup and removal on the array
- EnsureCapacity(_count);
+ var index = FindIndex(item.Key);
+ var array = _arrayStorage;
+ if (index >= 0 && EqualityComparer<object>.Default.Equals(array[index].Value, item.Value))
+ {
+ Array.Copy(array, index + 1, array, index, _count - index);
+ _count--;
+ array[_count] = default;
+ return true;
+ }
- var index = FindIndex(key);
- if (index >= 0)
- {
- _count--;
- var array = _arrayStorage;
- Array.Copy(array, index + 1, array, index, _count - index);
- array[_count] = default;
+ return false;
+ }
- return true;
- }
+ /// <inheritdoc />
+ public bool Remove(string key)
+ {
+ if (key == null)
+ {
+ ThrowArgumentNullExceptionForKey();
+ }
+ if (Count == 0)
+ {
return false;
}
- /// <summary>
- /// Attempts to remove and return the value that has the specified key from the <see cref="RouteValueDictionary"/>.
- /// </summary>
- /// <param name="key">The key of the element to remove and return.</param>
- /// <param name="value">When this method returns, contains the object removed from the <see cref="RouteValueDictionary"/>, or <c>null</c> if key does not exist.</param>
- /// <returns>
- /// <c>true</c> if the object was removed successfully; otherwise, <c>false</c>.
- /// </returns>
- public bool Remove(string key, out object? value)
- {
- if (key == null)
- {
- ThrowArgumentNullExceptionForKey();
- }
+ // Ensure property storage is converted to array storage as we'll be
+ // applying the lookup and removal on the array
+ EnsureCapacity(_count);
- if (_count == 0)
- {
- value = default;
- return false;
- }
+ var index = FindIndex(key);
+ if (index >= 0)
+ {
+ _count--;
+ var array = _arrayStorage;
+ Array.Copy(array, index + 1, array, index, _count - index);
+ array[_count] = default;
- // Ensure property storage is converted to array storage as we'll be
- // applying the lookup and removal on the array
- EnsureCapacity(_count);
+ return true;
+ }
- var index = FindIndex(key);
- if (index >= 0)
- {
- _count--;
- var array = _arrayStorage;
- value = array[index].Value;
- Array.Copy(array, index + 1, array, index, _count - index);
- array[_count] = default;
+ return false;
+ }
- return true;
- }
+ /// <summary>
+ /// Attempts to remove and return the value that has the specified key from the <see cref="RouteValueDictionary"/>.
+ /// </summary>
+ /// <param name="key">The key of the element to remove and return.</param>
+ /// <param name="value">When this method returns, contains the object removed from the <see cref="RouteValueDictionary"/>, or <c>null</c> if key does not exist.</param>
+ /// <returns>
+ /// <c>true</c> if the object was removed successfully; otherwise, <c>false</c>.
+ /// </returns>
+ public bool Remove(string key, out object? value)
+ {
+ if (key == null)
+ {
+ ThrowArgumentNullExceptionForKey();
+ }
+ if (_count == 0)
+ {
value = default;
return false;
}
- /// <summary>
- /// Attempts to the add the provided <paramref name="key"/> and <paramref name="value"/> to the dictionary.
- /// </summary>
- /// <param name="key">The key.</param>
- /// <param name="value">The value.</param>
- /// <returns>Returns <c>true</c> if the value was added. Returns <c>false</c> if the key was already present.</returns>
- public bool TryAdd(string key, object? value)
- {
- if (key == null)
- {
- ThrowArgumentNullExceptionForKey();
- }
+ // Ensure property storage is converted to array storage as we'll be
+ // applying the lookup and removal on the array
+ EnsureCapacity(_count);
- if (ContainsKeyCore(key))
- {
- return false;
- }
+ var index = FindIndex(key);
+ if (index >= 0)
+ {
+ _count--;
+ var array = _arrayStorage;
+ value = array[index].Value;
+ Array.Copy(array, index + 1, array, index, _count - index);
+ array[_count] = default;
- EnsureCapacity(Count + 1);
- _arrayStorage[Count] = new KeyValuePair<string, object?>(key, value);
- _count++;
return true;
}
- /// <inheritdoc />
- public bool TryGetValue(string key, out object? value)
+ value = default;
+ return false;
+ }
+
+ /// <summary>
+ /// Attempts to the add the provided <paramref name="key"/> and <paramref name="value"/> to the dictionary.
+ /// </summary>
+ /// <param name="key">The key.</param>
+ /// <param name="value">The value.</param>
+ /// <returns>Returns <c>true</c> if the value was added. Returns <c>false</c> if the key was already present.</returns>
+ public bool TryAdd(string key, object? value)
+ {
+ if (key == null)
{
- if (key == null)
- {
- ThrowArgumentNullExceptionForKey();
- }
+ ThrowArgumentNullExceptionForKey();
+ }
- if (_propertyStorage == null)
- {
- return TryFindItem(key, out value);
- }
+ if (ContainsKeyCore(key))
+ {
+ return false;
+ }
+
+ EnsureCapacity(Count + 1);
+ _arrayStorage[Count] = new KeyValuePair<string, object?>(key, value);
+ _count++;
+ return true;
+ }
- return TryGetValueSlow(key, out value);
+ /// <inheritdoc />
+ public bool TryGetValue(string key, out object? value)
+ {
+ if (key == null)
+ {
+ ThrowArgumentNullExceptionForKey();
}
- private bool TryGetValueSlow(string key, out object? value)
+ if (_propertyStorage == null)
{
- if (_propertyStorage != null)
+ return TryFindItem(key, out value);
+ }
+
+ return TryGetValueSlow(key, out value);
+ }
+
+ private bool TryGetValueSlow(string key, out object? value)
+ {
+ if (_propertyStorage != null)
+ {
+ var storage = _propertyStorage;
+ for (var i = 0; i < storage.Properties.Length; i++)
{
- var storage = _propertyStorage;
- for (var i = 0; i < storage.Properties.Length; i++)
+ if (string.Equals(storage.Properties[i].Name, key, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(storage.Properties[i].Name, key, StringComparison.OrdinalIgnoreCase))
- {
- value = storage.Properties[i].GetValue(storage.Value);
- return true;
- }
+ value = storage.Properties[i].GetValue(storage.Value);
+ return true;
}
}
-
- value = default;
- return false;
}
- [DoesNotReturn]
- private static void ThrowArgumentNullExceptionForKey()
+ value = default;
+ return false;
+ }
+
+ [DoesNotReturn]
+ private static void ThrowArgumentNullExceptionForKey()
+ {
+ throw new ArgumentNullException("key");
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void EnsureCapacity(int capacity)
+ {
+ if (_propertyStorage != null || _arrayStorage.Length < capacity)
{
- throw new ArgumentNullException("key");
+ EnsureCapacitySlow(capacity);
}
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void EnsureCapacity(int capacity)
+ private void EnsureCapacitySlow(int capacity)
+ {
+ if (_propertyStorage != null)
{
- if (_propertyStorage != null || _arrayStorage.Length < capacity)
+ var storage = _propertyStorage;
+
+ // If we're converting from properties, it's likely due to an 'add' to make sure we have at least
+ // the default amount of space.
+ capacity = Math.Max(DefaultCapacity, Math.Max(storage.Properties.Length, capacity));
+ var array = new KeyValuePair<string, object?>[capacity];
+
+ for (var i = 0; i < storage.Properties.Length; i++)
{
- EnsureCapacitySlow(capacity);
+ var property = storage.Properties[i];
+ array[i] = new KeyValuePair<string, object?>(property.Name, property.GetValue(storage.Value));
}
+
+ _arrayStorage = array;
+ _propertyStorage = null;
+ return;
}
- private void EnsureCapacitySlow(int capacity)
+ if (_arrayStorage.Length < capacity)
{
- if (_propertyStorage != null)
+ capacity = _arrayStorage.Length == 0 ? DefaultCapacity : _arrayStorage.Length * 2;
+ var array = new KeyValuePair<string, object?>[capacity];
+ if (_count > 0)
{
- var storage = _propertyStorage;
+ Array.Copy(_arrayStorage, 0, array, 0, _count);
+ }
- // If we're converting from properties, it's likely due to an 'add' to make sure we have at least
- // the default amount of space.
- capacity = Math.Max(DefaultCapacity, Math.Max(storage.Properties.Length, capacity));
- var array = new KeyValuePair<string, object?>[capacity];
+ _arrayStorage = array;
+ }
+ }
- for (var i = 0; i < storage.Properties.Length; i++)
- {
- var property = storage.Properties[i];
- array[i] = new KeyValuePair<string, object?>(property.Name, property.GetValue(storage.Value));
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int FindIndex(string key)
+ {
+ // Generally the bounds checking here will be elided by the JIT because this will be called
+ // on the same code path as EnsureCapacity.
+ var array = _arrayStorage;
+ var count = _count;
- _arrayStorage = array;
- _propertyStorage = null;
- return;
+ for (var i = 0; i < count; i++)
+ {
+ if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
+ {
+ return i;
}
+ }
+
+ return -1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool TryFindItem(string key, out object? value)
+ {
+ var array = _arrayStorage;
+ var count = _count;
- if (_arrayStorage.Length < capacity)
+ // Elide bounds check for indexing.
+ if ((uint)count <= (uint)array.Length)
+ {
+ for (var i = 0; i < count; i++)
{
- capacity = _arrayStorage.Length == 0 ? DefaultCapacity : _arrayStorage.Length * 2;
- var array = new KeyValuePair<string, object?>[capacity];
- if (_count > 0)
+ if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
- Array.Copy(_arrayStorage, 0, array, 0, _count);
+ value = array[i].Value;
+ return true;
}
-
- _arrayStorage = array;
}
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int FindIndex(string key)
- {
- // Generally the bounds checking here will be elided by the JIT because this will be called
- // on the same code path as EnsureCapacity.
- var array = _arrayStorage;
- var count = _count;
+ value = null;
+ return false;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool ContainsKeyArray(string key)
+ {
+ var array = _arrayStorage;
+ var count = _count;
+
+ // Elide bounds check for indexing.
+ if ((uint)count <= (uint)array.Length)
+ {
for (var i = 0; i < count; i++)
{
if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
{
- return i;
+ return true;
}
}
-
- return -1;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryFindItem(string key, out object? value)
- {
- var array = _arrayStorage;
- var count = _count;
+ return false;
+ }
- // Elide bounds check for indexing.
- if ((uint)count <= (uint)array.Length)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool ContainsKeyProperties(string key)
+ {
+ Debug.Assert(_propertyStorage != null);
+
+ var properties = _propertyStorage.Properties;
+ for (var i = 0; i < properties.Length; i++)
+ {
+ if (string.Equals(properties[i].Name, key, StringComparison.OrdinalIgnoreCase))
{
- for (var i = 0; i < count; i++)
- {
- if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
- {
- value = array[i].Value;
- return true;
- }
- }
+ return true;
}
-
- value = null;
- return false;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool ContainsKeyArray(string key)
- {
- var array = _arrayStorage;
- var count = _count;
+ return false;
+ }
+
+ /// <inheritdoc />
+ public struct Enumerator : IEnumerator<KeyValuePair<string, object?>>
+ {
+ private readonly RouteValueDictionary _dictionary;
+ private int _index;
- // Elide bounds check for indexing.
- if ((uint)count <= (uint)array.Length)
+ /// <summary>
+ /// Instantiates a new enumerator with the values provided in <paramref name="dictionary"/>.
+ /// </summary>
+ /// <param name="dictionary">A <see cref="RouteValueDictionary"/>.</param>
+ public Enumerator(RouteValueDictionary dictionary)
+ {
+ if (dictionary == null)
{
- for (var i = 0; i < count; i++)
- {
- if (string.Equals(array[i].Key, key, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
+ throw new ArgumentNullException(nameof(dictionary));
}
- return false;
+ _dictionary = dictionary;
+
+ Current = default;
+ _index = 0;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool ContainsKeyProperties(string key)
- {
- Debug.Assert(_propertyStorage != null);
+ /// <inheritdoc />
+ public KeyValuePair<string, object?> Current { get; private set; }
- var properties = _propertyStorage.Properties;
- for (var i = 0; i < properties.Length; i++)
- {
- if (string.Equals(properties[i].Name, key, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
+ object IEnumerator.Current => Current;
- return false;
+ /// <summary>
+ /// Releases resources used by the <see cref="Enumerator"/>.
+ /// </summary>
+ public void Dispose()
+ {
}
+ // Similar to the design of List<T>.Enumerator - Split into fast path and slow path for inlining friendliness
/// <inheritdoc />
- public struct Enumerator : IEnumerator<KeyValuePair<string, object?>>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext()
{
- private readonly RouteValueDictionary _dictionary;
- private int _index;
+ var dictionary = _dictionary;
- /// <summary>
- /// Instantiates a new enumerator with the values provided in <paramref name="dictionary"/>.
- /// </summary>
- /// <param name="dictionary">A <see cref="RouteValueDictionary"/>.</param>
- public Enumerator(RouteValueDictionary dictionary)
+ // The uncommon case is that the propertyStorage is in use
+ if (dictionary._propertyStorage == null && ((uint)_index < (uint)dictionary._count))
{
- if (dictionary == null)
- {
- throw new ArgumentNullException(nameof(dictionary));
- }
-
- _dictionary = dictionary;
-
- Current = default;
- _index = 0;
+ Current = dictionary._arrayStorage[_index];
+ _index++;
+ return true;
}
- /// <inheritdoc />
- public KeyValuePair<string, object?> Current { get; private set; }
-
- object IEnumerator.Current => Current;
+ return MoveNextRare();
+ }
- /// <summary>
- /// Releases resources used by the <see cref="Enumerator"/>.
- /// </summary>
- public void Dispose()
+ private bool MoveNextRare()
+ {
+ var dictionary = _dictionary;
+ if (dictionary._propertyStorage != null && ((uint)_index < (uint)dictionary._count))
{
+ var storage = dictionary._propertyStorage;
+ var property = storage.Properties[_index];
+ Current = new KeyValuePair<string, object?>(property.Name, property.GetValue(storage.Value));
+ _index++;
+ return true;
}
- // Similar to the design of List<T>.Enumerator - Split into fast path and slow path for inlining friendliness
- /// <inheritdoc />
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool MoveNext()
- {
- var dictionary = _dictionary;
+ _index = dictionary._count;
+ Current = default;
+ return false;
+ }
- // The uncommon case is that the propertyStorage is in use
- if (dictionary._propertyStorage == null && ((uint)_index < (uint)dictionary._count))
- {
- Current = dictionary._arrayStorage[_index];
- _index++;
- return true;
- }
+ /// <inheritdoc />
+ public void Reset()
+ {
+ Current = default;
+ _index = 0;
+ }
+ }
- return MoveNextRare();
- }
+ internal class PropertyStorage
+ {
+ private static readonly ConcurrentDictionary<Type, PropertyHelper[]> _propertyCache = new ConcurrentDictionary<Type, PropertyHelper[]>();
- private bool MoveNextRare()
- {
- var dictionary = _dictionary;
- if (dictionary._propertyStorage != null && ((uint)_index < (uint)dictionary._count))
- {
- var storage = dictionary._propertyStorage;
- var property = storage.Properties[_index];
- Current = new KeyValuePair<string, object?>(property.Name, property.GetValue(storage.Value));
- _index++;
- return true;
- }
+ public readonly object Value;
+ public readonly PropertyHelper[] Properties;
- _index = dictionary._count;
- Current = default;
- return false;
- }
+ public PropertyStorage(object value)
+ {
+ Debug.Assert(value != null);
+ Value = value;
- /// <inheritdoc />
- public void Reset()
+ // Cache the properties so we can know if we've already validated them for duplicates.
+ var type = Value.GetType();
+ if (!_propertyCache.TryGetValue(type, out Properties!))
{
- Current = default;
- _index = 0;
+ Properties = PropertyHelper.GetVisibleProperties(type);
+ ValidatePropertyNames(type, Properties);
+ _propertyCache.TryAdd(type, Properties);
}
}
- internal class PropertyStorage
+ private static void ValidatePropertyNames(Type type, PropertyHelper[] properties)
{
- private static readonly ConcurrentDictionary<Type, PropertyHelper[]> _propertyCache = new ConcurrentDictionary<Type, PropertyHelper[]>();
-
- public readonly object Value;
- public readonly PropertyHelper[] Properties;
-
- public PropertyStorage(object value)
+ var names = new Dictionary<string, PropertyHelper>(StringComparer.OrdinalIgnoreCase);
+ for (var i = 0; i < properties.Length; i++)
{
- Debug.Assert(value != null);
- Value = value;
+ var property = properties[i];
- // Cache the properties so we can know if we've already validated them for duplicates.
- var type = Value.GetType();
- if (!_propertyCache.TryGetValue(type, out Properties!))
+ if (names.TryGetValue(property.Name, out var duplicate))
{
- Properties = PropertyHelper.GetVisibleProperties(type);
- ValidatePropertyNames(type, Properties);
- _propertyCache.TryAdd(type, Properties);
+ var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName(
+ type.FullName,
+ property.Name,
+ duplicate.Name,
+ nameof(RouteValueDictionary));
+ throw new InvalidOperationException(message);
}
- }
- private static void ValidatePropertyNames(Type type, PropertyHelper[] properties)
- {
- var names = new Dictionary<string, PropertyHelper>(StringComparer.OrdinalIgnoreCase);
- for (var i = 0; i < properties.Length; i++)
- {
- var property = properties[i];
-
- if (names.TryGetValue(property.Name, out var duplicate))
- {
- var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName(
- type.FullName,
- property.Name,
- duplicate.Name,
- nameof(RouteValueDictionary));
- throw new InvalidOperationException(message);
- }
-
- names.Add(property.Name, property);
- }
+ names.Add(property.Name, property);
}
}
}
diff --git a/src/Http/Http.Abstractions/src/StatusCodes.cs b/src/Http/Http.Abstractions/src/StatusCodes.cs
index 99a47f9f22..d24da21a4b 100644
--- a/src/Http/Http.Abstractions/src/StatusCodes.cs
+++ b/src/Http/Http.Abstractions/src/StatusCodes.cs
@@ -1,340 +1,339 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A collection of constants for HTTP status codes.
+///
+/// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+/// </summary>
+public static class StatusCodes
{
/// <summary>
- /// A collection of constants for HTTP status codes.
- ///
- /// Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
- /// </summary>
- public static class StatusCodes
- {
- /// <summary>
- /// HTTP status code 100.
- /// </summary>
- public const int Status100Continue = 100;
-
- /// <summary>
- /// HTTP status code 101.
- /// </summary>
- public const int Status101SwitchingProtocols = 101;
-
- /// <summary>
- /// HTTP status code 102.
- /// </summary>
- public const int Status102Processing = 102;
-
- /// <summary>
- /// HTTP status code 200.
- /// </summary>
- public const int Status200OK = 200;
-
- /// <summary>
- /// HTTP status code 201.
- /// </summary>
- public const int Status201Created = 201;
-
- /// <summary>
- /// HTTP status code 202.
- /// </summary>
- public const int Status202Accepted = 202;
-
- /// <summary>
- /// HTTP status code 203.
- /// </summary>
- public const int Status203NonAuthoritative = 203;
-
- /// <summary>
- /// HTTP status code 204.
- /// </summary>
- public const int Status204NoContent = 204;
-
- /// <summary>
- /// HTTP status code 205.
- /// </summary>
- public const int Status205ResetContent = 205;
-
- /// <summary>
- /// HTTP status code 206.
- /// </summary>
- public const int Status206PartialContent = 206;
-
- /// <summary>
- /// HTTP status code 207.
- /// </summary>
- public const int Status207MultiStatus = 207;
-
- /// <summary>
- /// HTTP status code 208.
- /// </summary>
- public const int Status208AlreadyReported = 208;
-
- /// <summary>
- /// HTTP status code 226.
- /// </summary>
- public const int Status226IMUsed = 226;
-
- /// <summary>
- /// HTTP status code 300.
- /// </summary>
- public const int Status300MultipleChoices = 300;
-
- /// <summary>
- /// HTTP status code 301.
- /// </summary>
- public const int Status301MovedPermanently = 301;
-
- /// <summary>
- /// HTTP status code 302.
- /// </summary>
- public const int Status302Found = 302;
-
- /// <summary>
- /// HTTP status code 303.
- /// </summary>
- public const int Status303SeeOther = 303;
-
- /// <summary>
- /// HTTP status code 304.
- /// </summary>
- public const int Status304NotModified = 304;
-
- /// <summary>
- /// HTTP status code 305.
- /// </summary>
- public const int Status305UseProxy = 305;
-
- /// <summary>
- /// HTTP status code 306.
- /// </summary>
- public const int Status306SwitchProxy = 306; // RFC 2616, removed
-
- /// <summary>
- /// HTTP status code 307.
- /// </summary>
- public const int Status307TemporaryRedirect = 307;
-
- /// <summary>
- /// HTTP status code 308.
- /// </summary>
- public const int Status308PermanentRedirect = 308;
-
- /// <summary>
- /// HTTP status code 400.
- /// </summary>
-
- public const int Status400BadRequest = 400;
-
- /// <summary>
- /// HTTP status code 401.
- /// </summary>
- public const int Status401Unauthorized = 401;
-
- /// <summary>
- /// HTTP status code 402.
- /// </summary>
- public const int Status402PaymentRequired = 402;
-
- /// <summary>
- /// HTTP status code 403.
- /// </summary>
- public const int Status403Forbidden = 403;
-
- /// <summary>
- /// HTTP status code 404.
- /// </summary>
- public const int Status404NotFound = 404;
-
- /// <summary>
- /// HTTP status code 405.
- /// </summary>
- public const int Status405MethodNotAllowed = 405;
-
- /// <summary>
- /// HTTP status code 406.
- /// </summary>
- public const int Status406NotAcceptable = 406;
-
- /// <summary>
- /// HTTP status code 407.
- /// </summary>
- public const int Status407ProxyAuthenticationRequired = 407;
-
- /// <summary>
- /// HTTP status code 408.
- /// </summary>
- public const int Status408RequestTimeout = 408;
-
- /// <summary>
- /// HTTP status code 409.
- /// </summary>
- public const int Status409Conflict = 409;
-
- /// <summary>
- /// HTTP status code 410.
- /// </summary>
- public const int Status410Gone = 410;
-
- /// <summary>
- /// HTTP status code 411.
- /// </summary>
- public const int Status411LengthRequired = 411;
-
- /// <summary>
- /// HTTP status code 412.
- /// </summary>
- public const int Status412PreconditionFailed = 412;
-
- /// <summary>
- /// HTTP status code 413.
- /// </summary>
- public const int Status413RequestEntityTooLarge = 413; // RFC 2616, renamed
-
- /// <summary>
- /// HTTP status code 413.
- /// </summary>
- public const int Status413PayloadTooLarge = 413; // RFC 7231
-
- /// <summary>
- /// HTTP status code 414.
- /// </summary>
- public const int Status414RequestUriTooLong = 414; // RFC 2616, renamed
-
- /// <summary>
- /// HTTP status code 414.
- /// </summary>
- public const int Status414UriTooLong = 414; // RFC 7231
-
- /// <summary>
- /// HTTP status code 415.
- /// </summary>
- public const int Status415UnsupportedMediaType = 415;
-
- /// <summary>
- /// HTTP status code 416.
- /// </summary>
- public const int Status416RequestedRangeNotSatisfiable = 416; // RFC 2616, renamed
-
- /// <summary>
- /// HTTP status code 416.
- /// </summary>
- public const int Status416RangeNotSatisfiable = 416; // RFC 7233
-
- /// <summary>
- /// HTTP status code 417.
- /// </summary>
- public const int Status417ExpectationFailed = 417;
-
- /// <summary>
- /// HTTP status code 418.
- /// </summary>
- public const int Status418ImATeapot = 418;
-
- /// <summary>
- /// HTTP status code 419.
- /// </summary>
- public const int Status419AuthenticationTimeout = 419; // Not defined in any RFC
-
- /// <summary>
- /// HTTP status code 422.
- /// </summary>
- public const int Status421MisdirectedRequest = 421;
-
- /// <summary>
- /// HTTP status code 422.
- /// </summary>
- public const int Status422UnprocessableEntity = 422;
-
- /// <summary>
- /// HTTP status code 423.
- /// </summary>
- public const int Status423Locked = 423;
-
- /// <summary>
- /// HTTP status code 424.
- /// </summary>
- public const int Status424FailedDependency = 424;
-
- /// <summary>
- /// HTTP status code 426.
- /// </summary>
- public const int Status426UpgradeRequired = 426;
-
- /// <summary>
- /// HTTP status code 428.
- /// </summary>
- public const int Status428PreconditionRequired = 428;
-
- /// <summary>
- /// HTTP status code 429.
- /// </summary>
- public const int Status429TooManyRequests = 429;
-
- /// <summary>
- /// HTTP status code 431.
- /// </summary>
- public const int Status431RequestHeaderFieldsTooLarge = 431;
-
- /// <summary>
- /// HTTP status code 451.
- /// </summary>
- public const int Status451UnavailableForLegalReasons = 451;
-
- /// <summary>
- /// HTTP status code 500.
- /// </summary>
-
- public const int Status500InternalServerError = 500;
-
- /// <summary>
- /// HTTP status code 501.
- /// </summary>
- public const int Status501NotImplemented = 501;
-
- /// <summary>
- /// HTTP status code 502.
- /// </summary>
- public const int Status502BadGateway = 502;
-
- /// <summary>
- /// HTTP status code 503.
- /// </summary>
- public const int Status503ServiceUnavailable = 503;
-
- /// <summary>
- /// HTTP status code 504.
- /// </summary>
- public const int Status504GatewayTimeout = 504;
-
- /// <summary>
- /// HTTP status code 505.
- /// </summary>
- public const int Status505HttpVersionNotsupported = 505;
-
- /// <summary>
- /// HTTP status code 506.
- /// </summary>
- public const int Status506VariantAlsoNegotiates = 506;
-
- /// <summary>
- /// HTTP status code 507.
- /// </summary>
- public const int Status507InsufficientStorage = 507;
-
- /// <summary>
- /// HTTP status code 508.
- /// </summary>
- public const int Status508LoopDetected = 508;
-
- /// <summary>
- /// HTTP status code 510.
- /// </summary>
- public const int Status510NotExtended = 510;
-
- /// <summary>
- /// HTTP status code 511.
- /// </summary>
- public const int Status511NetworkAuthenticationRequired = 511;
- }
+ /// HTTP status code 100.
+ /// </summary>
+ public const int Status100Continue = 100;
+
+ /// <summary>
+ /// HTTP status code 101.
+ /// </summary>
+ public const int Status101SwitchingProtocols = 101;
+
+ /// <summary>
+ /// HTTP status code 102.
+ /// </summary>
+ public const int Status102Processing = 102;
+
+ /// <summary>
+ /// HTTP status code 200.
+ /// </summary>
+ public const int Status200OK = 200;
+
+ /// <summary>
+ /// HTTP status code 201.
+ /// </summary>
+ public const int Status201Created = 201;
+
+ /// <summary>
+ /// HTTP status code 202.
+ /// </summary>
+ public const int Status202Accepted = 202;
+
+ /// <summary>
+ /// HTTP status code 203.
+ /// </summary>
+ public const int Status203NonAuthoritative = 203;
+
+ /// <summary>
+ /// HTTP status code 204.
+ /// </summary>
+ public const int Status204NoContent = 204;
+
+ /// <summary>
+ /// HTTP status code 205.
+ /// </summary>
+ public const int Status205ResetContent = 205;
+
+ /// <summary>
+ /// HTTP status code 206.
+ /// </summary>
+ public const int Status206PartialContent = 206;
+
+ /// <summary>
+ /// HTTP status code 207.
+ /// </summary>
+ public const int Status207MultiStatus = 207;
+
+ /// <summary>
+ /// HTTP status code 208.
+ /// </summary>
+ public const int Status208AlreadyReported = 208;
+
+ /// <summary>
+ /// HTTP status code 226.
+ /// </summary>
+ public const int Status226IMUsed = 226;
+
+ /// <summary>
+ /// HTTP status code 300.
+ /// </summary>
+ public const int Status300MultipleChoices = 300;
+
+ /// <summary>
+ /// HTTP status code 301.
+ /// </summary>
+ public const int Status301MovedPermanently = 301;
+
+ /// <summary>
+ /// HTTP status code 302.
+ /// </summary>
+ public const int Status302Found = 302;
+
+ /// <summary>
+ /// HTTP status code 303.
+ /// </summary>
+ public const int Status303SeeOther = 303;
+
+ /// <summary>
+ /// HTTP status code 304.
+ /// </summary>
+ public const int Status304NotModified = 304;
+
+ /// <summary>
+ /// HTTP status code 305.
+ /// </summary>
+ public const int Status305UseProxy = 305;
+
+ /// <summary>
+ /// HTTP status code 306.
+ /// </summary>
+ public const int Status306SwitchProxy = 306; // RFC 2616, removed
+
+ /// <summary>
+ /// HTTP status code 307.
+ /// </summary>
+ public const int Status307TemporaryRedirect = 307;
+
+ /// <summary>
+ /// HTTP status code 308.
+ /// </summary>
+ public const int Status308PermanentRedirect = 308;
+
+ /// <summary>
+ /// HTTP status code 400.
+ /// </summary>
+
+ public const int Status400BadRequest = 400;
+
+ /// <summary>
+ /// HTTP status code 401.
+ /// </summary>
+ public const int Status401Unauthorized = 401;
+
+ /// <summary>
+ /// HTTP status code 402.
+ /// </summary>
+ public const int Status402PaymentRequired = 402;
+
+ /// <summary>
+ /// HTTP status code 403.
+ /// </summary>
+ public const int Status403Forbidden = 403;
+
+ /// <summary>
+ /// HTTP status code 404.
+ /// </summary>
+ public const int Status404NotFound = 404;
+
+ /// <summary>
+ /// HTTP status code 405.
+ /// </summary>
+ public const int Status405MethodNotAllowed = 405;
+
+ /// <summary>
+ /// HTTP status code 406.
+ /// </summary>
+ public const int Status406NotAcceptable = 406;
+
+ /// <summary>
+ /// HTTP status code 407.
+ /// </summary>
+ public const int Status407ProxyAuthenticationRequired = 407;
+
+ /// <summary>
+ /// HTTP status code 408.
+ /// </summary>
+ public const int Status408RequestTimeout = 408;
+
+ /// <summary>
+ /// HTTP status code 409.
+ /// </summary>
+ public const int Status409Conflict = 409;
+
+ /// <summary>
+ /// HTTP status code 410.
+ /// </summary>
+ public const int Status410Gone = 410;
+
+ /// <summary>
+ /// HTTP status code 411.
+ /// </summary>
+ public const int Status411LengthRequired = 411;
+
+ /// <summary>
+ /// HTTP status code 412.
+ /// </summary>
+ public const int Status412PreconditionFailed = 412;
+
+ /// <summary>
+ /// HTTP status code 413.
+ /// </summary>
+ public const int Status413RequestEntityTooLarge = 413; // RFC 2616, renamed
+
+ /// <summary>
+ /// HTTP status code 413.
+ /// </summary>
+ public const int Status413PayloadTooLarge = 413; // RFC 7231
+
+ /// <summary>
+ /// HTTP status code 414.
+ /// </summary>
+ public const int Status414RequestUriTooLong = 414; // RFC 2616, renamed
+
+ /// <summary>
+ /// HTTP status code 414.
+ /// </summary>
+ public const int Status414UriTooLong = 414; // RFC 7231
+
+ /// <summary>
+ /// HTTP status code 415.
+ /// </summary>
+ public const int Status415UnsupportedMediaType = 415;
+
+ /// <summary>
+ /// HTTP status code 416.
+ /// </summary>
+ public const int Status416RequestedRangeNotSatisfiable = 416; // RFC 2616, renamed
+
+ /// <summary>
+ /// HTTP status code 416.
+ /// </summary>
+ public const int Status416RangeNotSatisfiable = 416; // RFC 7233
+
+ /// <summary>
+ /// HTTP status code 417.
+ /// </summary>
+ public const int Status417ExpectationFailed = 417;
+
+ /// <summary>
+ /// HTTP status code 418.
+ /// </summary>
+ public const int Status418ImATeapot = 418;
+
+ /// <summary>
+ /// HTTP status code 419.
+ /// </summary>
+ public const int Status419AuthenticationTimeout = 419; // Not defined in any RFC
+
+ /// <summary>
+ /// HTTP status code 422.
+ /// </summary>
+ public const int Status421MisdirectedRequest = 421;
+
+ /// <summary>
+ /// HTTP status code 422.
+ /// </summary>
+ public const int Status422UnprocessableEntity = 422;
+
+ /// <summary>
+ /// HTTP status code 423.
+ /// </summary>
+ public const int Status423Locked = 423;
+
+ /// <summary>
+ /// HTTP status code 424.
+ /// </summary>
+ public const int Status424FailedDependency = 424;
+
+ /// <summary>
+ /// HTTP status code 426.
+ /// </summary>
+ public const int Status426UpgradeRequired = 426;
+
+ /// <summary>
+ /// HTTP status code 428.
+ /// </summary>
+ public const int Status428PreconditionRequired = 428;
+
+ /// <summary>
+ /// HTTP status code 429.
+ /// </summary>
+ public const int Status429TooManyRequests = 429;
+
+ /// <summary>
+ /// HTTP status code 431.
+ /// </summary>
+ public const int Status431RequestHeaderFieldsTooLarge = 431;
+
+ /// <summary>
+ /// HTTP status code 451.
+ /// </summary>
+ public const int Status451UnavailableForLegalReasons = 451;
+
+ /// <summary>
+ /// HTTP status code 500.
+ /// </summary>
+
+ public const int Status500InternalServerError = 500;
+
+ /// <summary>
+ /// HTTP status code 501.
+ /// </summary>
+ public const int Status501NotImplemented = 501;
+
+ /// <summary>
+ /// HTTP status code 502.
+ /// </summary>
+ public const int Status502BadGateway = 502;
+
+ /// <summary>
+ /// HTTP status code 503.
+ /// </summary>
+ public const int Status503ServiceUnavailable = 503;
+
+ /// <summary>
+ /// HTTP status code 504.
+ /// </summary>
+ public const int Status504GatewayTimeout = 504;
+
+ /// <summary>
+ /// HTTP status code 505.
+ /// </summary>
+ public const int Status505HttpVersionNotsupported = 505;
+
+ /// <summary>
+ /// HTTP status code 506.
+ /// </summary>
+ public const int Status506VariantAlsoNegotiates = 506;
+
+ /// <summary>
+ /// HTTP status code 507.
+ /// </summary>
+ public const int Status507InsufficientStorage = 507;
+
+ /// <summary>
+ /// HTTP status code 508.
+ /// </summary>
+ public const int Status508LoopDetected = 508;
+
+ /// <summary>
+ /// HTTP status code 510.
+ /// </summary>
+ public const int Status510NotExtended = 510;
+
+ /// <summary>
+ /// HTTP status code 511.
+ /// </summary>
+ public const int Status511NetworkAuthenticationRequired = 511;
}
diff --git a/src/Http/Http.Abstractions/src/WebSocketManager.cs b/src/Http/Http.Abstractions/src/WebSocketManager.cs
index 14725d698d..6f1f76a467 100644
--- a/src/Http/Http.Abstractions/src/WebSocketManager.cs
+++ b/src/Http/Http.Abstractions/src/WebSocketManager.cs
@@ -6,44 +6,43 @@ using System.Collections.Generic;
using System.Net.WebSockets;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Manages the establishment of WebSocket connections for a specific HTTP request.
+/// </summary>
+public abstract class WebSocketManager
{
/// <summary>
- /// Manages the establishment of WebSocket connections for a specific HTTP request.
+ /// Gets a value indicating whether the request is a WebSocket establishment 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; }
+ public abstract bool IsWebSocketRequest { get; }
- /// <summary>
- /// Gets the list of requested WebSocket sub-protocols.
- /// </summary>
- public abstract IList<string> WebSocketRequestedProtocols { 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.
+ /// </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);
+ /// <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);
- /// <summary>
- ///
- /// </summary>
- /// <param name="acceptContext"></param>
- /// <returns></returns>
- public virtual Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext acceptContext) => throw new NotImplementedException();
- }
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="acceptContext"></param>
+ /// <returns></returns>
+ public virtual Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext acceptContext) => throw new NotImplementedException();
}
diff --git a/src/Http/Http.Abstractions/test/CookieBuilderTests.cs b/src/Http/Http.Abstractions/test/CookieBuilderTests.cs
index 6b545c18dd..fa5c8b5f9d 100644
--- a/src/Http/Http.Abstractions/test/CookieBuilderTests.cs
+++ b/src/Http/Http.Abstractions/test/CookieBuilderTests.cs
@@ -4,54 +4,53 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Abstractions.Tests
+namespace Microsoft.AspNetCore.Http.Abstractions.Tests;
+
+public class CookieBuilderTests
{
- 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)
{
- [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()
+ var builder = new CookieBuilder
{
- Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).Expires);
+ SecurePolicy = policy
+ };
+ var context = new DefaultHttpContext();
+ context.Request.IsHttps = requestIsHttps;
+ var options = builder.Build(context);
+
+ Assert.Equal(secure, options.Secure);
+ }
- 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 ComputesExpiration()
+ {
+ Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).Expires);
- [Fact]
- public void ComputesMaxAge()
- {
- Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).MaxAge);
+ var now = DateTimeOffset.Now;
+ var options = new CookieBuilder { Expiration = TimeSpan.FromHours(1) }.Build(new DefaultHttpContext(), now);
+ Assert.Equal(now.AddHours(1), options.Expires);
+ }
- var now = TimeSpan.FromHours(1);
- var options = new CookieBuilder { MaxAge = now }.Build(new DefaultHttpContext());
- Assert.Equal(now, options.MaxAge);
- }
+ [Fact]
+ public void ComputesMaxAge()
+ {
+ Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).MaxAge);
- [Fact]
- public void CookieBuilderPreservesDefaultPath()
- {
- Assert.Equal(new CookieOptions().Path, new CookieBuilder().Build(new DefaultHttpContext()).Path);
- }
+ 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/EndpointHttpContextExtensionsTests.cs b/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs
index 247a9e880b..fd742aa6e0 100644
--- a/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs
+++ b/src/Http/Http.Abstractions/test/EndpointHttpContextExtensionsTests.cs
@@ -8,148 +8,147 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Abstractions.Tests
+namespace Microsoft.AspNetCore.Http.Abstractions.Tests;
+
+public class EndpointHttpContextExtensionsTests
{
- public class EndpointHttpContextExtensionsTests
+ [Fact]
+ public void GetEndpoint_ContextWithoutFeature_ReturnsNull()
{
- [Fact]
- public void GetEndpoint_ContextWithoutFeature_ReturnsNull()
- {
- // Arrange
- var context = new DefaultHttpContext();
+ // Arrange
+ var context = new DefaultHttpContext();
- // Act
- var endpoint = context.GetEndpoint();
+ // Act
+ var endpoint = context.GetEndpoint();
- // Assert
- Assert.Null(endpoint);
- }
+ // Assert
+ Assert.Null(endpoint);
+ }
- [Fact]
- public void GetEndpoint_ContextWithFeatureAndNullEndpoint_ReturnsNull()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Features.Set<IEndpointFeature>(new EndpointFeature
- {
- Endpoint = null
- });
-
- // Act
- var endpoint = context.GetEndpoint();
-
- // Assert
- Assert.Null(endpoint);
- }
-
- [Fact]
- public void GetEndpoint_ContextWithFeatureAndEndpoint_ReturnsEndpoint()
- {
- // Arrange
- var context = new DefaultHttpContext();
- var initial = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
- context.Features.Set<IEndpointFeature>(new EndpointFeature
- {
- Endpoint = initial
- });
-
- // Act
- var endpoint = context.GetEndpoint();
-
- // Assert
- Assert.Equal(initial, endpoint);
- }
-
- [Fact]
- public void SetEndpoint_NullOnContextWithoutFeature_NoFeatureSet()
+ [Fact]
+ public void GetEndpoint_ContextWithFeatureAndNullEndpoint_ReturnsNull()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Features.Set<IEndpointFeature>(new EndpointFeature
{
- // Arrange
- var context = new DefaultHttpContext();
+ Endpoint = null
+ });
- // Act
- context.SetEndpoint(null);
+ // Act
+ var endpoint = context.GetEndpoint();
- // Assert
- Assert.Null(context.Features.Get<IEndpointFeature>());
- }
+ // Assert
+ Assert.Null(endpoint);
+ }
- [Fact]
- public void SetEndpoint_EndpointOnContextWithoutFeature_FeatureWithEndpointSet()
+ [Fact]
+ public void GetEndpoint_ContextWithFeatureAndEndpoint_ReturnsEndpoint()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var initial = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ context.Features.Set<IEndpointFeature>(new EndpointFeature
{
- // Arrange
- var context = new DefaultHttpContext();
+ Endpoint = initial
+ });
- // Act
- var endpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
- context.SetEndpoint(endpoint);
+ // Act
+ var endpoint = context.GetEndpoint();
- // Assert
- var feature = context.Features.Get<IEndpointFeature>();
- Assert.NotNull(feature);
- Assert.Equal(endpoint, context.GetEndpoint());
- }
+ // Assert
+ Assert.Equal(initial, endpoint);
+ }
- [Fact]
- public void SetEndpoint_EndpointOnContextWithFeature_EndpointSetOnExistingFeature()
- {
- // Arrange
- var context = new DefaultHttpContext();
- var initialEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
- var initialFeature = new EndpointFeature
- {
- Endpoint = initialEndpoint
- };
- context.Features.Set<IEndpointFeature>(initialFeature);
-
- // Act
- var endpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
- context.SetEndpoint(endpoint);
-
- // Assert
- var feature = context.Features.Get<IEndpointFeature>();
- Assert.Equal(initialFeature, feature);
- Assert.Equal(endpoint, context.GetEndpoint());
- }
-
- [Fact]
- public void SetEndpoint_NullOnContextWithFeature_NullSetOnExistingFeature()
+ [Fact]
+ public void SetEndpoint_NullOnContextWithoutFeature_NoFeatureSet()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+
+ // Act
+ context.SetEndpoint(null);
+
+ // Assert
+ Assert.Null(context.Features.Get<IEndpointFeature>());
+ }
+
+ [Fact]
+ public void SetEndpoint_EndpointOnContextWithoutFeature_FeatureWithEndpointSet()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+
+ // Act
+ var endpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ context.SetEndpoint(endpoint);
+
+ // Assert
+ var feature = context.Features.Get<IEndpointFeature>();
+ Assert.NotNull(feature);
+ Assert.Equal(endpoint, context.GetEndpoint());
+ }
+
+ [Fact]
+ public void SetEndpoint_EndpointOnContextWithFeature_EndpointSetOnExistingFeature()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var initialEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ var initialFeature = new EndpointFeature
{
- // Arrange
- var context = new DefaultHttpContext();
- var initialEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
- var initialFeature = new EndpointFeature
- {
- Endpoint = initialEndpoint
- };
- context.Features.Set<IEndpointFeature>(initialFeature);
-
- // Act
- context.SetEndpoint(null);
-
- // Assert
- var feature = context.Features.Get<IEndpointFeature>();
- Assert.Equal(initialFeature, feature);
- Assert.Null(context.GetEndpoint());
- }
-
- [Fact]
- public void SetAndGetEndpoint_Roundtrip_EndpointIsRoundtrip()
+ Endpoint = initialEndpoint
+ };
+ context.Features.Set<IEndpointFeature>(initialFeature);
+
+ // Act
+ var endpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ context.SetEndpoint(endpoint);
+
+ // Assert
+ var feature = context.Features.Get<IEndpointFeature>();
+ Assert.Equal(initialFeature, feature);
+ Assert.Equal(endpoint, context.GetEndpoint());
+ }
+
+ [Fact]
+ public void SetEndpoint_NullOnContextWithFeature_NullSetOnExistingFeature()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var initialEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ var initialFeature = new EndpointFeature
{
- // Arrange
- var context = new DefaultHttpContext();
- var initialEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+ Endpoint = initialEndpoint
+ };
+ context.Features.Set<IEndpointFeature>(initialFeature);
- // Act
- context.SetEndpoint(initialEndpoint);
- var endpoint = context.GetEndpoint();
+ // Act
+ context.SetEndpoint(null);
- // Assert
- Assert.Equal(initialEndpoint, endpoint);
- }
+ // Assert
+ var feature = context.Features.Get<IEndpointFeature>();
+ Assert.Equal(initialFeature, feature);
+ Assert.Null(context.GetEndpoint());
+ }
- private class EndpointFeature : IEndpointFeature
- {
- public Endpoint? Endpoint { get; set; }
- }
+ [Fact]
+ public void SetAndGetEndpoint_Roundtrip_EndpointIsRoundtrip()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ var initialEndpoint = new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test endpoint");
+
+ // Act
+ context.SetEndpoint(initialEndpoint);
+ var endpoint = context.GetEndpoint();
+
+ // Assert
+ Assert.Equal(initialEndpoint, endpoint);
+ }
+
+ private class EndpointFeature : IEndpointFeature
+ {
+ public Endpoint? Endpoint { get; set; }
}
}
diff --git a/src/Http/Http.Abstractions/test/EndpointMetadataCollectionTests.cs b/src/Http/Http.Abstractions/test/EndpointMetadataCollectionTests.cs
index 1cc65f79e4..37a2e6d025 100644
--- a/src/Http/Http.Abstractions/test/EndpointMetadataCollectionTests.cs
+++ b/src/Http/Http.Abstractions/test/EndpointMetadataCollectionTests.cs
@@ -7,69 +7,68 @@ using System.Text;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class EndpointMetadataCollectionTests
{
- public class EndpointMetadataCollectionTests
+ [Fact]
+ public void Constructor_Enumeration_ContainsValues()
{
- [Fact]
- public void Constructor_Enumeration_ContainsValues()
- {
- // Arrange & Act
- var metadata = new EndpointMetadataCollection(new List<object>
+ // Arrange & Act
+ var metadata = new EndpointMetadataCollection(new List<object>
{
1,
2,
3,
});
- // Assert
- Assert.Equal(3, metadata.Count);
+ // Assert
+ Assert.Equal(3, metadata.Count);
- Assert.Collection(metadata,
- value => Assert.Equal(1, value),
- value => Assert.Equal(2, value),
- value => Assert.Equal(3, value));
- }
+ Assert.Collection(metadata,
+ value => Assert.Equal(1, value),
+ value => Assert.Equal(2, value),
+ value => Assert.Equal(3, value));
+ }
- [Fact]
- public void Constructor_ParamsArray_ContainsValues()
- {
- // Arrange & Act
- var metadata = new EndpointMetadataCollection(1, 2, 3);
+ [Fact]
+ public void Constructor_ParamsArray_ContainsValues()
+ {
+ // Arrange & Act
+ var metadata = new EndpointMetadataCollection(1, 2, 3);
- // Assert
- Assert.Equal(3, metadata.Count);
+ // Assert
+ Assert.Equal(3, metadata.Count);
- Assert.Collection(metadata,
- value => Assert.Equal(1, value),
- value => Assert.Equal(2, value),
- value => Assert.Equal(3, value));
- }
+ Assert.Collection(metadata,
+ value => Assert.Equal(1, value),
+ value => Assert.Equal(2, value),
+ value => Assert.Equal(3, value));
+ }
- [Fact]
- public void GetOrderedMetadata_CanReturnEmptyCollection()
- {
- // Arrange
- var metadata = new EndpointMetadataCollection(1, 2, 3);
+ [Fact]
+ public void GetOrderedMetadata_CanReturnEmptyCollection()
+ {
+ // Arrange
+ var metadata = new EndpointMetadataCollection(1, 2, 3);
- // Act
- var ordered = metadata.GetOrderedMetadata<string>();
+ // Act
+ var ordered = metadata.GetOrderedMetadata<string>();
- Assert.Same(Array.Empty<string>(), ordered);
- }
+ Assert.Same(Array.Empty<string>(), ordered);
+ }
- [Fact]
- public void GetOrderedMetadata_CanReturnNonEmptyCollection()
- {
- // Arrange
- var metadata = new EndpointMetadataCollection("1", "2");
+ [Fact]
+ public void GetOrderedMetadata_CanReturnNonEmptyCollection()
+ {
+ // Arrange
+ var metadata = new EndpointMetadataCollection("1", "2");
- // Act
- var ordered1 = metadata.GetOrderedMetadata<string>();
- var ordered2 = metadata.GetOrderedMetadata<string>();
+ // Act
+ var ordered1 = metadata.GetOrderedMetadata<string>();
+ var ordered2 = metadata.GetOrderedMetadata<string>();
- Assert.Same(ordered1, ordered2);
- Assert.Equal(new string[] { "1", "2" }, ordered1);
- }
+ Assert.Same(ordered1, ordered2);
+ Assert.Equal(new string[] { "1", "2" }, ordered1);
}
}
diff --git a/src/Http/Http.Abstractions/test/FragmentStringTests.cs b/src/Http/Http.Abstractions/test/FragmentStringTests.cs
index 370f5b92f9..7eb68cc1a2 100644
--- a/src/Http/Http.Abstractions/test/FragmentStringTests.cs
+++ b/src/Http/Http.Abstractions/test/FragmentStringTests.cs
@@ -3,39 +3,38 @@
using Xunit;
-namespace Microsoft.AspNetCore.Http.Abstractions.Tests
+namespace Microsoft.AspNetCore.Http.Abstractions.Tests;
+
+public class FragmentStringTests
{
- public class FragmentStringTests
+ [Fact]
+ public void Equals_EmptyFragmentStringAndDefaultFragmentString()
{
- [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);
- }
+ // 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");
+ [Fact]
+ public void NotEquals_DefaultFragmentStringAndNonNullFragmentString()
+ {
+ // Arrange
+ var fragmentString = new FragmentString("#col=1");
- // Act and Assert
- Assert.NotEqual(default(FragmentString), fragmentString);
- }
+ // Act and Assert
+ Assert.NotEqual(default(FragmentString), fragmentString);
+ }
- [Fact]
- public void NotEquals_EmptyFragmentStringAndNonNullFragmentString()
- {
- // Arrange
- var fragmentString = new FragmentString("#col=1");
+ [Fact]
+ public void NotEquals_EmptyFragmentStringAndNonNullFragmentString()
+ {
+ // Arrange
+ var fragmentString = new FragmentString("#col=1");
- // Act and Assert
- Assert.NotEqual(fragmentString, FragmentString.Empty);
- }
+ // 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
index c672ccfc80..443e382c13 100644
--- a/src/Http/Http.Abstractions/test/HostStringTest.cs
+++ b/src/Http/Http.Abstractions/test/HostStringTest.cs
@@ -6,170 +6,169 @@ using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class HostStringTests
{
- 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()
{
- [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" }));
- }
+ Assert.Throws<FormatException>(() => HostString.MatchesAny("example.com:1abc", new StringSegment[] { "example.com" }));
}
}
diff --git a/src/Http/Http.Abstractions/test/HttpMethodslTests.cs b/src/Http/Http.Abstractions/test/HttpMethodslTests.cs
index 10c8e32a99..b60b5468eb 100644
--- a/src/Http/Http.Abstractions/test/HttpMethodslTests.cs
+++ b/src/Http/Http.Abstractions/test/HttpMethodslTests.cs
@@ -6,14 +6,14 @@ using System.Collections.Generic;
using System.Text;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Abstractions
+namespace Microsoft.AspNetCore.Http.Abstractions;
+
+public class HttpMethodslTests
{
- public class HttpMethodslTests
+ [Fact]
+ public void CanonicalizedValue_Success()
{
- [Fact]
- public void CanonicalizedValue_Success()
- {
- var testCases = new List<(string[] methods, string expectedMethod)>
+ var testCases = new List<(string[] methods, string expectedMethod)>
{
(new string[] { "GET", "Get", "get" }, HttpMethods.Get),
(new string[] { "POST", "Post", "post" }, HttpMethods.Post),
@@ -26,28 +26,27 @@ namespace Microsoft.AspNetCore.Http.Abstractions
(new string[] { "TRACE", "Trace", "trace" }, HttpMethods.Trace)
};
- for (int i = 0; i < testCases.Count; i++)
+ for (int i = 0; i < testCases.Count; i++)
+ {
+ var testCase = testCases[i];
+ for (int j = 0; j < testCase.methods.Length; j++)
{
- var testCase = testCases[i];
- for (int j = 0; j < testCase.methods.Length; j++)
- {
- CanonicalizedValueTest(testCase.methods[j], testCase.expectedMethod);
- }
+ CanonicalizedValueTest(testCase.methods[j], testCase.expectedMethod);
}
}
+ }
- private void CanonicalizedValueTest(string method, string expectedMethod)
- {
- string inputMethod = CreateStringAtRuntime(method);
- var canonicalizedValue = HttpMethods.GetCanonicalizedValue(inputMethod);
+ private void CanonicalizedValueTest(string method, string expectedMethod)
+ {
+ string inputMethod = CreateStringAtRuntime(method);
+ var canonicalizedValue = HttpMethods.GetCanonicalizedValue(inputMethod);
- Assert.Same(expectedMethod, canonicalizedValue);
- }
+ Assert.Same(expectedMethod, canonicalizedValue);
+ }
- private string CreateStringAtRuntime(string input)
- {
- return new StringBuilder(input).ToString();
- }
+ private string CreateStringAtRuntime(string input)
+ {
+ return new StringBuilder(input).ToString();
}
}
diff --git a/src/Http/Http.Abstractions/test/HttpProtocolTests.cs b/src/Http/Http.Abstractions/test/HttpProtocolTests.cs
index a73ce04e7f..adcf24c5b5 100644
--- a/src/Http/Http.Abstractions/test/HttpProtocolTests.cs
+++ b/src/Http/Http.Abstractions/test/HttpProtocolTests.cs
@@ -4,106 +4,106 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Abstractions
+namespace Microsoft.AspNetCore.Http.Abstractions;
+
+public class HttpProtocolTests
{
- public class HttpProtocolTests
+ [Fact]
+ public void Http3_Success()
{
- [Fact]
- public void Http3_Success()
- {
- Assert.Equal("HTTP/3", HttpProtocol.Http3);
- }
-
- [Theory]
- [InlineData("HTTP/3", true)]
- [InlineData("http/3", true)]
- [InlineData("HTTP/1.1", false)]
- [InlineData("HTTP/3.0", false)]
- [InlineData("HTTP/1", false)]
- [InlineData(" HTTP/3", false)]
- [InlineData("HTTP/3 ", false)]
- public void IsHttp3_Success(string protocol, bool match)
- {
- Assert.Equal(match, HttpProtocol.IsHttp3(protocol));
- }
+ Assert.Equal("HTTP/3", HttpProtocol.Http3);
+ }
- [Fact]
- public void Http2_Success()
- {
- Assert.Equal("HTTP/2", HttpProtocol.Http2);
- }
-
- [Theory]
- [InlineData("HTTP/2", true)]
- [InlineData("http/2", true)]
- [InlineData("HTTP/1.1", false)]
- [InlineData("HTTP/2.0", false)]
- [InlineData("HTTP/1", false)]
- [InlineData(" HTTP/2", false)]
- [InlineData("HTTP/2 ", false)]
- public void IsHttp2_Success(string protocol, bool match)
- {
- Assert.Equal(match, HttpProtocol.IsHttp2(protocol));
- }
+ [Theory]
+ [InlineData("HTTP/3", true)]
+ [InlineData("http/3", true)]
+ [InlineData("HTTP/1.1", false)]
+ [InlineData("HTTP/3.0", false)]
+ [InlineData("HTTP/1", false)]
+ [InlineData(" HTTP/3", false)]
+ [InlineData("HTTP/3 ", false)]
+ public void IsHttp3_Success(string protocol, bool match)
+ {
+ Assert.Equal(match, HttpProtocol.IsHttp3(protocol));
+ }
- [Fact]
- public void Http11_Success()
- {
- Assert.Equal("HTTP/1.1", HttpProtocol.Http11);
- }
-
- [Theory]
- [InlineData("HTTP/1.1", true)]
- [InlineData("http/1.1", true)]
- [InlineData("HTTP/2", false)]
- [InlineData("HTTP/1.0", false)]
- [InlineData("HTTP/1", false)]
- [InlineData(" HTTP/1.1", false)]
- [InlineData("HTTP/1.1 ", false)]
- public void IsHttp11_Success(string protocol, bool match)
- {
- Assert.Equal(match, HttpProtocol.IsHttp11(protocol));
- }
+ [Fact]
+ public void Http2_Success()
+ {
+ Assert.Equal("HTTP/2", HttpProtocol.Http2);
+ }
- [Fact]
- public void Http10_Success()
- {
- Assert.Equal("HTTP/1.0", HttpProtocol.Http10);
- }
-
- [Theory]
- [InlineData("HTTP/1.0", true)]
- [InlineData("http/1.0", true)]
- [InlineData("HTTP/2", false)]
- [InlineData("HTTP/1.1", false)]
- [InlineData("HTTP/1", false)]
- [InlineData(" HTTP/1.0", false)]
- [InlineData("HTTP/1.0 ", false)]
- public void IsHttp10_Success(string protocol, bool match)
- {
- Assert.Equal(match, HttpProtocol.IsHttp10(protocol));
- }
+ [Theory]
+ [InlineData("HTTP/2", true)]
+ [InlineData("http/2", true)]
+ [InlineData("HTTP/1.1", false)]
+ [InlineData("HTTP/2.0", false)]
+ [InlineData("HTTP/1", false)]
+ [InlineData(" HTTP/2", false)]
+ [InlineData("HTTP/2 ", false)]
+ public void IsHttp2_Success(string protocol, bool match)
+ {
+ Assert.Equal(match, HttpProtocol.IsHttp2(protocol));
+ }
- [Fact]
- public void Http09_Success()
- {
- Assert.Equal("HTTP/0.9", HttpProtocol.Http09);
- }
-
- [Theory]
- [InlineData("HTTP/0.9", true)]
- [InlineData("http/0.9", true)]
- [InlineData("HTTP/2", false)]
- [InlineData("HTTP/1", false)]
- [InlineData("HTTP/09", false)]
- [InlineData(" HTTP/0.9", false)]
- [InlineData("HTTP/0.9 ", false)]
- public void IsHttp09_Success(string protocol, bool match)
- {
- Assert.Equal(match, HttpProtocol.IsHttp09(protocol));
- }
+ [Fact]
+ public void Http11_Success()
+ {
+ Assert.Equal("HTTP/1.1", HttpProtocol.Http11);
+ }
- public static TheoryData<Version, string> s_ValidData = new TheoryData<Version, string>
+ [Theory]
+ [InlineData("HTTP/1.1", true)]
+ [InlineData("http/1.1", true)]
+ [InlineData("HTTP/2", false)]
+ [InlineData("HTTP/1.0", false)]
+ [InlineData("HTTP/1", false)]
+ [InlineData(" HTTP/1.1", false)]
+ [InlineData("HTTP/1.1 ", false)]
+ public void IsHttp11_Success(string protocol, bool match)
+ {
+ Assert.Equal(match, HttpProtocol.IsHttp11(protocol));
+ }
+
+ [Fact]
+ public void Http10_Success()
+ {
+ Assert.Equal("HTTP/1.0", HttpProtocol.Http10);
+ }
+
+ [Theory]
+ [InlineData("HTTP/1.0", true)]
+ [InlineData("http/1.0", true)]
+ [InlineData("HTTP/2", false)]
+ [InlineData("HTTP/1.1", false)]
+ [InlineData("HTTP/1", false)]
+ [InlineData(" HTTP/1.0", false)]
+ [InlineData("HTTP/1.0 ", false)]
+ public void IsHttp10_Success(string protocol, bool match)
+ {
+ Assert.Equal(match, HttpProtocol.IsHttp10(protocol));
+ }
+
+ [Fact]
+ public void Http09_Success()
+ {
+ Assert.Equal("HTTP/0.9", HttpProtocol.Http09);
+ }
+
+ [Theory]
+ [InlineData("HTTP/0.9", true)]
+ [InlineData("http/0.9", true)]
+ [InlineData("HTTP/2", false)]
+ [InlineData("HTTP/1", false)]
+ [InlineData("HTTP/09", false)]
+ [InlineData(" HTTP/0.9", false)]
+ [InlineData("HTTP/0.9 ", false)]
+ public void IsHttp09_Success(string protocol, bool match)
+ {
+ Assert.Equal(match, HttpProtocol.IsHttp09(protocol));
+ }
+
+ public static TheoryData<Version, string> s_ValidData = new TheoryData<Version, string>
{
{ new Version(3, 0), "HTTP/3" },
{ new Version(2, 0), "HTTP/2" },
@@ -112,26 +112,25 @@ namespace Microsoft.AspNetCore.Http.Abstractions
{ new Version(0, 9), "HTTP/0.9" }
};
- [Theory]
- [MemberData(nameof(s_ValidData))]
- public void GetHttpProtocol_CorrectIETFVersion(Version version, string expected)
- {
- var actual = HttpProtocol.GetHttpProtocol(version);
+ [Theory]
+ [MemberData(nameof(s_ValidData))]
+ public void GetHttpProtocol_CorrectIETFVersion(Version version, string expected)
+ {
+ var actual = HttpProtocol.GetHttpProtocol(version);
- Assert.Equal(expected, actual);
- }
+ Assert.Equal(expected, actual);
+ }
- public static TheoryData<Version> s_InvalidData = new TheoryData<Version>
+ public static TheoryData<Version> s_InvalidData = new TheoryData<Version>
{
{ new Version(0, 3) },
{ new Version(2, 1) }
};
- [Theory]
- [MemberData(nameof(s_InvalidData))]
- public void GetHttpProtocol_ThrowErrorForUnknownVersion(Version version)
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => HttpProtocol.GetHttpProtocol(version));
- }
+ [Theory]
+ [MemberData(nameof(s_InvalidData))]
+ public void GetHttpProtocol_ThrowErrorForUnknownVersion(Version version)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => HttpProtocol.GetHttpProtocol(version));
}
}
diff --git a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs
index 6f47fa57d1..60db97e5ba 100644
--- a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs
+++ b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs
@@ -8,74 +8,74 @@ using System.Text;
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class HttpResponseWritingExtensionsTests
{
- public class HttpResponseWritingExtensionsTests
+ [Fact]
+ public async Task WritingText_WriteText()
{
- [Fact]
- public async Task WritingText_WriteText()
- {
- HttpContext context = CreateRequest();
- await context.Response.WriteAsync("Hello World");
+ HttpContext context = CreateRequest();
+ await context.Response.WriteAsync("Hello World");
- Assert.Equal(11, context.Response.Body.Length);
- }
+ 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");
+ [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);
- }
+ Assert.Equal(22, context.Response.Body.Length);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public async Task WritingTextThatRequiresMultipleSegmentsWorks(Encoding encoding)
- {
- var outputStream = new MemoryStream();
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public async Task WritingTextThatRequiresMultipleSegmentsWorks(Encoding encoding)
+ {
+ var outputStream = new MemoryStream();
- HttpContext context = new DefaultHttpContext();
- context.Response.Body = outputStream;
+ HttpContext context = new DefaultHttpContext();
+ context.Response.Body = outputStream;
- var inputString = string.Concat(Enumerable.Repeat("昨日すき焼きを食べました", 1000));
- var expected = encoding.GetBytes(inputString);
- await context.Response.WriteAsync(inputString, encoding);
+ var inputString = string.Concat(Enumerable.Repeat("昨日すき焼きを食べました", 1000));
+ var expected = encoding.GetBytes(inputString);
+ await context.Response.WriteAsync(inputString, encoding);
- outputStream.Position = 0;
- var actual = new byte[expected.Length];
- var length = outputStream.Read(actual);
+ outputStream.Position = 0;
+ var actual = new byte[expected.Length];
+ var length = outputStream.Read(actual);
- Assert.Equal(expected.Length, length);
- Assert.Equal(expected, actual);
- }
+ Assert.Equal(expected.Length, length);
+ Assert.Equal(expected, actual);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public async Task WritingTextWithPassedInEncodingWorks(Encoding encoding)
- {
- HttpContext context = CreateRequest();
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public async Task WritingTextWithPassedInEncodingWorks(Encoding encoding)
+ {
+ HttpContext context = CreateRequest();
- var inputString = "昨日すき焼きを食べました";
- var expected = encoding.GetBytes(inputString);
- await context.Response.WriteAsync(inputString, encoding);
+ var inputString = "昨日すき焼きを食べました";
+ var expected = encoding.GetBytes(inputString);
+ await context.Response.WriteAsync(inputString, encoding);
- context.Response.Body.Position = 0;
- var actual = new byte[expected.Length * 2];
- var length = context.Response.Body.Read(actual);
+ context.Response.Body.Position = 0;
+ var actual = new byte[expected.Length * 2];
+ var length = context.Response.Body.Read(actual);
- var actualShortened = new byte[length];
- Array.Copy(actual, actualShortened, length);
+ var actualShortened = new byte[length];
+ Array.Copy(actual, actualShortened, length);
- Assert.Equal(expected.Length, length);
- Assert.Equal(expected, actualShortened);
- }
+ Assert.Equal(expected.Length, length);
+ Assert.Equal(expected, actualShortened);
+ }
- public static TheoryData<Encoding> Encodings =>
- new TheoryData<Encoding>
- {
+ public static TheoryData<Encoding> Encodings =>
+ new TheoryData<Encoding>
+ {
{ Encoding.ASCII },
{ Encoding.BigEndianUnicode },
{ Encoding.Unicode },
@@ -84,13 +84,12 @@ namespace Microsoft.AspNetCore.Http
{ Encoding.UTF7 },
#pragma warning restore CS0618, SYSLIB0001 // Type or member is obsolete
{ Encoding.UTF8 }
- };
+ };
- private HttpContext CreateRequest()
- {
- HttpContext context = new DefaultHttpContext();
- context.Response.Body = new MemoryStream();
- return context;
- }
+ 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
index 48c718a47c..2882c92b63 100644
--- a/src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs
+++ b/src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs
@@ -5,269 +5,268 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+public class MapPathMiddlewareTests
{
- public class MapPathMiddlewareTests
+ private static Task Success(HttpContext context)
{
- 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);
- }
+ 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 void UseSuccess(IApplicationBuilder app)
+ {
+ app.Run(Success);
+ }
- private static Task NotImplemented(HttpContext context)
- {
- throw new NotImplementedException();
- }
+ private static Task NotImplemented(HttpContext context)
+ {
+ throw new NotImplementedException();
+ }
- private static void UseNotImplemented(IApplicationBuilder app)
- {
- app.Run(NotImplemented);
- }
+ 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!));
- }
+ [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 async Task 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();
- await app.Invoke(context);
-
- 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")]
+ public async Task 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();
+ await app.Invoke(context);
+
+ 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 async Task 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();
- await app.Invoke(context);
-
- Assert.Equal(200, context.Response.StatusCode);
- Assert.Equal(string.Concat(basePath, requestPath.AsSpan(0, matchPath.Length)), (string)context.Items["test.PathBase"]!);
- Assert.Equal(requestPath.Substring(matchPath.Length), context.Items["test.Path"]);
- }
+ [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 async Task 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();
+ await app.Invoke(context);
+
+ Assert.Equal(200, context.Response.StatusCode);
+ Assert.Equal(string.Concat(basePath, requestPath.AsSpan(0, matchPath.Length)), (string)context.Items["test.PathBase"]!);
+ Assert.Equal(requestPath.Substring(matchPath.Length), context.Items["test.Path"]);
+ }
- [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 async Task PathMatchAction_BranchTaken_WithPreserveMatchedPathSegment(string matchPath, string basePath, string requestPath)
- {
- HttpContext context = CreateRequest(basePath, requestPath);
- var builder = new ApplicationBuilder(serviceProvider: null!);
- builder.Map(matchPath, true, subBuilder => subBuilder.Run(Success));
- var app = builder.Build();
- await app.Invoke(context);
-
- Assert.Equal(200, context.Response.StatusCode);
- Assert.Equal(basePath, (string)context.Items["test.PathBase"]!);
- Assert.Equal(requestPath, context.Items["test.Path"]);
- }
+ [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 async Task PathMatchAction_BranchTaken_WithPreserveMatchedPathSegment(string matchPath, string basePath, string requestPath)
+ {
+ HttpContext context = CreateRequest(basePath, requestPath);
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ builder.Map(matchPath, true, subBuilder => subBuilder.Run(Success));
+ var app = builder.Build();
+ await app.Invoke(context);
+
+ Assert.Equal(200, context.Response.StatusCode);
+ Assert.Equal(basePath, (string)context.Items["test.PathBase"]!);
+ Assert.Equal(requestPath, 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("/")]
+ [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 async Task 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();
- await app.Invoke(context);
-
- 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 async Task 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();
+ await app.Invoke(context);
+
+ 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 async Task 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();
- await app.Invoke(context);
-
- 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 async Task 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();
+ await app.Invoke(context);
+
+ Assert.Equal(200, context.Response.StatusCode);
+ Assert.Equal(basePath, context.Request.PathBase.Value);
+ Assert.Equal(requestPath, context.Request.Path.Value);
+ }
- [Fact]
- public async Task ChainedRoutes_Success()
+ [Fact]
+ public async Task ChainedRoutes_Success()
+ {
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ builder.Map("/route1", map =>
{
- 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");
- await Assert.ThrowsAsync<NotImplementedException>(() => app.Invoke(context));
-
- context = CreateRequest(string.Empty, "/route1/subroute1");
- await app.Invoke(context);
- 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");
- await app.Invoke(context);
- 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");
- await app.Invoke(context);
- 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");
- await app.Invoke(context);
- Assert.Equal(200, context.Response.StatusCode);
- Assert.Equal(string.Empty, context.Request.PathBase.Value);
- Assert.Equal("/route2/subroute2/subsub2", context.Request.Path.Value);
- }
+ map.Map("/subroute1", UseSuccess);
+ map.Run(NotImplemented);
+ });
+ builder.Map("/route2/subroute2", UseSuccess);
+ var app = builder.Build();
+
+ HttpContext context = CreateRequest(string.Empty, "/route1");
+ await Assert.ThrowsAsync<NotImplementedException>(() => app.Invoke(context));
+
+ context = CreateRequest(string.Empty, "/route1/subroute1");
+ await app.Invoke(context);
+ 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");
+ await app.Invoke(context);
+ 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");
+ await app.Invoke(context);
+ 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");
+ await app.Invoke(context);
+ Assert.Equal(200, context.Response.StatusCode);
+ Assert.Equal(string.Empty, context.Request.PathBase.Value);
+ Assert.Equal("/route2/subroute2/subsub2", context.Request.Path.Value);
+ }
- [Fact]
- public void ApplicationBuilderMapOverloadPreferredOverEndpointBuilderGivenStringPathAndImplicitLambdaParameterType()
- {
- var mockWebApplication = new MockWebApplication();
+ [Fact]
+ public void ApplicationBuilderMapOverloadPreferredOverEndpointBuilderGivenStringPathAndImplicitLambdaParameterType()
+ {
+ var mockWebApplication = new MockWebApplication();
- mockWebApplication.Map("/foo", app => { });
+ mockWebApplication.Map("/foo", app => { });
- Assert.True(mockWebApplication.UseCalled);
- }
+ Assert.True(mockWebApplication.UseCalled);
+ }
- [Fact]
- public void ApplicationBuilderMapOverloadPreferredOverEndpointBuilderGivenStringPathAndExplicitLambdaParameterType()
- {
- var mockWebApplication = new MockWebApplication();
+ [Fact]
+ public void ApplicationBuilderMapOverloadPreferredOverEndpointBuilderGivenStringPathAndExplicitLambdaParameterType()
+ {
+ var mockWebApplication = new MockWebApplication();
- mockWebApplication.Map("/foo", (IApplicationBuilder app) => { });
+ mockWebApplication.Map("/foo", (IApplicationBuilder app) => { });
- Assert.True(mockWebApplication.UseCalled);
- }
+ Assert.True(mockWebApplication.UseCalled);
+ }
- 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;
- }
+ 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;
+ }
- private class MockWebApplication : IApplicationBuilder, IEndpointRouteBuilder
- {
- public bool UseCalled { get; set; }
+ private class MockWebApplication : IApplicationBuilder, IEndpointRouteBuilder
+ {
+ public bool UseCalled { get; set; }
- public IServiceProvider ApplicationServices { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ public IServiceProvider ApplicationServices { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
- public IFeatureCollection ServerFeatures => throw new NotImplementedException();
+ public IFeatureCollection ServerFeatures => throw new NotImplementedException();
- public IDictionary<string, object?> Properties => throw new NotImplementedException();
+ public IDictionary<string, object?> Properties => throw new NotImplementedException();
- public IServiceProvider ServiceProvider => throw new NotImplementedException();
+ public IServiceProvider ServiceProvider => throw new NotImplementedException();
- public ICollection<EndpointDataSource> DataSources => throw new NotImplementedException();
+ public ICollection<EndpointDataSource> DataSources => throw new NotImplementedException();
- public IApplicationBuilder CreateApplicationBuilder() => throw new NotImplementedException();
+ public IApplicationBuilder CreateApplicationBuilder() => throw new NotImplementedException();
- public IApplicationBuilder New() => this;
+ public IApplicationBuilder New() => this;
- public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
- {
- UseCalled = true;
- return this;
- }
+ public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
+ {
+ UseCalled = true;
+ return this;
+ }
- public RequestDelegate Build()
- {
- return context => Task.CompletedTask;
- }
+ public RequestDelegate Build()
+ {
+ return context => Task.CompletedTask;
}
}
}
diff --git a/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs b/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs
index 5f1a189043..a543686e72 100644
--- a/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs
+++ b/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs
@@ -6,117 +6,116 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+using Predicate = Func<HttpContext, bool>;
+
+public class MapPredicateMiddlewareTests
{
- using Predicate = Func<HttpContext, bool>;
+ private static readonly Predicate NotImplementedPredicate = new Predicate(environment => { throw new NotImplementedException(); });
- public class MapPredicateMiddlewareTests
+ private static Task Success(HttpContext context)
{
- private static readonly Predicate NotImplementedPredicate = new Predicate(environment => { throw new NotImplementedException(); });
+ context.Response.StatusCode = 200;
+ return Task.FromResult<object>(null!);
+ }
- 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 void UseSuccess(IApplicationBuilder app)
- {
- app.Run(Success);
- }
+ private static Task NotImplemented(HttpContext context)
+ {
+ throw new NotImplementedException();
+ }
- private static Task NotImplemented(HttpContext context)
- {
- throw new NotImplementedException();
- }
+ private static void UseNotImplemented(IApplicationBuilder app)
+ {
+ app.Run(NotImplemented);
+ }
- private static void UseNotImplemented(IApplicationBuilder app)
- {
- app.Run(NotImplemented);
- }
+ private bool TruePredicate(HttpContext context)
+ {
+ return true;
+ }
- private bool TruePredicate(HttpContext context)
- {
- return true;
- }
+ private bool FalsePredicate(HttpContext context)
+ {
+ return false;
+ }
- 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 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 async Task PredicateTrue_BranchTaken()
- {
- HttpContext context = CreateRequest();
- var builder = new ApplicationBuilder(serviceProvider: null!);
- builder.MapWhen(TruePredicate, UseSuccess);
- var app = builder.Build();
- await app.Invoke(context);
+ [Fact]
+ public async Task PredicateTrue_BranchTaken()
+ {
+ HttpContext context = CreateRequest();
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ builder.MapWhen(TruePredicate, UseSuccess);
+ var app = builder.Build();
+ await app.Invoke(context);
- Assert.Equal(200, context.Response.StatusCode);
- }
+ Assert.Equal(200, context.Response.StatusCode);
+ }
- [Fact]
- public async Task PredicateTrueAction_BranchTaken()
- {
- HttpContext context = CreateRequest();
- var builder = new ApplicationBuilder(serviceProvider: null!);
- builder.MapWhen(TruePredicate, UseSuccess);
- var app = builder.Build();
- await app.Invoke(context);
+ [Fact]
+ public async Task PredicateTrueAction_BranchTaken()
+ {
+ HttpContext context = CreateRequest();
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ builder.MapWhen(TruePredicate, UseSuccess);
+ var app = builder.Build();
+ await app.Invoke(context);
+
+ Assert.Equal(200, context.Response.StatusCode);
+ }
- Assert.Equal(200, context.Response.StatusCode);
- }
+ [Fact]
+ public async Task PredicateFalseAction_PassThrough()
+ {
+ HttpContext context = CreateRequest();
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ builder.MapWhen(FalsePredicate, UseNotImplemented);
+ builder.Run(Success);
+ var app = builder.Build();
+ await app.Invoke(context);
+
+ Assert.Equal(200, context.Response.StatusCode);
+ }
- [Fact]
- public async Task PredicateFalseAction_PassThrough()
- {
- HttpContext context = CreateRequest();
- var builder = new ApplicationBuilder(serviceProvider: null!);
- builder.MapWhen(FalsePredicate, UseNotImplemented);
- builder.Run(Success);
- var app = builder.Build();
- await app.Invoke(context);
-
- Assert.Equal(200, context.Response.StatusCode);
- }
-
- [Fact]
- public async Task 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();
- await app.Invoke(context);
- Assert.Equal(200, context.Response.StatusCode);
- }
-
- private HttpContext CreateRequest()
+ [Fact]
+ public async Task ChainedPredicates_Success()
+ {
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ builder.MapWhen(TruePredicate, map1 =>
{
- HttpContext context = new DefaultHttpContext();
- return context;
- }
+ 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();
+ await app.Invoke(context);
+ Assert.Equal(200, context.Response.StatusCode);
+ }
+
+ private HttpContext CreateRequest()
+ {
+ HttpContext context = new DefaultHttpContext();
+ return context;
}
}
diff --git a/src/Http/Http.Abstractions/test/PathStringTests.cs b/src/Http/Http.Abstractions/test/PathStringTests.cs
index ca53a3014e..9fe0b08138 100644
--- a/src/Http/Http.Abstractions/test/PathStringTests.cs
+++ b/src/Http/Http.Abstractions/test/PathStringTests.cs
@@ -9,342 +9,341 @@ using System.Linq;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class PathStringTests
{
- public class PathStringTests
+ [Fact]
+ public void CtorThrows_IfPathDoesNotHaveLeadingSlash()
{
- [Fact]
- public void CtorThrows_IfPathDoesNotHaveLeadingSlash()
- {
- // Act and Assert
- ExceptionAssert.ThrowsArgument(() => new PathString("hello"), "value", "The path in 'value' must start with '/'.");
- }
+ // 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 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");
+ [Fact]
+ public void NotEquals_DefaultPathStringAndNonNullPathString()
+ {
+ // Arrange
+ var pathString = new PathString("/hello");
- // Act and Assert
- Assert.NotEqual(default(PathString), pathString);
- }
+ // Act and Assert
+ Assert.NotEqual(default(PathString), pathString);
+ }
- [Fact]
- public void NotEquals_EmptyPathStringAndNonNullPathString()
- {
- // Arrange
- var pathString = new PathString("/hello");
+ [Fact]
+ public void NotEquals_EmptyPathStringAndNonNullPathString()
+ {
+ // Arrange
+ var pathString = new PathString("/hello");
- // Act and Assert
- Assert.NotEqual(pathString, PathString.Empty);
- }
+ // Act and Assert
+ Assert.NotEqual(pathString, PathString.Empty);
+ }
- [Fact]
- public void HashCode_CheckNullAndEmptyHaveSameHashcodes()
- {
- Assert.Equal(PathString.Empty.GetHashCode(), default(PathString).GetHashCode());
- }
+ [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);
+ [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);
+ // Act
+ var result = appPath.Add(concatPath);
- // Assert
- Assert.False(result.HasValue);
- }
+ // 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);
+ [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);
+ // Act
+ var result = appPath.Add(concatPath);
- // Assert
- Assert.Equal(expected, result.Value);
- }
+ // 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");
+ [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);
+ 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 = pathBase + path + query + fragment;
+ Assert.Equal("/base/path?query#frag", result);
- result = path + "text";
- Assert.Equal("/pathtext", 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);
+ [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);
+ var result = source.StartsWithSegments(test);
- Assert.Equal(expectedResult, result);
- }
+ 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);
+ [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);
+ var result = source.StartsWithSegments(test, out var remaining);
- Assert.Equal(expectedResult, result);
- }
+ 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);
+ [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);
+ var result = source.StartsWithSegments(test, comparison);
- Assert.Equal(expectedResult, result);
- }
+ 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);
+ [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);
+ var result = source.StartsWithSegments(test, comparison, out var remaining);
- Assert.Equal(expectedResult, result);
- }
+ 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);
+ [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());
- }
+ 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 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);
- }
+ [Fact]
+ public void PathStringStaysEqualAfterAssignments()
+ {
+ PathString p1 = "/?";
+ string s1 = p1;
+ PathString p2 = s1;
+ Assert.Equal(p1, p2);
+ }
- [Theory]
- [InlineData("/a%2Fb")]
- [InlineData("/a%2F")]
- [InlineData("/%2fb")]
- [InlineData("/a%2Fb/c%2Fd/e")]
- public void StringFromUriComponentLeavesForwardSlashEscaped(string input)
- {
- var sut = PathString.FromUriComponent(input);
- Assert.Equal(input, sut.Value);
- }
+ [Theory]
+ [InlineData("/a%2Fb")]
+ [InlineData("/a%2F")]
+ [InlineData("/%2fb")]
+ [InlineData("/a%2Fb/c%2Fd/e")]
+ public void StringFromUriComponentLeavesForwardSlashEscaped(string input)
+ {
+ var sut = PathString.FromUriComponent(input);
+ Assert.Equal(input, sut.Value);
+ }
- [Theory]
- [InlineData("/a%2Fb")]
- [InlineData("/a%2F")]
- [InlineData("/%2fb")]
- [InlineData("/a%2Fb/c%2Fd/e")]
- public void UriFromUriComponentLeavesForwardSlashEscaped(string input)
- {
- var uri = new Uri($"https://localhost:5001{input}");
- var sut = PathString.FromUriComponent(uri);
- Assert.Equal(input, sut.Value);
- }
+ [Theory]
+ [InlineData("/a%2Fb")]
+ [InlineData("/a%2F")]
+ [InlineData("/%2fb")]
+ [InlineData("/a%2Fb/c%2Fd/e")]
+ public void UriFromUriComponentLeavesForwardSlashEscaped(string input)
+ {
+ var uri = new Uri($"https://localhost:5001{input}");
+ var sut = PathString.FromUriComponent(uri);
+ Assert.Equal(input, sut.Value);
+ }
- [Theory]
- [InlineData("/a%20b", "/a b")]
- [InlineData("/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a%20b",
- "/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a b")]
- public void StringFromUriComponentUnescapes(string input, string expected)
- {
- var sut = PathString.FromUriComponent(input);
- Assert.Equal(expected, sut.Value);
- }
+ [Theory]
+ [InlineData("/a%20b", "/a b")]
+ [InlineData("/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a%20b",
+ "/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a b")]
+ public void StringFromUriComponentUnescapes(string input, string expected)
+ {
+ var sut = PathString.FromUriComponent(input);
+ Assert.Equal(expected, sut.Value);
+ }
- [Theory]
- [InlineData("/a%20b", "/a b")]
- [InlineData("/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a%20b",
- "/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a b")]
- public void UriFromUriComponentUnescapes(string input, string expected)
- {
- var uri = new Uri($"https://localhost:5001{input}");
- var sut = PathString.FromUriComponent(uri);
- Assert.Equal(expected, sut.Value);
- }
+ [Theory]
+ [InlineData("/a%20b", "/a b")]
+ [InlineData("/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a%20b",
+"/thisMustBeAVeryLongPath/SoLongThatItCouldActuallyBeLargerToTheStackAllocThresholdValue/PathsShorterToThisAllocateLessOnHeapByUsingStackAllocation/api/a b")]
+ public void UriFromUriComponentUnescapes(string input, string expected)
+ {
+ var uri = new Uri($"https://localhost:5001{input}");
+ var sut = PathString.FromUriComponent(uri);
+ Assert.Equal(expected, sut.Value);
+ }
- [Theory]
- [InlineData("/a%2Fb")]
- [InlineData("/a%2F")]
- [InlineData("/%2fb")]
- [InlineData("/%2Fb%20c")]
- [InlineData("/a%2Fb%20c")]
- [InlineData("/a%20b")]
- [InlineData("/a%2Fb/c%2Fd/e%20f")]
- [InlineData("/%E4%BD%A0%E5%A5%BD")]
- public void FromUriComponentToUriComponent(string input)
- {
- var sut = PathString.FromUriComponent(input);
- Assert.Equal(input, sut.ToUriComponent());
- }
+ [Theory]
+ [InlineData("/a%2Fb")]
+ [InlineData("/a%2F")]
+ [InlineData("/%2fb")]
+ [InlineData("/%2Fb%20c")]
+ [InlineData("/a%2Fb%20c")]
+ [InlineData("/a%20b")]
+ [InlineData("/a%2Fb/c%2Fd/e%20f")]
+ [InlineData("/%E4%BD%A0%E5%A5%BD")]
+ public void FromUriComponentToUriComponent(string input)
+ {
+ var sut = PathString.FromUriComponent(input);
+ Assert.Equal(input, sut.ToUriComponent());
+ }
- [Theory]
- [MemberData(nameof(CharsToUnescape))]
- [InlineData("/%E4%BD%A0%E5%A5%BD", "/你好")]
- public void FromUriComponentUnescapesAllExceptForwardSlash(string input, string expected)
- {
- var sut = PathString.FromUriComponent(input);
- Assert.Equal(expected, sut.Value);
- }
+ [Theory]
+ [MemberData(nameof(CharsToUnescape))]
+ [InlineData("/%E4%BD%A0%E5%A5%BD", "/你好")]
+ public void FromUriComponentUnescapesAllExceptForwardSlash(string input, string expected)
+ {
+ var sut = PathString.FromUriComponent(input);
+ Assert.Equal(expected, sut.Value);
+ }
- [Theory]
- [InlineData(-1)]
- [InlineData(0)]
- [InlineData(1)]
- public void ExercisingStringFromUriComponentOnStackAllocLimit(int offset)
- {
- var path = "/";
- var testString = new string('a', PathString.StackAllocThreshold + offset - path.Length);
- var sut = PathString.FromUriComponent(path + testString);
- Assert.Equal(PathString.StackAllocThreshold + offset, sut.Value!.Length);
- }
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(0)]
+ [InlineData(1)]
+ public void ExercisingStringFromUriComponentOnStackAllocLimit(int offset)
+ {
+ var path = "/";
+ var testString = new string('a', PathString.StackAllocThreshold + offset - path.Length);
+ var sut = PathString.FromUriComponent(path + testString);
+ Assert.Equal(PathString.StackAllocThreshold + offset, sut.Value!.Length);
+ }
- [Theory]
- [InlineData(-1)]
- [InlineData(0)]
- [InlineData(1)]
- public void ExercisingUriFromUriComponentOnStackAllocLimit(int offset)
- {
- var localhost = "https://localhost:5001/";
- var testString = new string('a', PathString.StackAllocThreshold + offset);
- var sut = PathString.FromUriComponent(new Uri(localhost + testString));
- Assert.Equal(PathString.StackAllocThreshold + offset + 1, sut.Value!.Length);
- }
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(0)]
+ [InlineData(1)]
+ public void ExercisingUriFromUriComponentOnStackAllocLimit(int offset)
+ {
+ var localhost = "https://localhost:5001/";
+ var testString = new string('a', PathString.StackAllocThreshold + offset);
+ var sut = PathString.FromUriComponent(new Uri(localhost + testString));
+ Assert.Equal(PathString.StackAllocThreshold + offset + 1, sut.Value!.Length);
+ }
- public static IEnumerable<object[]> CharsToUnescape
+ public static IEnumerable<object[]> CharsToUnescape
+ {
+ get
{
- get
+ foreach (var item in Enumerable.Range(1, 127))
{
- foreach (var item in Enumerable.Range(1, 127))
+ // %2F is '/' not escaped for paths
+ if (item != 0x2f)
{
- // %2F is '/' not escaped for paths
- if (item != 0x2f)
- {
- var hexEscapedValue = "%" + item.ToString("x2", CultureInfo.InvariantCulture);
- var expected = Uri.UnescapeDataString(hexEscapedValue);
- yield return new object[] { "/a" + hexEscapedValue, "/a" + expected };
- }
+ var hexEscapedValue = "%" + item.ToString("x2", CultureInfo.InvariantCulture);
+ var expected = Uri.UnescapeDataString(hexEscapedValue);
+ yield return new object[] { "/a" + hexEscapedValue, "/a" + expected };
}
}
}
diff --git a/src/Http/Http.Abstractions/test/QueryStringTests.cs b/src/Http/Http.Abstractions/test/QueryStringTests.cs
index 1025f7a7d9..4152de93ec 100644
--- a/src/Http/Http.Abstractions/test/QueryStringTests.cs
+++ b/src/Http/Http.Abstractions/test/QueryStringTests.cs
@@ -9,158 +9,157 @@ using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Abstractions
+namespace Microsoft.AspNetCore.Http.Abstractions;
+
+public class QueryStringTests
{
- public class QueryStringTests
+ [Fact]
+ public void CtorThrows_IfQueryDoesNotHaveLeadingQuestionMark()
{
- [Fact]
- public void CtorThrows_IfQueryDoesNotHaveLeadingQuestionMark()
- {
- // Act and Assert
- ExceptionAssert.ThrowsArgument(() => new QueryString("hello"), "value", "The leading '?' must be included for a non-empty query.");
- }
+ // 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);
+ [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(null);
+ Assert.False(query.HasValue);
+ Assert.Null(query.Value);
- query = new QueryString(string.Empty);
- Assert.False(query.HasValue);
- Assert.Equal(string.Empty, 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 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 expected)
- {
- var query = QueryString.Create(name, value);
- Assert.Equal(expected, query.Value);
- }
+ [Fact]
+ public void ToString_EncodesHash()
+ {
+ var query = new QueryString("?Hello=Wor#ld");
+ Assert.Equal("?Hello=Wor%23ld", query.ToString());
+ }
- [Fact]
- public void CreateFromList_Success()
+ [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 expected)
+ {
+ var query = QueryString.Create(name, value);
+ Assert.Equal(expected, query.Value);
+ }
+
+ [Fact]
+ public void CreateFromList_Success()
+ {
+ var query = QueryString.Create(new[]
{
- 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);
- }
+ Assert.Equal("?key1=value1&key2=value2&key3=value3&key4=&key5=", query.Value);
+ }
- [Fact]
- public void CreateFromListStringValues_Success()
+ [Fact]
+ public void CreateFromListStringValues_Success()
+ {
+ var query = QueryString.Create(new[]
{
- 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);
- }
+ Assert.Equal("?key1=value1&key2=value2&key3=value3&key4=&key5=", query.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");
+ [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);
+ }
- // Act and Assert
- Assert.NotEqual(default(QueryString), queryString);
- }
+ [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_EmptyQueryStringAndNonNullQueryString()
- {
- // Arrange
- var queryString = new QueryString("?foo=1");
+ [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);
- }
+ // Act and Assert
+ Assert.NotEqual(queryString, QueryString.Empty);
}
}
diff --git a/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs b/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs
index a9cff69dca..9e56c70cab 100644
--- a/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs
+++ b/src/Http/Http.Abstractions/test/RouteValueDictionaryTests.cs
@@ -7,1813 +7,1813 @@ using System.Linq;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class RouteValueDictionaryTests
{
- public class RouteValueDictionaryTests
+ [Fact]
+ public void DefaultCtor_UsesEmptyStorage()
{
- [Fact]
- public void DefaultCtor_UsesEmptyStorage()
- {
- // Arrange
- // Act
- var dict = new RouteValueDictionary();
-
- // Assert
- Assert.Empty(dict);
- Assert.Empty(dict._arrayStorage);
- Assert.Null(dict._propertyStorage);
- }
+ // Arrange
+ // Act
+ var dict = new RouteValueDictionary();
+
+ // Assert
+ Assert.Empty(dict);
+ Assert.Empty(dict._arrayStorage);
+ Assert.Null(dict._propertyStorage);
+ }
- [Fact]
- public void CreateFromNull_UsesEmptyStorage()
- {
- // Arrange
- // Act
- var dict = new RouteValueDictionary(null);
-
- // Assert
- Assert.Empty(dict);
- Assert.Empty(dict._arrayStorage);
- Assert.Null(dict._propertyStorage);
- }
+ [Fact]
+ public void CreateFromNull_UsesEmptyStorage()
+ {
+ // Arrange
+ // Act
+ var dict = new RouteValueDictionary(null);
+
+ // Assert
+ Assert.Empty(dict);
+ Assert.Empty(dict._arrayStorage);
+ Assert.Null(dict._propertyStorage);
+ }
- [Fact]
- public void CreateFromRouteValueDictionary_WithArrayStorage_CopiesStorage()
- {
- // Arrange
- var other = new RouteValueDictionary()
+ [Fact]
+ public void CreateFromRouteValueDictionary_WithArrayStorage_CopiesStorage()
+ {
+ // Arrange
+ var other = new RouteValueDictionary()
{
{ "1", 1 }
};
- // Act
- var dict = new RouteValueDictionary(other);
+ // Act
+ var dict = new RouteValueDictionary(other);
- // Assert
- Assert.Equal(other, dict);
- Assert.Single(dict._arrayStorage);
- Assert.Null(dict._propertyStorage);
+ // Assert
+ Assert.Equal(other, dict);
+ Assert.Single(dict._arrayStorage);
+ Assert.Null(dict._propertyStorage);
- var storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- var otherStorage = Assert.IsType<KeyValuePair<string, object?>[]>(other._arrayStorage);
- Assert.NotSame(otherStorage, storage);
- }
+ var storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ var otherStorage = Assert.IsType<KeyValuePair<string, object?>[]>(other._arrayStorage);
+ Assert.NotSame(otherStorage, storage);
+ }
- [Fact]
- public void CreateFromRouteValueDictionary_WithPropertyStorage_CopiesStorage()
- {
- // Arrange
- var other = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void CreateFromRouteValueDictionary_WithPropertyStorage_CopiesStorage()
+ {
+ // Arrange
+ var other = new RouteValueDictionary(new { key = "value" });
- // Act
- var dict = new RouteValueDictionary(other);
+ // Act
+ var dict = new RouteValueDictionary(other);
- // Assert
- Assert.Equal(other, dict);
- AssertEmptyArrayStorage(dict);
+ // Assert
+ Assert.Equal(other, dict);
+ AssertEmptyArrayStorage(dict);
- var storage = dict._propertyStorage;
- var otherStorage = other._propertyStorage;
- Assert.Same(otherStorage, storage);
- }
+ var storage = dict._propertyStorage;
+ var otherStorage = other._propertyStorage;
+ Assert.Same(otherStorage, storage);
+ }
- public static IEnumerable<object[]> IEnumerableKeyValuePairData
+ public static IEnumerable<object[]> IEnumerableKeyValuePairData
+ {
+ get
{
- get
+ var routeValues = new[]
{
- var routeValues = new[]
- {
new KeyValuePair<string, object?>("Name", "James"),
new KeyValuePair<string, object?>("Age", 30),
new KeyValuePair<string, object?>("Address", new Address() { City = "Redmond", State = "WA" })
};
- yield return new object[] { routeValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) };
+ yield return new object[] { routeValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) };
- yield return new object[] { routeValues.ToList() };
+ yield return new object[] { routeValues.ToList() };
- yield return new object[] { routeValues };
- }
+ yield return new object[] { routeValues };
}
+ }
- public static IEnumerable<object[]> IEnumerableStringValuePairData
+ public static IEnumerable<object[]> IEnumerableStringValuePairData
+ {
+ get
{
- get
+ var routeValues = new[]
{
- var routeValues = new[]
- {
new KeyValuePair<string, string>("First Name", "James"),
new KeyValuePair<string, string>("Last Name", "Henrik"),
new KeyValuePair<string, string>("Middle Name", "Bob")
};
- yield return new object[] { routeValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) };
+ yield return new object[] { routeValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) };
- yield return new object[] { routeValues.ToList() };
+ yield return new object[] { routeValues.ToList() };
- yield return new object[] { routeValues };
- }
+ yield return new object[] { routeValues };
}
+ }
- [Theory]
- [MemberData(nameof(IEnumerableKeyValuePairData))]
- public void CreateFromIEnumerableKeyValuePair_CopiesValues(object values)
- {
- // Arrange & Act
- var dict = new RouteValueDictionary(values);
-
- // Assert
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("Address", kvp.Key);
- var address = Assert.IsType<Address>(kvp.Value);
- Assert.Equal("Redmond", address.City);
- Assert.Equal("WA", address.State);
- },
- kvp => { Assert.Equal("Age", kvp.Key); Assert.Equal(30, kvp.Value); },
- kvp => { Assert.Equal("Name", kvp.Key); Assert.Equal("James", kvp.Value); });
- }
+ [Theory]
+ [MemberData(nameof(IEnumerableKeyValuePairData))]
+ public void CreateFromIEnumerableKeyValuePair_CopiesValues(object values)
+ {
+ // Arrange & Act
+ var dict = new RouteValueDictionary(values);
+
+ // Assert
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("Address", kvp.Key);
+ var address = Assert.IsType<Address>(kvp.Value);
+ Assert.Equal("Redmond", address.City);
+ Assert.Equal("WA", address.State);
+ },
+ kvp => { Assert.Equal("Age", kvp.Key); Assert.Equal(30, kvp.Value); },
+ kvp => { Assert.Equal("Name", kvp.Key); Assert.Equal("James", kvp.Value); });
+ }
- [Theory]
- [MemberData(nameof(IEnumerableStringValuePairData))]
- public void CreateFromIEnumerableStringValuePair_CopiesValues(object values)
- {
- // Arrange & Act
- var dict = new RouteValueDictionary(values);
-
- // Assert
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("First Name", kvp.Key); Assert.Equal("James", kvp.Value); },
- kvp => { Assert.Equal("Last Name", kvp.Key); Assert.Equal("Henrik", kvp.Value); },
- kvp => { Assert.Equal("Middle Name", kvp.Key); Assert.Equal("Bob", kvp.Value); });
- }
+ [Theory]
+ [MemberData(nameof(IEnumerableStringValuePairData))]
+ public void CreateFromIEnumerableStringValuePair_CopiesValues(object values)
+ {
+ // Arrange & Act
+ var dict = new RouteValueDictionary(values);
+
+ // Assert
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("First Name", kvp.Key); Assert.Equal("James", kvp.Value); },
+ kvp => { Assert.Equal("Last Name", kvp.Key); Assert.Equal("Henrik", kvp.Value); },
+ kvp => { Assert.Equal("Middle Name", kvp.Key); Assert.Equal("Bob", kvp.Value); });
+ }
- [Fact]
- public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey()
- {
- // Arrange
- var values = new List<KeyValuePair<string, object?>>()
+ [Fact]
+ public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey()
+ {
+ // Arrange
+ var values = new List<KeyValuePair<string, object?>>()
{
new KeyValuePair<string, object?>("name", "Billy"),
new KeyValuePair<string, object?>("Name", "Joey"),
};
- // Act & Assert
- ExceptionAssert.ThrowsArgument(
- () => new RouteValueDictionary(values),
- "key",
- $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}.");
- }
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(
+ () => new RouteValueDictionary(values),
+ "key",
+ $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}.");
+ }
- [Fact]
- public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey()
- {
- // Arrange
- var values = new List<KeyValuePair<string, string>>()
+ [Fact]
+ public void CreateFromIEnumerableStringValuePair_ThrowsExceptionForDuplicateKey()
+ {
+ // Arrange
+ var values = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("name", "Billy"),
new KeyValuePair<string, string>("Name", "Joey"),
};
- // Act & Assert
- ExceptionAssert.ThrowsArgument(
- () => new RouteValueDictionary(values),
- "key",
- $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}.");
- }
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(
+ () => new RouteValueDictionary(values),
+ "key",
+ $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}.");
+ }
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromAnonymousType()
- {
- // Arrange
- var obj = new { cool = "beans", awesome = 123 };
-
- // Act
- var dict = new RouteValueDictionary(obj);
-
- // Assert
- Assert.NotNull(dict._propertyStorage);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("awesome", kvp.Key); Assert.Equal(123, kvp.Value); },
- kvp => { Assert.Equal("cool", kvp.Key); Assert.Equal("beans", kvp.Value); });
- }
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromAnonymousType()
+ {
+ // Arrange
+ var obj = new { cool = "beans", awesome = 123 };
+
+ // Act
+ var dict = new RouteValueDictionary(obj);
+
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("awesome", kvp.Key); Assert.Equal(123, kvp.Value); },
+ kvp => { Assert.Equal("cool", kvp.Key); Assert.Equal("beans", kvp.Value); });
+ }
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType()
- {
- // Arrange
- var obj = new RegularType() { CoolnessFactor = 73 };
-
- // Act
- var dict = new RouteValueDictionary(obj);
-
- // Assert
- Assert.NotNull(dict._propertyStorage);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("CoolnessFactor", kvp.Key);
- Assert.Equal(73, kvp.Value);
- },
- kvp =>
- {
- Assert.Equal("IsAwesome", kvp.Key);
- var value = Assert.IsType<bool>(kvp.Value);
- Assert.False(value);
- });
- }
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType()
+ {
+ // Arrange
+ var obj = new RegularType() { CoolnessFactor = 73 };
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType_PublicOnly()
- {
- // Arrange
- var obj = new Visibility() { IsPublic = true, ItsInternalDealWithIt = 5 };
-
- // Act
- var dict = new RouteValueDictionary(obj);
-
- // Assert
- Assert.NotNull(dict._propertyStorage);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("IsPublic", kvp.Key);
- var value = Assert.IsType<bool>(kvp.Value);
- Assert.True(value);
- });
- }
+ // Act
+ var dict = new RouteValueDictionary(obj);
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresStatic()
- {
- // Arrange
- var obj = new StaticProperty();
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("CoolnessFactor", kvp.Key);
+ Assert.Equal(73, kvp.Value);
+ },
+ kvp =>
+ {
+ Assert.Equal("IsAwesome", kvp.Key);
+ var value = Assert.IsType<bool>(kvp.Value);
+ Assert.False(value);
+ });
+ }
- // Act
- var dict = new RouteValueDictionary(obj);
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType_PublicOnly()
+ {
+ // Arrange
+ var obj = new Visibility() { IsPublic = true, ItsInternalDealWithIt = 5 };
- // Assert
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Empty(dict);
- }
+ // Act
+ var dict = new RouteValueDictionary(obj);
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresSetOnly()
- {
- // Arrange
- var obj = new SetterOnly() { CoolSetOnly = false };
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("IsPublic", kvp.Key);
+ var value = Assert.IsType<bool>(kvp.Value);
+ Assert.True(value);
+ });
+ }
- // Act
- var dict = new RouteValueDictionary(obj);
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresStatic()
+ {
+ // Arrange
+ var obj = new StaticProperty();
- // Assert
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Empty(dict);
- }
+ // Act
+ var dict = new RouteValueDictionary(obj);
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType_IncludesInherited()
- {
- // Arrange
- var obj = new Derived() { TotallySweetProperty = true, DerivedProperty = false };
-
- // Act
- var dict = new RouteValueDictionary(obj);
-
- // Assert
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("DerivedProperty", kvp.Key);
- var value = Assert.IsType<bool>(kvp.Value);
- Assert.False(value);
- },
- kvp =>
- {
- Assert.Equal("TotallySweetProperty", kvp.Key);
- var value = Assert.IsType<bool>(kvp.Value);
- Assert.True(value);
- });
- }
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Empty(dict);
+ }
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType_WithHiddenProperty()
- {
- // Arrange
- var obj = new DerivedHiddenProperty() { DerivedProperty = 5 };
-
- // Act
- var dict = new RouteValueDictionary(obj);
-
- // Assert
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(5, kvp.Value); });
- }
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresSetOnly()
+ {
+ // Arrange
+ var obj = new SetterOnly() { CoolSetOnly = false };
- [Fact]
- public void CreateFromObject_CopiesPropertiesFromRegularType_WithIndexerProperty()
- {
- // Arrange
- var obj = new IndexerProperty();
+ // Act
+ var dict = new RouteValueDictionary(obj);
- // Act
- var dict = new RouteValueDictionary(obj);
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Empty(dict);
+ }
- // Assert
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Empty(dict);
- }
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType_IncludesInherited()
+ {
+ // Arrange
+ var obj = new Derived() { TotallySweetProperty = true, DerivedProperty = false };
+
+ // Act
+ var dict = new RouteValueDictionary(obj);
+
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("DerivedProperty", kvp.Key);
+ var value = Assert.IsType<bool>(kvp.Value);
+ Assert.False(value);
+ },
+ kvp =>
+ {
+ Assert.Equal("TotallySweetProperty", kvp.Key);
+ var value = Assert.IsType<bool>(kvp.Value);
+ Assert.True(value);
+ });
+ }
- [Fact]
- public void CreateFromObject_MixedCaseThrows()
- {
- // Arrange
- var obj = new { controller = "Home", Controller = "Home" };
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType_WithHiddenProperty()
+ {
+ // Arrange
+ var obj = new DerivedHiddenProperty() { DerivedProperty = 5 };
+
+ // Act
+ var dict = new RouteValueDictionary(obj);
+
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(5, kvp.Value); });
+ }
- var message =
- $"The type '{obj.GetType().FullName}' defines properties 'controller' and 'Controller' which differ " +
- $"only by casing. This is not supported by {nameof(RouteValueDictionary)} which uses " +
- $"case-insensitive comparisons.";
+ [Fact]
+ public void CreateFromObject_CopiesPropertiesFromRegularType_WithIndexerProperty()
+ {
+ // Arrange
+ var obj = new IndexerProperty();
- // Act & Assert
- var exception = Assert.Throws<InvalidOperationException>(() =>
- {
- var dictionary = new RouteValueDictionary(obj);
- });
+ // Act
+ var dict = new RouteValueDictionary(obj);
- // Ignoring case to make sure we're not testing reflection's ordering.
- Assert.Equal(message, exception.Message, ignoreCase: true);
- }
+ // Assert
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Empty(dict);
+ }
- // Our comparer is hardcoded to be OrdinalIgnoreCase no matter what.
- [Fact]
- public void Comparer_IsOrdinalIgnoreCase()
- {
- // Arrange
- // Act
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void CreateFromObject_MixedCaseThrows()
+ {
+ // Arrange
+ var obj = new { controller = "Home", Controller = "Home" };
- // Assert
- Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer);
- }
+ var message =
+ $"The type '{obj.GetType().FullName}' defines properties 'controller' and 'Controller' which differ " +
+ $"only by casing. This is not supported by {nameof(RouteValueDictionary)} which uses " +
+ $"case-insensitive comparisons.";
- // Our comparer is hardcoded to be IsReadOnly==false no matter what.
- [Fact]
- public void IsReadOnly_False()
+ // Act & Assert
+ var exception = Assert.Throws<InvalidOperationException>(() =>
{
- // Arrange
- var dict = new RouteValueDictionary();
+ var dictionary = new RouteValueDictionary(obj);
+ });
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).IsReadOnly;
+ // Ignoring case to make sure we're not testing reflection's ordering.
+ Assert.Equal(message, exception.Message, ignoreCase: true);
+ }
- // Assert
- Assert.False(result);
- }
+ // Our comparer is hardcoded to be OrdinalIgnoreCase no matter what.
+ [Fact]
+ public void Comparer_IsOrdinalIgnoreCase()
+ {
+ // Arrange
+ // Act
+ var dict = new RouteValueDictionary();
- [Fact]
- public void IndexGet_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ // Assert
+ Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer);
+ }
- // Act
- var value = dict[""];
+ // Our comparer is hardcoded to be IsReadOnly==false no matter what.
+ [Fact]
+ public void IsReadOnly_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Assert
- Assert.Null(value);
- }
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).IsReadOnly;
- [Fact]
- public void IndexGet_EmptyStorage_ReturnsNull()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ // Assert
+ Assert.False(result);
+ }
- // Act
- var value = dict["key"];
+ [Fact]
+ public void IndexGet_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Assert
- Assert.Null(value);
- }
+ // Act
+ var value = dict[""];
- [Fact]
- public void IndexGet_PropertyStorage_NoMatch_ReturnsNull()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { age = 30 });
+ // Assert
+ Assert.Null(value);
+ }
- // Act
- var value = dict["key"];
+ [Fact]
+ public void IndexGet_EmptyStorage_ReturnsNull()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Assert
- Assert.Null(value);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Act
+ var value = dict["key"];
- [Fact]
- public void IndexGet_PropertyStorage_Match_ReturnsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ // Assert
+ Assert.Null(value);
+ }
- // Act
- var value = dict["key"];
+ [Fact]
+ public void IndexGet_PropertyStorage_NoMatch_ReturnsNull()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { age = 30 });
- // Assert
- Assert.Equal("value", value);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Act
+ var value = dict["key"];
- [Fact]
- public void IndexGet_PropertyStorage_MatchIgnoreCase_ReturnsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ // Assert
+ Assert.Null(value);
+ Assert.NotNull(dict._propertyStorage);
+ }
- // Act
- var value = dict["kEy"];
+ [Fact]
+ public void IndexGet_PropertyStorage_Match_ReturnsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Assert
- Assert.Equal("value", value);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Act
+ var value = dict["key"];
- [Fact]
- public void IndexGet_ArrayStorage_NoMatch_ReturnsNull()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ // Assert
+ Assert.Equal("value", value);
+ Assert.NotNull(dict._propertyStorage);
+ }
+
+ [Fact]
+ public void IndexGet_PropertyStorage_MatchIgnoreCase_ReturnsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
+
+ // Act
+ var value = dict["kEy"];
+
+ // Assert
+ Assert.Equal("value", value);
+ Assert.NotNull(dict._propertyStorage);
+ }
+
+ [Fact]
+ public void IndexGet_ArrayStorage_NoMatch_ReturnsNull()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "age", 30 },
};
- // Act
- var value = dict["key"];
+ // Act
+ var value = dict["key"];
- // Assert
- Assert.Null(value);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Null(value);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexGet_ListStorage_Match_ReturnsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void IndexGet_ListStorage_Match_ReturnsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var value = dict["key"];
+ // Act
+ var value = dict["key"];
- // Assert
- Assert.Equal("value", value);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal("value", value);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var value = dict["kEy"];
+ // Act
+ var value = dict["kEy"];
- // Assert
- Assert.Equal("value", value);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal("value", value);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void IndexSet_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- dict[""] = "foo";
+ // Act
+ dict[""] = "foo";
- // Assert
- Assert.Equal("foo", dict[""]);
- }
+ // Assert
+ Assert.Equal("foo", dict[""]);
+ }
- [Fact]
- public void IndexSet_EmptyStorage_UpgradesToList()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void IndexSet_EmptyStorage_UpgradesToList()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- dict["key"] = "value";
+ // Act
+ dict["key"] = "value";
- // Assert
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_PropertyStorage_NoMatch_AddsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { age = 30 });
-
- // Act
- dict["key"] = "value";
-
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ [Fact]
+ public void IndexSet_PropertyStorage_NoMatch_AddsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { age = 30 });
+
+ // Act
+ dict["key"] = "value";
+
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_PropertyStorage_Match_SetsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void IndexSet_PropertyStorage_Match_SetsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- dict["key"] = "value";
+ // Act
+ dict["key"] = "value";
- // Assert
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_PropertyStorage_MatchIgnoreCase_SetsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void IndexSet_PropertyStorage_MatchIgnoreCase_SetsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- dict["kEy"] = "value";
+ // Act
+ dict["kEy"] = "value";
- // Assert
- Assert.Collection(dict, kvp => { Assert.Equal("kEy", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(dict, kvp => { Assert.Equal("kEy", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_ListStorage_NoMatch_AddsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void IndexSet_ListStorage_NoMatch_AddsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "age", 30 },
};
- // Act
- dict["key"] = "value";
+ // Act
+ dict["key"] = "value";
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_ListStorage_Match_SetsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void IndexSet_ListStorage_Match_SetsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- dict["key"] = "value";
+ // Act
+ dict["key"] = "value";
- // Assert
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- dict["key"] = "value";
+ // Act
+ dict["key"] = "value";
- // Assert
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Count_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Count_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var count = dict.Count;
+ // Act
+ var count = dict.Count;
- // Assert
- Assert.Equal(0, count);
- }
+ // Assert
+ Assert.Equal(0, count);
+ }
- [Fact]
- public void Count_PropertyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value", });
+ [Fact]
+ public void Count_PropertyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value", });
- // Act
- var count = dict.Count;
+ // Act
+ var count = dict.Count;
- // Assert
- Assert.Equal(1, count);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Assert
+ Assert.Equal(1, count);
+ Assert.NotNull(dict._propertyStorage);
+ }
- [Fact]
- public void Count_ListStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Count_ListStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var count = dict.Count;
+ // Act
+ var count = dict.Count;
- // Assert
- Assert.Equal(1, count);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal(1, count);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Keys_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Keys_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var keys = dict.Keys;
+ // Act
+ var keys = dict.Keys;
- // Assert
- Assert.Empty(keys);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Empty(keys);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Keys_PropertyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value", });
+ [Fact]
+ public void Keys_PropertyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value", });
- // Act
- var keys = dict.Keys;
+ // Act
+ var keys = dict.Keys;
- // Assert
- Assert.Equal(new[] { "key" }, keys);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal(new[] { "key" }, keys);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Keys_ListStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Keys_ListStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var keys = dict.Keys;
+ // Act
+ var keys = dict.Keys;
- // Assert
- Assert.Equal(new[] { "key" }, keys);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal(new[] { "key" }, keys);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Values_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Values_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var values = dict.Values;
+ // Act
+ var values = dict.Values;
- // Assert
- Assert.Empty(values);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Empty(values);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Values_PropertyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value", });
+ [Fact]
+ public void Values_PropertyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value", });
- // Act
- var values = dict.Values;
+ // Act
+ var values = dict.Values;
- // Assert
- Assert.Equal(new object[] { "value" }, values);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal(new object[] { "value" }, values);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Values_ListStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Values_ListStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var values = dict.Values;
+ // Act
+ var values = dict.Values;
- // Assert
- Assert.Equal(new object[] { "value" }, values);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Equal(new object[] { "value" }, values);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Add_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Add_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- dict.Add("key", "value");
+ // Act
+ dict.Add("key", "value");
- // Assert
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Add_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Add_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- dict.Add("", "foo");
+ // Act
+ dict.Add("", "foo");
- // Assert
- Assert.Equal("foo", dict[""]);
- }
+ // Assert
+ Assert.Equal("foo", dict[""]);
+ }
- [Fact]
- public void Add_PropertyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { age = 30 });
-
- // Act
- dict.Add("key", "value");
-
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
-
- // The upgrade from property -> array should make space for at least 4 entries
- Assert.Collection(
- dict._arrayStorage,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("age", 30), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp));
- }
+ [Fact]
+ public void Add_PropertyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { age = 30 });
+
+ // Act
+ dict.Add("key", "value");
+
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+
+ // The upgrade from property -> array should make space for at least 4 entries
+ Assert.Collection(
+ dict._arrayStorage,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("age", 30), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp));
+ }
- [Fact]
- public void Add_ListStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Add_ListStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "age", 30 },
};
- // Act
- dict.Add("key", "value");
+ // Act
+ dict.Add("key", "value");
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Add_DuplicateKey()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Add_DuplicateKey()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var message = $"An element with the key 'key' already exists in the {nameof(RouteValueDictionary)}";
+ var message = $"An element with the key 'key' already exists in the {nameof(RouteValueDictionary)}";
- // Act & Assert
- ExceptionAssert.ThrowsArgument(() => dict.Add("key", "value2"), "key", message);
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(() => dict.Add("key", "value2"), "key", message);
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Add_DuplicateKey_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Add_DuplicateKey_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var message = $"An element with the key 'kEy' already exists in the {nameof(RouteValueDictionary)}";
+ var message = $"An element with the key 'kEy' already exists in the {nameof(RouteValueDictionary)}";
- // Act & Assert
- ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message);
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message);
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Add_KeyValuePair()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Add_KeyValuePair()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "age", 30 },
};
- // Act
- ((ICollection<KeyValuePair<string, object?>>)dict).Add(new KeyValuePair<string, object?>("key", "value"));
+ // Act
+ ((ICollection<KeyValuePair<string, object?>>)dict).Add(new KeyValuePair<string, object?>("key", "value"));
- // Assert
- Assert.Collection(
- dict.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
- kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.Collection(
+ dict.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); },
+ kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Clear_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Clear_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- dict.Clear();
+ // Act
+ dict.Clear();
- // Assert
- Assert.Empty(dict);
- }
+ // Assert
+ Assert.Empty(dict);
+ }
- [Fact]
- public void Clear_PropertyStorage_AlreadyEmpty()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { });
+ [Fact]
+ public void Clear_PropertyStorage_AlreadyEmpty()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { });
- // Act
- dict.Clear();
+ // Act
+ dict.Clear();
- // Assert
- Assert.Empty(dict);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- }
+ // Assert
+ Assert.Empty(dict);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ }
- [Fact]
- public void Clear_PropertyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Clear_PropertyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- dict.Clear();
+ // Act
+ dict.Clear();
- // Assert
- Assert.Empty(dict);
- Assert.Null(dict._propertyStorage);
- Assert.Empty(dict._arrayStorage);
- }
+ // Assert
+ Assert.Empty(dict);
+ Assert.Null(dict._propertyStorage);
+ Assert.Empty(dict._arrayStorage);
+ }
- [Fact]
- public void Clear_ListStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Clear_ListStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- dict.Clear();
+ // Act
+ dict.Clear();
- // Assert
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Null(dict._propertyStorage);
- }
+ // Assert
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Null(dict._propertyStorage);
+ }
- [Fact]
- public void Contains_ListStorage_KeyValuePair_True()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Contains_ListStorage_KeyValuePair_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("key", "value");
+ var input = new KeyValuePair<string, object?>("key", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.True(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Contains_ListStory_KeyValuePair_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Contains_ListStory_KeyValuePair_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("KEY", "value");
+ var input = new KeyValuePair<string, object?>("KEY", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.True(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Contains_ListStorage_KeyValuePair_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Contains_ListStorage_KeyValuePair_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("other", "value");
+ var input = new KeyValuePair<string, object?>("other", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.False(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- // Value comparisons use the default equality comparer.
- [Fact]
- public void Contains_ListStorage_KeyValuePair_False_ValueComparisonIsDefault()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ // Value comparisons use the default equality comparer.
+ [Fact]
+ public void Contains_ListStorage_KeyValuePair_False_ValueComparisonIsDefault()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("key", "valUE");
+ var input = new KeyValuePair<string, object?>("key", "valUE");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.False(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Contains_PropertyStorage_KeyValuePair_True()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Contains_PropertyStorage_KeyValuePair_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- var input = new KeyValuePair<string, object?>("key", "value");
+ var input = new KeyValuePair<string, object?>("key", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.True(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Collection(
- dict,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
- }
+ // Assert
+ Assert.True(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Collection(
+ dict,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
+ }
- [Fact]
- public void Contains_PropertyStory_KeyValuePair_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Contains_PropertyStory_KeyValuePair_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- var input = new KeyValuePair<string, object?>("KEY", "value");
+ var input = new KeyValuePair<string, object?>("KEY", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.True(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Collection(
- dict,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
- }
+ // Assert
+ Assert.True(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Collection(
+ dict,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
+ }
- [Fact]
- public void Contains_PropertyStorage_KeyValuePair_False()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Contains_PropertyStorage_KeyValuePair_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- var input = new KeyValuePair<string, object?>("other", "value");
+ var input = new KeyValuePair<string, object?>("other", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.False(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Collection(
- dict,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
- }
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Collection(
+ dict,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
+ }
- // Value comparisons use the default equality comparer.
- [Fact]
- public void Contains_PropertyStorage_KeyValuePair_False_ValueComparisonIsDefault()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ // Value comparisons use the default equality comparer.
+ [Fact]
+ public void Contains_PropertyStorage_KeyValuePair_False_ValueComparisonIsDefault()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- var input = new KeyValuePair<string, object?>("key", "valUE");
+ var input = new KeyValuePair<string, object?>("key", "valUE");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Contains(input);
- // Assert
- Assert.False(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- Assert.Collection(
- dict,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
- }
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ Assert.Collection(
+ dict,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
+ }
- [Fact]
- public void ContainsKey_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void ContainsKey_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.ContainsKey("key");
+ // Act
+ var result = dict.ContainsKey("key");
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void ContainsKey_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void ContainsKey_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.ContainsKey("");
+ // Act
+ var result = dict.ContainsKey("");
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void ContainsKey_PropertyStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void ContainsKey_PropertyStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.ContainsKey("other");
+ // Act
+ var result = dict.ContainsKey("other");
- // Assert
- Assert.False(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- }
+ // Assert
+ Assert.False(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ }
- [Fact]
- public void ContainsKey_PropertyStorage_True()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void ContainsKey_PropertyStorage_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.ContainsKey("key");
+ // Act
+ var result = dict.ContainsKey("key");
- // Assert
- Assert.True(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- }
+ // Assert
+ Assert.True(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ }
- [Fact]
- public void ContainsKey_PropertyStorage_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void ContainsKey_PropertyStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.ContainsKey("kEy");
+ // Act
+ var result = dict.ContainsKey("kEy");
- // Assert
- Assert.True(result);
- Assert.NotNull(dict._propertyStorage);
- AssertEmptyArrayStorage(dict);
- }
+ // Assert
+ Assert.True(result);
+ Assert.NotNull(dict._propertyStorage);
+ AssertEmptyArrayStorage(dict);
+ }
- [Fact]
- public void ContainsKey_ListStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void ContainsKey_ListStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.ContainsKey("other");
+ // Act
+ var result = dict.ContainsKey("other");
- // Assert
- Assert.False(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void ContainsKey_ListStorage_True()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void ContainsKey_ListStorage_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.ContainsKey("key");
+ // Act
+ var result = dict.ContainsKey("key");
- // Assert
- Assert.True(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void ContainsKey_ListStorage_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void ContainsKey_ListStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.ContainsKey("kEy");
+ // Act
+ var result = dict.ContainsKey("kEy");
- // Assert
- Assert.True(result);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void CopyTo()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void CopyTo()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var array = new KeyValuePair<string, object?>[2];
+ var array = new KeyValuePair<string, object?>[2];
- // Act
- ((ICollection<KeyValuePair<string, object?>>)dict).CopyTo(array, 1);
+ // Act
+ ((ICollection<KeyValuePair<string, object?>>)dict).CopyTo(array, 1);
- // Assert
- Assert.Equal(
- new KeyValuePair<string, object?>[]
- {
+ // Assert
+ Assert.Equal(
+ new KeyValuePair<string, object?>[]
+ {
default(KeyValuePair<string, object?>),
new KeyValuePair<string, object?>("key", "value")
- },
- array);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ },
+ array);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyValuePair_True()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyValuePair_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("key", "value");
+ var input = new KeyValuePair<string, object?>("key", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
- // Assert
- Assert.True(result);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyValuePair_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyValuePair_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("KEY", "value");
+ var input = new KeyValuePair<string, object?>("KEY", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
- // Assert
- Assert.True(result);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyValuePair_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyValuePair_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("other", "value");
+ var input = new KeyValuePair<string, object?>("other", "value");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
- // Assert
- Assert.False(result);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- // Value comparisons use the default equality comparer.
- [Fact]
- public void Remove_KeyValuePair_False_ValueComparisonIsDefault()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ // Value comparisons use the default equality comparer.
+ [Fact]
+ public void Remove_KeyValuePair_False_ValueComparisonIsDefault()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- var input = new KeyValuePair<string, object?>("key", "valUE");
+ var input = new KeyValuePair<string, object?>("key", "valUE");
- // Act
- var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
+ // Act
+ var result = ((ICollection<KeyValuePair<string, object?>>)dict).Remove(input);
- // Assert
- Assert.False(result);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Remove_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.Remove("key");
+ // Act
+ var result = dict.Remove("key");
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void Remove_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Remove_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.Remove("");
+ // Act
+ var result = dict.Remove("");
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void Remove_PropertyStorage_Empty()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { });
+ [Fact]
+ public void Remove_PropertyStorage_Empty()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { });
- // Act
- var result = dict.Remove("other");
+ // Act
+ var result = dict.Remove("other");
- // Assert
- Assert.False(result);
- Assert.Empty(dict);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Empty(dict);
+ Assert.NotNull(dict._propertyStorage);
+ }
- [Fact]
- public void Remove_PropertyStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Remove_PropertyStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.Remove("other");
+ // Act
+ var result = dict.Remove("other");
- // Assert
- Assert.False(result);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_PropertyStorage_True()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Remove_PropertyStorage_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.Remove("key");
+ // Act
+ var result = dict.Remove("key");
- // Assert
- Assert.True(result);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_PropertyStorage_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Remove_PropertyStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.Remove("kEy");
+ // Act
+ var result = dict.Remove("kEy");
- // Assert
- Assert.True(result);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_ListStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_ListStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.Remove("other");
+ // Act
+ var result = dict.Remove("other");
- // Assert
- Assert.False(result);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_ListStorage_True()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_ListStorage_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.Remove("key");
+ // Act
+ var result = dict.Remove("key");
- // Assert
- Assert.True(result);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_ListStorage_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_ListStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.Remove("kEy");
+ // Act
+ var result = dict.Remove("kEy");
- // Assert
- Assert.True(result);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Remove_KeyAndOutValue_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.Remove("key", out var removedValue);
+ // Act
+ var result = dict.Remove("key", out var removedValue);
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(removedValue);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void Remove_KeyAndOutValue_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.Remove("", out var removedValue);
+ // Act
+ var result = dict.Remove("", out var removedValue);
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(removedValue);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_Empty()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { });
+ [Fact]
+ public void Remove_KeyAndOutValue_PropertyStorage_Empty()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { });
- // Act
- var result = dict.Remove("other", out var removedValue);
+ // Act
+ var result = dict.Remove("other", out var removedValue);
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- Assert.Empty(dict);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(removedValue);
+ Assert.Empty(dict);
+ Assert.NotNull(dict._propertyStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void Remove_KeyAndOutValue_PropertyStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.Remove("other", out var removedValue);
+ // Act
+ var result = dict.Remove("other", out var removedValue);
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(removedValue);
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_True()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary(new { key = value });
-
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ [Fact]
+ public void Remove_KeyAndOutValue_PropertyStorage_True()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary(new { key = value });
+
+ // Act
+ var result = dict.Remove("key", out var removedValue);
+
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_PropertyStorage_True_CaseInsensitive()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary(new { key = value });
-
- // Act
- var result = dict.Remove("kEy", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ [Fact]
+ public void Remove_KeyAndOutValue_PropertyStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary(new { key = value });
+
+ // Act
+ var result = dict.Remove("kEy", out var removedValue);
+
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyAndOutValue_ListStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.Remove("other", out var removedValue);
+ // Act
+ var result = dict.Remove("other", out var removedValue);
- // Assert
- Assert.False(result);
- Assert.Null(removedValue);
- Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(removedValue);
+ Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); });
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_True()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyAndOutValue_ListStorage_True()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary()
{
{ "key", value }
};
- // Act
- var result = dict.Remove("key", out var removedValue);
+ // Act
+ var result = dict.Remove("key", out var removedValue);
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyAndOutValue_ListStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary()
{
{ "key", value }
};
- // Act
- var result = dict.Remove("kEy", out var removedValue);
+ // Act
+ var result = dict.Remove("kEy", out var removedValue);
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Empty(dict);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Empty(dict);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_KeyExists_First()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyAndOutValue_ListStorage_KeyExists_First()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary()
{
{ "key", value },
{ "other", 5 },
{ "dotnet", "rocks" }
};
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Equal(2, dict.Count);
- Assert.False(dict.ContainsKey("key"));
- Assert.True(dict.ContainsKey("other"));
- Assert.True(dict.ContainsKey("dotnet"));
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Act
+ var result = dict.Remove("key", out var removedValue);
+
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Equal(2, dict.Count);
+ Assert.False(dict.ContainsKey("key"));
+ Assert.True(dict.ContainsKey("other"));
+ Assert.True(dict.ContainsKey("dotnet"));
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyAndOutValue_ListStorage_KeyExists_Middle()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary()
{
{ "other", 5 },
{ "key", value },
{ "dotnet", "rocks" }
};
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Equal(2, dict.Count);
- Assert.False(dict.ContainsKey("key"));
- Assert.True(dict.ContainsKey("other"));
- Assert.True(dict.ContainsKey("dotnet"));
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Act
+ var result = dict.Remove("key", out var removedValue);
+
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Equal(2, dict.Count);
+ Assert.False(dict.ContainsKey("key"));
+ Assert.True(dict.ContainsKey("other"));
+ Assert.True(dict.ContainsKey("dotnet"));
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last()
- {
- // Arrange
- object value = "value";
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void Remove_KeyAndOutValue_ListStorage_KeyExists_Last()
+ {
+ // Arrange
+ object value = "value";
+ var dict = new RouteValueDictionary()
{
{ "other", 5 },
{ "dotnet", "rocks" },
{ "key", value }
};
- // Act
- var result = dict.Remove("key", out var removedValue);
-
- // Assert
- Assert.True(result);
- Assert.Same(value, removedValue);
- Assert.Equal(2, dict.Count);
- Assert.False(dict.ContainsKey("key"));
- Assert.True(dict.ContainsKey("other"));
- Assert.True(dict.ContainsKey("dotnet"));
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Act
+ var result = dict.Remove("key", out var removedValue);
+
+ // Assert
+ Assert.True(result);
+ Assert.Same(value, removedValue);
+ Assert.Equal(2, dict.Count);
+ Assert.False(dict.ContainsKey("key"));
+ Assert.True(dict.ContainsKey("other"));
+ Assert.True(dict.ContainsKey("dotnet"));
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void TryAdd_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void TryAdd_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.TryAdd("", "foo");
+ // Act
+ var result = dict.TryAdd("", "foo");
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void TryAdd_PropertyStorage_KeyDoesNotExist_ConvertsPropertyStorageToArrayStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value", });
-
- // Act
- var result = dict.TryAdd("otherKey", "value");
-
- // Assert
- Assert.True(result);
- Assert.Null(dict._propertyStorage);
- Assert.Collection(
- dict._arrayStorage,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("otherKey", "value"), kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp));
- }
+ [Fact]
+ public void TryAdd_PropertyStorage_KeyDoesNotExist_ConvertsPropertyStorageToArrayStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value", });
+
+ // Act
+ var result = dict.TryAdd("otherKey", "value");
+
+ // Assert
+ Assert.True(result);
+ Assert.Null(dict._propertyStorage);
+ Assert.Collection(
+ dict._arrayStorage,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("otherKey", "value"), kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp));
+ }
- [Fact]
- public void TryAdd_PropertyStory_KeyExist_DoesNotConvertPropertyStorageToArrayStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value", });
-
- // Act
- var result = dict.TryAdd("key", "value");
-
- // Assert
- Assert.False(result);
- AssertEmptyArrayStorage(dict);
- Assert.NotNull(dict._propertyStorage);
- Assert.Collection(
- dict,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
- }
+ [Fact]
+ public void TryAdd_PropertyStory_KeyExist_DoesNotConvertPropertyStorageToArrayStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value", });
+
+ // Act
+ var result = dict.TryAdd("key", "value");
+
+ // Assert
+ Assert.False(result);
+ AssertEmptyArrayStorage(dict);
+ Assert.NotNull(dict._propertyStorage);
+ Assert.Collection(
+ dict,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp));
+ }
- [Fact]
- public void TryAdd_EmptyStorage_CanAdd()
- {
- // Arrange
- var dict = new RouteValueDictionary();
-
- // Act
- var result = dict.TryAdd("key", "value");
-
- // Assert
- Assert.True(result);
- Assert.Collection(
- dict._arrayStorage,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp));
- }
+ [Fact]
+ public void TryAdd_EmptyStorage_CanAdd()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
+
+ // Act
+ var result = dict.TryAdd("key", "value");
+
+ // Assert
+ Assert.True(result);
+ Assert.Collection(
+ dict._arrayStorage,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key", "value"), kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp));
+ }
- [Fact]
- public void TryAdd_ArrayStorage_CanAdd()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void TryAdd_ArrayStorage_CanAdd()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key0", "value0" },
};
- // Act
- var result = dict.TryAdd("key1", "value1");
-
- // Assert
- Assert.True(result);
- Assert.Collection(
- dict._arrayStorage,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key0", "value0"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key1", "value1"), kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp));
- }
+ // Act
+ var result = dict.TryAdd("key1", "value1");
+
+ // Assert
+ Assert.True(result);
+ Assert.Collection(
+ dict._arrayStorage,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key0", "value0"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key1", "value1"), kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp));
+ }
- [Fact]
- public void TryAdd_ArrayStorage_CanAddWithResize()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void TryAdd_ArrayStorage_CanAddWithResize()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key0", "value0" },
{ "key1", "value1" },
@@ -1821,261 +1821,261 @@ namespace Microsoft.AspNetCore.Routing.Tests
{ "key3", "value3" },
};
- // Act
- var result = dict.TryAdd("key4", "value4");
-
- // Assert
- Assert.True(result);
- Assert.Collection(
- dict._arrayStorage,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key0", "value0"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key1", "value1"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key2", "value2"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key3", "value3"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key4", "value4"), kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp));
- }
+ // Act
+ var result = dict.TryAdd("key4", "value4");
+
+ // Assert
+ Assert.True(result);
+ Assert.Collection(
+ dict._arrayStorage,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key0", "value0"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key1", "value1"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key2", "value2"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key3", "value3"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key4", "value4"), kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp));
+ }
- [Fact]
- public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void TryAdd_ArrayStorage_DoesNotAddWhenKeyIsPresent()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key0", "value0" },
};
- // Act
- var result = dict.TryAdd("key0", "value1");
-
- // Assert
- Assert.False(result);
- Assert.Collection(
- dict._arrayStorage,
- kvp => Assert.Equal(new KeyValuePair<string, object?>("key0", "value0"), kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp),
- kvp => Assert.Equal(default, kvp));
- }
+ // Act
+ var result = dict.TryAdd("key0", "value1");
+
+ // Assert
+ Assert.False(result);
+ Assert.Collection(
+ dict._arrayStorage,
+ kvp => Assert.Equal(new KeyValuePair<string, object?>("key0", "value0"), kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp),
+ kvp => Assert.Equal(default, kvp));
+ }
- [Fact]
- public void TryGetValue_EmptyStorage()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void TryGetValue_EmptyStorage()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.TryGetValue("key", out var value);
+ // Act
+ var result = dict.TryGetValue("key", out var value);
- // Assert
- Assert.False(result);
- Assert.Null(value);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(value);
+ }
- [Fact]
- public void TryGetValue_EmptyStringIsAllowed()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void TryGetValue_EmptyStringIsAllowed()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act
- var result = dict.TryGetValue("", out var value);
+ // Act
+ var result = dict.TryGetValue("", out var value);
- // Assert
- Assert.False(result);
- Assert.Null(value);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(value);
+ }
- [Fact]
- public void TryGetValue_PropertyStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void TryGetValue_PropertyStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.TryGetValue("other", out var value);
+ // Act
+ var result = dict.TryGetValue("other", out var value);
- // Assert
- Assert.False(result);
- Assert.Null(value);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(value);
+ Assert.NotNull(dict._propertyStorage);
+ }
- [Fact]
- public void TryGetValue_PropertyStorage_True()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void TryGetValue_PropertyStorage_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.TryGetValue("key", out var value);
+ // Act
+ var result = dict.TryGetValue("key", out var value);
- // Assert
- Assert.True(result);
- Assert.Equal("value", value);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Equal("value", value);
+ Assert.NotNull(dict._propertyStorage);
+ }
- [Fact]
- public void TryGetValue_PropertyStorage_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary(new { key = "value" });
+ [Fact]
+ public void TryGetValue_PropertyStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary(new { key = "value" });
- // Act
- var result = dict.TryGetValue("kEy", out var value);
+ // Act
+ var result = dict.TryGetValue("kEy", out var value);
- // Assert
- Assert.True(result);
- Assert.Equal("value", value);
- Assert.NotNull(dict._propertyStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Equal("value", value);
+ Assert.NotNull(dict._propertyStorage);
+ }
- [Fact]
- public void TryGetValue_ListStorage_False()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void TryGetValue_ListStorage_False()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.TryGetValue("other", out var value);
+ // Act
+ var result = dict.TryGetValue("other", out var value);
- // Assert
- Assert.False(result);
- Assert.Null(value);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.False(result);
+ Assert.Null(value);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void TryGetValue_ListStorage_True()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void TryGetValue_ListStorage_True()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.TryGetValue("key", out var value);
+ // Act
+ var result = dict.TryGetValue("key", out var value);
- // Assert
- Assert.True(result);
- Assert.Equal("value", value);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Equal("value", value);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void TryGetValue_ListStorage_True_CaseInsensitive()
- {
- // Arrange
- var dict = new RouteValueDictionary()
+ [Fact]
+ public void TryGetValue_ListStorage_True_CaseInsensitive()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary()
{
{ "key", "value" },
};
- // Act
- var result = dict.TryGetValue("kEy", out var value);
+ // Act
+ var result = dict.TryGetValue("kEy", out var value);
- // Assert
- Assert.True(result);
- Assert.Equal("value", value);
- Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- }
+ // Assert
+ Assert.True(result);
+ Assert.Equal("value", value);
+ Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ }
- [Fact]
- public void ListStorage_DynamicallyAdjustsCapacity()
- {
- // Arrange
- var dict = new RouteValueDictionary();
+ [Fact]
+ public void ListStorage_DynamicallyAdjustsCapacity()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
- // Act 1
- dict.Add("key", "value");
+ // Act 1
+ dict.Add("key", "value");
- // Assert 1
- var storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Equal(4, storage.Length);
+ // Assert 1
+ var storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Equal(4, storage.Length);
- // Act 2
- dict.Add("key2", "value2");
- dict.Add("key3", "value3");
- dict.Add("key4", "value4");
- dict.Add("key5", "value5");
+ // Act 2
+ dict.Add("key2", "value2");
+ dict.Add("key3", "value3");
+ dict.Add("key4", "value4");
+ dict.Add("key5", "value5");
- // Assert 2
- storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Equal(8, storage.Length);
- }
+ // Assert 2
+ storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Equal(8, storage.Length);
+ }
- [Fact]
- public void ListStorage_RemoveAt_RearrangesInnerArray()
- {
- // Arrange
- var dict = new RouteValueDictionary();
- dict.Add("key", "value");
- dict.Add("key2", "value2");
- dict.Add("key3", "value3");
-
- // Assert 1
- var storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Equal(3, dict.Count);
-
- // Act
- dict.Remove("key2");
-
- // Assert 2
- storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
- Assert.Equal(2, dict.Count);
- Assert.Equal("key", storage[0].Key);
- Assert.Equal("value", storage[0].Value);
- Assert.Equal("key3", storage[1].Key);
- Assert.Equal("value3", storage[1].Value);
- }
+ [Fact]
+ public void ListStorage_RemoveAt_RearrangesInnerArray()
+ {
+ // Arrange
+ var dict = new RouteValueDictionary();
+ dict.Add("key", "value");
+ dict.Add("key2", "value2");
+ dict.Add("key3", "value3");
+
+ // Assert 1
+ var storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Equal(3, dict.Count);
+
+ // Act
+ dict.Remove("key2");
+
+ // Assert 2
+ storage = Assert.IsType<KeyValuePair<string, object?>[]>(dict._arrayStorage);
+ Assert.Equal(2, dict.Count);
+ Assert.Equal("key", storage[0].Key);
+ Assert.Equal("value", storage[0].Value);
+ Assert.Equal("key3", storage[1].Key);
+ Assert.Equal("value3", storage[1].Value);
+ }
- [Fact]
- public void FromArray_TakesOwnershipOfArray()
+ [Fact]
+ public void FromArray_TakesOwnershipOfArray()
+ {
+ // Arrange
+ var array = new KeyValuePair<string, object?>[]
{
- // Arrange
- var array = new KeyValuePair<string, object?>[]
- {
new KeyValuePair<string, object?>("a", 0),
new KeyValuePair<string, object?>("b", 1),
new KeyValuePair<string, object?>("c", 2),
- };
+ };
- var dictionary = RouteValueDictionary.FromArray(array);
+ var dictionary = RouteValueDictionary.FromArray(array);
- // Act - modifying the array should modify the dictionary
- array[0] = new KeyValuePair<string, object?>("aa", 10);
+ // Act - modifying the array should modify the dictionary
+ array[0] = new KeyValuePair<string, object?>("aa", 10);
- // Assert
- Assert.Equal(3, dictionary.Count);
- Assert.Equal(10, dictionary["aa"]);
- }
+ // Assert
+ Assert.Equal(3, dictionary.Count);
+ Assert.Equal(10, dictionary["aa"]);
+ }
- [Fact]
- public void FromArray_EmptyArray()
- {
- // Arrange
- var array = Array.Empty<KeyValuePair<string, object?>>();
+ [Fact]
+ public void FromArray_EmptyArray()
+ {
+ // Arrange
+ var array = Array.Empty<KeyValuePair<string, object?>>();
- // Act
- var dictionary = RouteValueDictionary.FromArray(array);
+ // Act
+ var dictionary = RouteValueDictionary.FromArray(array);
- // Assert
- Assert.Empty(dictionary);
- }
+ // Assert
+ Assert.Empty(dictionary);
+ }
- [Fact]
- public void FromArray_RemovesGapsInArray()
+ [Fact]
+ public void FromArray_RemovesGapsInArray()
+ {
+ // Arrange
+ var array = new KeyValuePair<string, object?>[]
{
- // Arrange
- var array = new KeyValuePair<string, object?>[]
- {
new KeyValuePair<string, object?>(null!, null),
new KeyValuePair<string, object?>("a", 0),
new KeyValuePair<string, object?>(null!, null),
@@ -2084,16 +2084,16 @@ namespace Microsoft.AspNetCore.Routing.Tests
new KeyValuePair<string, object?>("c", 2),
new KeyValuePair<string, object?>("d", 3),
new KeyValuePair<string, object?>(null!, null),
- };
+ };
- // Act - calling From should modify the array
- var dictionary = RouteValueDictionary.FromArray(array);
+ // Act - calling From should modify the array
+ var dictionary = RouteValueDictionary.FromArray(array);
- // Assert
- Assert.Equal(4, dictionary.Count);
- Assert.Equal(
- new KeyValuePair<string, object?>[]
- {
+ // Assert
+ Assert.Equal(4, dictionary.Count);
+ Assert.Equal(
+ new KeyValuePair<string, object?>[]
+ {
new KeyValuePair<string, object?>("d", 3),
new KeyValuePair<string, object?>("a", 0),
new KeyValuePair<string, object?>("c", 2),
@@ -2102,72 +2102,71 @@ namespace Microsoft.AspNetCore.Routing.Tests
new KeyValuePair<string, object?>(null!, null),
new KeyValuePair<string, object?>(null!, null),
new KeyValuePair<string, object?>(null!, null),
- },
- array);
- }
+ },
+ array);
+ }
- private void AssertEmptyArrayStorage(RouteValueDictionary value)
- {
- Assert.Same(Array.Empty<KeyValuePair<string, object?>>(), value._arrayStorage);
- }
+ private void AssertEmptyArrayStorage(RouteValueDictionary value)
+ {
+ Assert.Same(Array.Empty<KeyValuePair<string, object?>>(), value._arrayStorage);
+ }
- private class RegularType
- {
- public bool IsAwesome { get; set; }
+ private class RegularType
+ {
+ public bool IsAwesome { get; set; }
- public int CoolnessFactor { get; set; }
- }
+ public int CoolnessFactor { get; set; }
+ }
- private class Visibility
- {
- private string? PrivateYo { get; set; }
+ private class Visibility
+ {
+ private string? PrivateYo { get; set; }
- internal int ItsInternalDealWithIt { get; set; }
+ internal int ItsInternalDealWithIt { get; set; }
- public bool IsPublic { get; set; }
- }
+ public bool IsPublic { get; set; }
+ }
- private class StaticProperty
- {
- public static bool IsStatic { get; set; }
- }
+ private class StaticProperty
+ {
+ public static bool IsStatic { get; set; }
+ }
- private class SetterOnly
- {
- private bool _coolSetOnly;
+ private class SetterOnly
+ {
+ private bool _coolSetOnly;
- public bool CoolSetOnly { set { _coolSetOnly = value; } }
- }
+ public bool CoolSetOnly { set { _coolSetOnly = value; } }
+ }
- private class Base
- {
- public bool DerivedProperty { get; set; }
- }
+ private class Base
+ {
+ public bool DerivedProperty { get; set; }
+ }
- private class Derived : Base
- {
- public bool TotallySweetProperty { get; set; }
- }
+ private class Derived : Base
+ {
+ public bool TotallySweetProperty { get; set; }
+ }
- private class DerivedHiddenProperty : Base
- {
- public new int DerivedProperty { get; set; }
- }
+ private class DerivedHiddenProperty : Base
+ {
+ public new int DerivedProperty { get; set; }
+ }
- private class IndexerProperty
+ private class IndexerProperty
+ {
+ public bool this[string key]
{
- public bool this[string key]
- {
- get { return false; }
- set { }
- }
+ get { return false; }
+ set { }
}
+ }
- private class Address
- {
- public string? City { get; set; }
+ private class Address
+ {
+ public string? City { get; set; }
- public string? State { get; set; }
- }
+ public string? State { get; set; }
}
}
diff --git a/src/Http/Http.Abstractions/test/UseExtensionsTests.cs b/src/Http/Http.Abstractions/test/UseExtensionsTests.cs
index 2fe3563e1f..3fc3aed617 100644
--- a/src/Http/Http.Abstractions/test/UseExtensionsTests.cs
+++ b/src/Http/Http.Abstractions/test/UseExtensionsTests.cs
@@ -6,75 +6,74 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+public class UseExtensionsTests
{
- public class UseExtensionsTests
+ [Fact]
+ public async Task UseCallsNextMiddleware()
{
- [Fact]
- public async Task UseCallsNextMiddleware()
+ // Arrange
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ var context = new DefaultHttpContext();
+ var firstCalled = false;
+ var secondCalled = false;
+ var lastCalled = false;
+
+ builder.Use((context, next) =>
+ {
+ firstCalled = true;
+ return next();
+ });
+ builder.Use((context, next) =>
+ {
+ Assert.True(firstCalled);
+ secondCalled = true;
+ return next(context);
+ });
+ builder.Run(context =>
{
- // Arrange
- var builder = new ApplicationBuilder(serviceProvider: null!);
- var context = new DefaultHttpContext();
- var firstCalled = false;
- var secondCalled = false;
- var lastCalled = false;
+ Assert.True(secondCalled);
+ lastCalled = true;
+ return Task.CompletedTask;
+ });
- builder.Use((context, next) =>
- {
- firstCalled = true;
- return next();
- });
- builder.Use((context, next) =>
- {
- Assert.True(firstCalled);
- secondCalled = true;
- return next(context);
- });
- builder.Run(context =>
- {
- Assert.True(secondCalled);
- lastCalled = true;
- return Task.CompletedTask;
- });
+ // Act
+ await builder.Build().Invoke(context);
- // Act
- await builder.Build().Invoke(context);
+ // Assert
+ Assert.True(firstCalled);
+ Assert.True(secondCalled);
+ Assert.True(lastCalled);
+ }
- // Assert
- Assert.True(firstCalled);
- Assert.True(secondCalled);
- Assert.True(lastCalled);
- }
+ [Fact]
+ public async Task ThrowFromMiddlewareFlowsBackToInvoke()
+ {
+ // Arrange
+ var builder = new ApplicationBuilder(serviceProvider: null!);
+ var context = new DefaultHttpContext();
+ var shouldThrow = true;
- [Fact]
- public async Task ThrowFromMiddlewareFlowsBackToInvoke()
+ builder.Use(async (context, next) =>
{
- // Arrange
- var builder = new ApplicationBuilder(serviceProvider: null!);
- var context = new DefaultHttpContext();
- var shouldThrow = true;
-
- builder.Use(async (context, next) =>
- {
- throw await Assert.ThrowsAsync<Exception>(() => next());
- });
- builder.Use(async (context, next) =>
- {
- throw await Assert.ThrowsAsync<Exception>(() => next(context));
- });
- builder.Run(context =>
+ throw await Assert.ThrowsAsync<Exception>(() => next());
+ });
+ builder.Use(async (context, next) =>
+ {
+ throw await Assert.ThrowsAsync<Exception>(() => next(context));
+ });
+ builder.Run(context =>
+ {
+ if (shouldThrow)
{
- if (shouldThrow)
- {
- throw new Exception("From Use");
- }
- return Task.CompletedTask;
- });
+ throw new Exception("From Use");
+ }
+ return Task.CompletedTask;
+ });
- // Act & Assert
- var ex = await Assert.ThrowsAsync<Exception>(() => builder.Build().Invoke(context));
- Assert.Equal("From Use", ex.Message);
- }
+ // Act & Assert
+ var ex = await Assert.ThrowsAsync<Exception>(() => builder.Build().Invoke(context));
+ Assert.Equal("From Use", ex.Message);
}
}
diff --git a/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs b/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs
index 0c89b00e32..5c2dab0b6e 100644
--- a/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs
+++ b/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs
@@ -9,368 +9,367 @@ using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class UseMiddlewareTest
{
- public class UseMiddlewareTest
+ [Fact]
+ public void UseMiddleware_WithNoParameters_ThrowsException()
{
- [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);
- }
+ 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_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_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_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_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_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_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_MultipleInvokeMethods_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_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_MultipleInvokeAsyncMethods_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_MultipleInvokeMethods_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_MultipleInvokeAndInvokeAsyncMethods_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 void UseMiddleware_MultipleInvokeAsyncMethods_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 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 UseMiddleware_MultipleInvokeAndInvokeAsyncMethods_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 void UseMiddlewareWithInvokeArg()
- {
- var builder = new ApplicationBuilder(new DummyServiceProvider());
- builder.UseMiddleware(typeof(MiddlewareInjectInvoke));
- var app = builder.Build();
- app(new DefaultHttpContext());
- }
+ [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 UseMiddlewareWithInvokeWithOutAndRefThrows()
- {
- var mockServiceProvider = new DummyServiceProvider();
- var builder = new ApplicationBuilder(mockServiceProvider);
- builder.UseMiddleware(typeof(MiddlewareInjectWithOutAndRefParams));
- var exception = Assert.Throws<NotSupportedException>(() => builder.Build());
- }
+ [Fact]
+ public void UseMiddlewareWithInvokeArg()
+ {
+ var builder = new ApplicationBuilder(new DummyServiceProvider());
+ builder.UseMiddleware(typeof(MiddlewareInjectInvoke));
+ var app = builder.Build();
+ app(new DefaultHttpContext());
+ }
- [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 void UseMiddlewareWithInvokeWithOutAndRefThrows()
+ {
+ var mockServiceProvider = new DummyServiceProvider();
+ var builder = new ApplicationBuilder(mockServiceProvider);
+ builder.UseMiddleware(typeof(MiddlewareInjectWithOutAndRefParams));
+ var exception = Assert.Throws<NotSupportedException>(() => builder.Build());
+ }
- [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 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 UseMiddlewareWithIMiddlewareThrowsIfMiddlewareFactoryCreateReturnsNull()
+ [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 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);
- }
+ 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 UseMiddlewareWithIMiddlewareWorks()
+ [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 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);
+ sp.AddService(typeof(IMiddlewareFactory), new BadMiddlewareFactory());
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;
- }
- }
+ Assert.Equal(
+ Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(
+ typeof(BadMiddlewareFactory),
+ typeof(Middleware)),
+ exception.Message);
+ }
- public class BasicMiddlewareFactory : IMiddlewareFactory
+ [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)
{
- public IMiddleware? Created { get; private set; }
- public IMiddleware? Released { get; private set; }
+ context.Items["before"] = true;
+ await next(context);
+ context.Items["after"] = true;
+ }
+ }
- public IMiddleware? Create(Type middlewareType)
- {
- Created = Activator.CreateInstance(middlewareType) as IMiddleware;
- return Created;
- }
+ public class BasicMiddlewareFactory : IMiddlewareFactory
+ {
+ public IMiddleware? Created { get; private set; }
+ public IMiddleware? Released { get; private set; }
- public void Release(IMiddleware middleware)
- {
- Released = middleware;
- }
+ public IMiddleware? Create(Type middlewareType)
+ {
+ Created = Activator.CreateInstance(middlewareType) as IMiddleware;
+ return Created;
}
- public class BadMiddlewareFactory : IMiddlewareFactory
+ public void Release(IMiddleware middleware)
{
- public IMiddleware? Create(Type middlewareType) => null;
-
- public void Release(IMiddleware middleware) { }
+ Released = middleware;
}
+ }
- private class DummyServiceProvider : IServiceProvider
- {
- private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
+ public class BadMiddlewareFactory : IMiddlewareFactory
+ {
+ public IMiddleware? Create(Type middlewareType) => null;
- public void AddService(Type type, object value) => _services[type] = value;
+ public void Release(IMiddleware middleware) { }
+ }
- public object? GetService(Type serviceType)
- {
- if (serviceType == typeof(IServiceProvider))
- {
- return this;
- }
-
- if (_services.TryGetValue(serviceType, out var value))
- {
- return value;
- }
- return null;
- }
- }
+ private class DummyServiceProvider : IServiceProvider
+ {
+ private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
- public class MiddlewareInjectWithOutAndRefParams
+ public void AddService(Type type, object value) => _services[type] = value;
+
+ public object? GetService(Type serviceType)
{
- public MiddlewareInjectWithOutAndRefParams(RequestDelegate next) { }
+ if (serviceType == typeof(IServiceProvider))
+ {
+ return this;
+ }
- public Task Invoke(HttpContext context, ref IServiceProvider? sp1, out IServiceProvider? sp2)
+ if (_services.TryGetValue(serviceType, out var value))
{
- sp1 = null;
- sp2 = null;
- return Task.FromResult(0);
+ return value;
}
+ return null;
}
+ }
- private class MiddlewareInjectInvokeNoService
- {
- public MiddlewareInjectInvokeNoService(RequestDelegate next) { }
+ public class MiddlewareInjectWithOutAndRefParams
+ {
+ public MiddlewareInjectWithOutAndRefParams(RequestDelegate next) { }
- public Task Invoke(HttpContext context, object value) => Task.CompletedTask;
+ public Task Invoke(HttpContext context, ref IServiceProvider? sp1, out IServiceProvider? sp2)
+ {
+ sp1 = null;
+ sp2 = null;
+ return Task.FromResult(0);
}
+ }
- private class MiddlewareInjectInvoke
- {
- public MiddlewareInjectInvoke(RequestDelegate next) { }
+ private class MiddlewareInjectInvokeNoService
+ {
+ public MiddlewareInjectInvokeNoService(RequestDelegate next) { }
- public Task Invoke(HttpContext context, IServiceProvider provider) => Task.CompletedTask;
- }
+ public Task Invoke(HttpContext context, object value) => Task.CompletedTask;
+ }
- private class MiddlewareNoParametersStub
- {
- public MiddlewareNoParametersStub(RequestDelegate next) { }
+ private class MiddlewareInjectInvoke
+ {
+ public MiddlewareInjectInvoke(RequestDelegate next) { }
- public Task Invoke() => Task.CompletedTask;
- }
+ public Task Invoke(HttpContext context, IServiceProvider provider) => Task.CompletedTask;
+ }
- private class MiddlewareAsyncNoParametersStub
- {
- public MiddlewareAsyncNoParametersStub(RequestDelegate next) { }
+ private class MiddlewareNoParametersStub
+ {
+ public MiddlewareNoParametersStub(RequestDelegate next) { }
- public Task InvokeAsync() => Task.CompletedTask;
- }
+ public Task Invoke() => Task.CompletedTask;
+ }
- private class MiddlewareNonTaskReturnStub
- {
- public MiddlewareNonTaskReturnStub(RequestDelegate next) { }
+ private class MiddlewareAsyncNoParametersStub
+ {
+ public MiddlewareAsyncNoParametersStub(RequestDelegate next) { }
- public int Invoke() => 0;
- }
+ public Task InvokeAsync() => Task.CompletedTask;
+ }
- private class MiddlewareAsyncNonTaskReturnStub
- {
- public MiddlewareAsyncNonTaskReturnStub(RequestDelegate next) { }
+ private class MiddlewareNonTaskReturnStub
+ {
+ public MiddlewareNonTaskReturnStub(RequestDelegate next) { }
- public int InvokeAsync() => 0;
- }
+ public int Invoke() => 0;
+ }
- private class MiddlewareNoInvokeStub
- {
- public MiddlewareNoInvokeStub(RequestDelegate next) { }
- }
+ private class MiddlewareAsyncNonTaskReturnStub
+ {
+ public MiddlewareAsyncNonTaskReturnStub(RequestDelegate next) { }
- private class MiddlewareMultipleInvokesStub
- {
- public MiddlewareMultipleInvokesStub(RequestDelegate next) { }
+ public int InvokeAsync() => 0;
+ }
+
+ private class MiddlewareNoInvokeStub
+ {
+ public MiddlewareNoInvokeStub(RequestDelegate next) { }
+ }
- public Task Invoke(HttpContext context) => Task.CompletedTask;
+ private class MiddlewareMultipleInvokesStub
+ {
+ public MiddlewareMultipleInvokesStub(RequestDelegate next) { }
- public Task Invoke(HttpContext context, int i) => Task.CompletedTask;
- }
+ public Task Invoke(HttpContext context) => Task.CompletedTask;
- private class MiddlewareMultipleInvokeAsyncStub
- {
- public MiddlewareMultipleInvokeAsyncStub(RequestDelegate next) { }
+ public Task Invoke(HttpContext context, int i) => Task.CompletedTask;
+ }
- public Task InvokeAsync(HttpContext context) => Task.CompletedTask;
+ private class MiddlewareMultipleInvokeAsyncStub
+ {
+ public MiddlewareMultipleInvokeAsyncStub(RequestDelegate next) { }
- public Task InvokeAsync(HttpContext context, int i) => Task.CompletedTask;
- }
+ public Task InvokeAsync(HttpContext context) => Task.CompletedTask;
- private class MiddlewareMultipleInvokeAndInvokeAsyncStub
- {
- public MiddlewareMultipleInvokeAndInvokeAsyncStub(RequestDelegate next) { }
+ 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 Invoke(HttpContext context) => Task.CompletedTask;
- public Task InvokeAsync(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
index a5511ef0c1..26a91a6777 100644
--- a/src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs
+++ b/src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs
@@ -9,161 +9,160 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+public class UsePathBaseExtensionsTests
{
- public class UsePathBaseExtensionsTests
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("/")]
+ public void EmptyOrNullPathBase_DoNotAddMiddleware(string pathBase)
{
- [Theory]
- [InlineData(null)]
- [InlineData("")]
- [InlineData("/")]
- public void EmptyOrNullPathBase_DoNotAddMiddleware(string pathBase)
- {
- // Arrange
- var useCalled = false;
- var builder = new ApplicationBuilderWrapper(CreateBuilder(), () => useCalled = true)
- .UsePathBase(pathBase);
+ // Arrange
+ var useCalled = false;
+ var builder = new ApplicationBuilderWrapper(CreateBuilder(), () => useCalled = true)
+ .UsePathBase(pathBase);
- // Act
- builder.Build();
+ // 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();
+ // Assert
+ Assert.False(useCalled);
+ }
- }
+ private class ApplicationBuilderWrapper : IApplicationBuilder
+ {
+ private readonly IApplicationBuilder _wrappedBuilder;
+ private readonly Action _useCallback;
- [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 Task RequestPathBaseContainingPathBase_IsSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ public ApplicationBuilderWrapper(IApplicationBuilder applicationBuilder, Action useCallback)
{
- return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+ _wrappedBuilder = applicationBuilder;
+ _useCallback = useCallback;
}
- [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 Task RequestPathBaseNotContainingPathBase_IsNotSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
- return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+ _useCallback();
+ return _wrappedBuilder.Use(middleware);
}
- [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 Task PathBaseNeverEndsWithSlash(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ public IServiceProvider ApplicationServices
{
- return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+ get { return _wrappedBuilder.ApplicationServices; }
+ set { _wrappedBuilder.ApplicationServices = value; }
}
- [Theory]
- [InlineData("/base", "", "/Base/Something", "/Base", "/Something")]
- [InlineData("/base", "/OldBase", "/Base/Something", "/OldBase/Base", "/Something")]
- public Task PathBaseAndPathPreserveRequestCasing(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
- {
- return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
- }
+ public IDictionary<string, object?> Properties => _wrappedBuilder.Properties;
+ public IFeatureCollection ServerFeatures => _wrappedBuilder.ServerFeatures;
+ public RequestDelegate Build() => _wrappedBuilder.Build();
+ public IApplicationBuilder New() => _wrappedBuilder.New();
- [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 Task PathBaseCanHaveUnicodeCharacters(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
- {
- return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
- }
+ }
- private static async Task 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);
- });
- await builder.Build().Invoke(requestContext);
-
- // Assert path and pathBase are split after middleware
- Assert.Equal(expectedPath, ((PathString?)requestContext.Items["test.Path"])!.Value.Value);
- Assert.Equal(expectedPathBase, ((PathString?)requestContext.Items["test.PathBase"])!.Value.Value);
-
- // Assert path and pathBase are reset after request
- Assert.Equal(pathBase, requestContext.Request.PathBase.Value);
- Assert.Equal(requestPath, requestContext.Request.Path.Value);
- }
+ [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 Task RequestPathBaseContainingPathBase_IsSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ {
+ return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+ }
- 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;
- }
+ [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 Task RequestPathBaseNotContainingPathBase_IsNotSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ {
+ return 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 Task PathBaseNeverEndsWithSlash(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ {
+ return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+ }
- private static ApplicationBuilder CreateBuilder()
+ [Theory]
+ [InlineData("/base", "", "/Base/Something", "/Base", "/Something")]
+ [InlineData("/base", "/OldBase", "/Base/Something", "/OldBase/Base", "/Something")]
+ public Task PathBaseAndPathPreserveRequestCasing(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ {
+ return 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 Task PathBaseCanHaveUnicodeCharacters(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ {
+ return TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+ }
+
+ private static async Task TestPathBase(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+ {
+ HttpContext requestContext = CreateRequest(pathBase, requestPath);
+ var builder = CreateBuilder()
+ .UsePathBase(registeredPathBase);
+ builder.Run(context =>
{
- return new ApplicationBuilder(serviceProvider: null!);
- }
+ context.Items["test.Path"] = context.Request.Path;
+ context.Items["test.PathBase"] = context.Request.PathBase;
+ return Task.FromResult(0);
+ });
+ await builder.Build().Invoke(requestContext);
+
+ // Assert path and pathBase are split after middleware
+ Assert.Equal(expectedPath, ((PathString?)requestContext.Items["test.Path"])!.Value.Value);
+ Assert.Equal(expectedPathBase, ((PathString?)requestContext.Items["test.PathBase"])!.Value.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
index 8b46e07b83..934f3dcbe9 100644
--- a/src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs
+++ b/src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs
@@ -7,164 +7,163 @@ using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Builder.Extensions
+namespace Microsoft.AspNetCore.Builder.Extensions;
+
+public class UseWhenExtensionsTests
{
- public class UseWhenExtensionsTests
+ [Fact]
+ public void NullArguments_ArgumentNullException()
{
- [Fact]
- public void NullArguments_ArgumentNullException()
- {
- // Arrange
- var builder = CreateBuilder();
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- Action nullPredicate = () => builder.UseWhen(null!, app => { });
- Action nullConfiguration = () => builder.UseWhen(TruePredicate, null!);
+ // Act
+ Action nullPredicate = () => builder.UseWhen(null!, app => { });
+ Action nullConfiguration = () => builder.UseWhen(TruePredicate, null!);
- // Assert
- Assert.Throws<ArgumentNullException>(nullPredicate);
- Assert.Throws<ArgumentNullException>(nullConfiguration);
- }
+ // Assert
+ Assert.Throws<ArgumentNullException>(nullPredicate);
+ Assert.Throws<ArgumentNullException>(nullConfiguration);
+ }
- [Fact]
- public async Task PredicateTrue_BranchTaken_WillRejoin()
- {
- // Arrange
- var context = CreateContext();
- var parent = CreateBuilder();
+ [Fact]
+ public async Task PredicateTrue_BranchTaken_WillRejoin()
+ {
+ // Arrange
+ var context = CreateContext();
+ var parent = CreateBuilder();
- parent.UseWhen(TruePredicate, child =>
+ parent.UseWhen(TruePredicate, child =>
+ {
+ child.UseWhen(TruePredicate, grandchild =>
{
- child.UseWhen(TruePredicate, grandchild =>
- {
- grandchild.Use(Increment("grandchild"));
- });
-
- child.Use(Increment("child"));
+ grandchild.Use(Increment("grandchild"));
});
- parent.Use(Increment("parent"));
+ child.Use(Increment("child"));
+ });
- // Act
- await parent.Build().Invoke(context);
+ parent.Use(Increment("parent"));
- // Assert
- Assert.Equal(1, Count(context, "parent"));
- Assert.Equal(1, Count(context, "child"));
- Assert.Equal(1, Count(context, "grandchild"));
- }
+ // Act
+ await parent.Build().Invoke(context);
- [Fact]
- public async Task PredicateTrue_BranchTaken_CanTerminate()
- {
- // Arrange
- var context = CreateContext();
- var parent = CreateBuilder();
+ // Assert
+ Assert.Equal(1, Count(context, "parent"));
+ Assert.Equal(1, Count(context, "child"));
+ Assert.Equal(1, Count(context, "grandchild"));
+ }
- parent.UseWhen(TruePredicate, child =>
- {
- child.UseWhen(TruePredicate, grandchild =>
- {
- grandchild.Use(Increment("grandchild", terminate: true));
- });
+ [Fact]
+ public async Task PredicateTrue_BranchTaken_CanTerminate()
+ {
+ // Arrange
+ var context = CreateContext();
+ var parent = CreateBuilder();
- child.Use(Increment("child"));
+ parent.UseWhen(TruePredicate, child =>
+ {
+ child.UseWhen(TruePredicate, grandchild =>
+ {
+ grandchild.Use(Increment("grandchild", terminate: true));
});
- parent.Use(Increment("parent"));
+ child.Use(Increment("child"));
+ });
- // Act
- await parent.Build().Invoke(context);
+ parent.Use(Increment("parent"));
- // Assert
- Assert.Equal(0, Count(context, "parent"));
- Assert.Equal(0, Count(context, "child"));
- Assert.Equal(1, Count(context, "grandchild"));
- }
+ // Act
+ await parent.Build().Invoke(context);
- [Fact]
- public async Task PredicateFalse_PassThrough()
- {
- // Arrange
- var context = CreateContext();
- var parent = CreateBuilder();
+ // Assert
+ Assert.Equal(0, Count(context, "parent"));
+ Assert.Equal(0, Count(context, "child"));
+ Assert.Equal(1, Count(context, "grandchild"));
+ }
- parent.UseWhen(FalsePredicate, child =>
- {
- child.Use(Increment("child"));
- });
+ [Fact]
+ public async Task PredicateFalse_PassThrough()
+ {
+ // Arrange
+ var context = CreateContext();
+ var parent = CreateBuilder();
- parent.Use(Increment("parent"));
+ parent.UseWhen(FalsePredicate, child =>
+ {
+ child.Use(Increment("child"));
+ });
- // Act
- await parent.Build().Invoke(context);
+ parent.Use(Increment("parent"));
- // Assert
- Assert.Equal(1, Count(context, "parent"));
- Assert.Equal(0, Count(context, "child"));
- }
+ // Act
+ await parent.Build().Invoke(context);
- private static HttpContext CreateContext()
- {
- return new DefaultHttpContext();
- }
+ // Assert
+ Assert.Equal(1, Count(context, "parent"));
+ Assert.Equal(0, Count(context, "child"));
+ }
- private static ApplicationBuilder CreateBuilder()
- {
- return new ApplicationBuilder(serviceProvider: null!);
- }
+ private static HttpContext CreateContext()
+ {
+ return new DefaultHttpContext();
+ }
- private static bool TruePredicate(HttpContext context)
- {
- return true;
- }
+ private static ApplicationBuilder CreateBuilder()
+ {
+ return new ApplicationBuilder(serviceProvider: null!);
+ }
- private static bool FalsePredicate(HttpContext context)
- {
- return false;
- }
+ 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)
+ private static Func<HttpContext, Func<Task>, Task> Increment(string key, bool terminate = false)
+ {
+ return (context, next) =>
{
- return (context, next) =>
+ if (!context.Items.ContainsKey(key))
{
- if (!context.Items.ContainsKey(key))
+ context.Items[key] = 1;
+ }
+ else
+ {
+ var item = context.Items[key];
+
+ if (item is int)
{
- context.Items[key] = 1;
+ context.Items[key] = 1 + (int)item;
}
else
{
- var item = context.Items[key];
-
- if (item is int)
- {
- context.Items[key] = 1 + (int)item;
- }
- else
- {
- context.Items[key] = 1;
- }
+ context.Items[key] = 1;
}
+ }
- return terminate ? Task.FromResult<object?>(null) : next();
- };
- }
+ return terminate ? Task.FromResult<object?>(null) : next();
+ };
+ }
- private static int Count(HttpContext context, string key)
+ private static int Count(HttpContext context, string key)
+ {
+ if (!context.Items.ContainsKey(key))
{
- if (!context.Items.ContainsKey(key))
- {
- return 0;
- }
-
- var item = context.Items[key];
+ return 0;
+ }
- if (item is int)
- {
- return (int)item;
- }
+ var item = context.Items[key];
- return 0;
+ 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
index 783afd3229..87268945ed 100644
--- a/src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs
+++ b/src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs
@@ -10,165 +10,165 @@ using Microsoft.AspNetCore.Http.Headers;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods for accessing strongly typed HTTP request and response
+/// headers.
+/// </summary>
+public static class HeaderDictionaryTypeExtensions
{
/// <summary>
- /// Extension methods for accessing strongly typed HTTP request and response
- /// headers.
+ /// Gets strongly typed HTTP request headers.
/// </summary>
- public static class HeaderDictionaryTypeExtensions
+ /// <param name="request">The <see cref="HttpRequest"/>.</param>
+ /// <returns>The <see cref="RequestHeaders"/>.</returns>
+ public static RequestHeaders GetTypedHeaders(this HttpRequest request)
{
- /// <summary>
- /// Gets strongly typed HTTP request headers.
- /// </summary>
- /// <param name="request">The <see cref="HttpRequest"/>.</param>
- /// <returns>The <see cref="RequestHeaders"/>.</returns>
- public static RequestHeaders GetTypedHeaders(this HttpRequest request)
+ return new RequestHeaders(request.Headers);
+ }
+
+ /// <summary>
+ /// Gets strongly typed HTTP response headers.
+ /// </summary>
+ /// <param name="response">The <see cref="HttpResponse"/>.</param>
+ /// <returns>The <see cref="ResponseHeaders"/>.</returns>
+ 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)
{
- return new RequestHeaders(request.Headers);
+ throw new ArgumentNullException(nameof(headers));
}
- /// <summary>
- /// Gets strongly typed HTTP response headers.
- /// </summary>
- /// <param name="response">The <see cref="HttpResponse"/>.</param>
- /// <returns>The <see cref="ResponseHeaders"/>.</returns>
- public static ResponseHeaders GetTypedHeaders(this HttpResponse response)
+ if (name == null)
{
- return new ResponseHeaders(response.Headers);
+ throw new ArgumentNullException(nameof(name));
}
- // These are all shared helpers used by both RequestHeaders and ResponseHeaders
+ return headers.Get<DateTimeOffset?>(name);
+ }
- internal static DateTimeOffset? GetDate(this IHeaderDictionary headers, string name)
+ internal static void Set(this IHeaderDictionary headers, string name, object? value)
+ {
+ if (headers == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ throw new ArgumentNullException(nameof(headers));
+ }
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
- return headers.Get<DateTimeOffset?>(name);
+ if (value == null)
+ {
+ headers.Remove(name);
+ }
+ else
+ {
+ headers[name] = value.ToString();
}
+ }
- internal static void Set(this IHeaderDictionary headers, string name, object? value)
+ internal static void SetList<T>(this IHeaderDictionary headers, string name, IList<T>? values)
+ {
+ if (headers == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ throw new ArgumentNullException(nameof(headers));
+ }
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
- if (value == null)
- {
- headers.Remove(name);
- }
- else
+ 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++)
{
- headers[name] = value.ToString();
+ newValues[i] = values[i]!.ToString()!;
}
+ headers[name] = new StringValues(newValues);
}
+ }
- internal static void SetList<T>(this IHeaderDictionary headers, string name, IList<T>? values)
+ /// <summary>
+ /// Appends a sequence of values to <see cref="IHeaderDictionary"/>.
+ /// </summary>
+ /// <typeparam name="T">The type of header value.</typeparam>
+ /// <param name="Headers">The <see cref="IHeaderDictionary"/>.</param>
+ /// <param name="name">The header name.</param>
+ /// <param name="values">The values to append.</param>
+ public static void AppendList<T>(this IHeaderDictionary Headers, string name, IList<T> values)
+ {
+ if (name == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ throw new ArgumentNullException(nameof(name));
+ }
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values == null || values.Count == 0)
- {
- headers.Remove(name);
- }
- else if (values.Count == 1)
- {
- headers[name] = new StringValues(values[0]!.ToString());
- }
- else
- {
+ 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[name] = new StringValues(newValues);
- }
+ Headers.Append(name, new StringValues(newValues));
+ break;
}
+ }
- /// <summary>
- /// Appends a sequence of values to <see cref="IHeaderDictionary"/>.
- /// </summary>
- /// <typeparam name="T">The type of header value.</typeparam>
- /// <param name="Headers">The <see cref="IHeaderDictionary"/>.</param>
- /// <param name="name">The header name.</param>
- /// <param name="values">The values to append.</param>
- public static void AppendList<T>(this IHeaderDictionary Headers, string name, IList<T> values)
+ internal static void SetDate(this IHeaderDictionary headers, string name, DateTimeOffset? value)
+ {
+ if (headers == null)
{
- 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;
- }
+ throw new ArgumentNullException(nameof(headers));
}
- internal static void SetDate(this IHeaderDictionary headers, string name, DateTimeOffset? value)
+ if (name == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
-
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ throw new ArgumentNullException(nameof(name));
+ }
- if (value.HasValue)
- {
- headers[name] = HeaderUtilities.FormatDate(value.GetValueOrDefault());
- }
- else
- {
- headers.Remove(name);
- }
+ if (value.HasValue)
+ {
+ headers[name] = HeaderUtilities.FormatDate(value.GetValueOrDefault());
+ }
+ else
+ {
+ headers.Remove(name);
}
+ }
- private static readonly IDictionary<Type, object> KnownParsers = new Dictionary<Type, object>()
+ private static readonly IDictionary<Type, object> KnownParsers = new Dictionary<Type, object>()
{
{ typeof(CacheControlHeaderValue), new Func<string, CacheControlHeaderValue?>(value => { return CacheControlHeaderValue.TryParse(value, out var result) ? result : null; }) },
{ typeof(ContentDispositionHeaderValue), new Func<string, ContentDispositionHeaderValue?>(value => { return ContentDispositionHeaderValue.TryParse(value, out var result) ? result : null; }) },
@@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Http
{ typeof(long?), new Func<string, long?>(value => { return HeaderUtilities.TryParseNonNegativeInt64(value, out var result) ? result : null; }) },
};
- private static readonly IDictionary<Type, object> KnownListParsers = new Dictionary<Type, object>()
+ private static readonly IDictionary<Type, object> KnownListParsers = new Dictionary<Type, object>()
{
{ typeof(MediaTypeHeaderValue), new Func<IList<string>, IList<MediaTypeHeaderValue>>(value => { return MediaTypeHeaderValue.TryParseList(value, out var result) ? result : Array.Empty<MediaTypeHeaderValue>(); }) },
{ typeof(StringWithQualityHeaderValue), new Func<IList<string>, IList<StringWithQualityHeaderValue>>(value => { return StringWithQualityHeaderValue.TryParseList(value, out var result) ? result : Array.Empty<StringWithQualityHeaderValue>(); }) },
@@ -190,127 +190,126 @@ namespace Microsoft.AspNetCore.Http
{ typeof(SetCookieHeaderValue), new Func<IList<string>, IList<SetCookieHeaderValue>>(value => { return SetCookieHeaderValue.TryParseList(value, out var result) ? result : Array.Empty<SetCookieHeaderValue>(); }) },
};
- internal static T? Get<T>(this IHeaderDictionary headers, string name)
+ internal static T? Get<T>(this IHeaderDictionary headers, string name)
+ {
+ if (headers == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
-
- var value = headers[name];
+ throw new ArgumentNullException(nameof(headers));
+ }
- if (StringValues.IsNullOrEmpty(value))
- {
- return default(T);
- }
+ var value = headers[name];
- if (KnownParsers.TryGetValue(typeof(T), out var temp))
- {
- var func = (Func<string, T>)temp;
- return func(value.ToString());
- }
+ if (StringValues.IsNullOrEmpty(value))
+ {
+ return default(T);
+ }
- return GetViaReflection<T>(value.ToString());
+ if (KnownParsers.TryGetValue(typeof(T), out var temp))
+ {
+ var func = (Func<string, T>)temp;
+ return func(value.ToString());
}
- internal static IList<T> GetList<T>(this IHeaderDictionary headers, string name)
+ return GetViaReflection<T>(value.ToString());
+ }
+
+ internal static IList<T> GetList<T>(this IHeaderDictionary headers, string name)
+ {
+ if (headers == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
+ throw new ArgumentNullException(nameof(headers));
+ }
+
+ var values = headers[name];
- var values = headers[name];
+ return GetList<T>(values);
+ }
- return GetList<T>(values);
+ internal static IList<T> GetList<T>(this StringValues values)
+ {
+ if (StringValues.IsNullOrEmpty(values))
+ {
+ return Array.Empty<T>();
}
- internal static IList<T> GetList<T>(this StringValues values)
+ if (KnownListParsers.TryGetValue(typeof(T), out var temp))
{
- if (StringValues.IsNullOrEmpty(values))
- {
- return Array.Empty<T>();
- }
+ var func = (Func<IList<string>, IList<T>>)temp;
+ return func(values);
+ }
+
+ return GetListViaReflection<T>(values);
+ }
- if (KnownListParsers.TryGetValue(typeof(T), out var temp))
+ 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 =>
{
- var func = (Func<IList<string>, IList<T>>)temp;
- return func(values);
- }
+ 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;
+ });
- return GetListViaReflection<T>(values);
+ if (method == null)
+ {
+ throw new NotSupportedException(string.Format(
+ CultureInfo.CurrentCulture,
+ "The given type '{0}' does not have a TryParse method with the required signature 'public static bool TryParse(string, out {0}).",
+ nameof(T)));
}
- private static T? GetViaReflection<T>(string value)
+ var parameters = new object?[] { value, null };
+ var success = (bool)method.Invoke(null, parameters)!;
+ if (success)
{
- // 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(
- CultureInfo.CurrentCulture,
- "The given type '{0}' does not have a TryParse method with the required signature 'public static bool TryParse(string, out {0}).",
- nameof(T)));
- }
+ return (T?)parameters[1];
+ }
+ return default(T);
+ }
- var parameters = new object?[] { value, null };
- var success = (bool)method.Invoke(null, parameters)!;
- if (success)
+ 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 =>
{
- return (T?)parameters[1];
- }
- return default(T);
- }
+ 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;
+ });
- private static IList<T> GetListViaReflection<T>(StringValues values)
+ if (method == null)
{
- // 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(
- CultureInfo.CurrentCulture,
- "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)));
- }
+ throw new NotSupportedException(string.Format(
+ CultureInfo.CurrentCulture,
+ "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 Array.Empty<T>();
+ var parameters = new object?[] { values, null };
+ var success = (bool)method.Invoke(null, parameters)!;
+ if (success)
+ {
+ return (IList<T>)parameters[1]!;
}
+ return Array.Empty<T>();
}
}
diff --git a/src/Http/Http.Extensions/src/HttpContextServerVariableExtensions.cs b/src/Http/Http.Extensions/src/HttpContextServerVariableExtensions.cs
index cefd005da2..2e7bdd2090 100644
--- a/src/Http/Http.Extensions/src/HttpContextServerVariableExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpContextServerVariableExtensions.cs
@@ -3,32 +3,31 @@
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extensions for reading HTTP server variables.
+/// </summary>
+public static class HttpContextServerVariableExtensions
{
/// <summary>
- /// Extensions for reading HTTP server variables.
+ /// Gets the value of a server variable for the current request.
/// </summary>
- public static class HttpContextServerVariableExtensions
+ /// <param name="context">The http context for the request.</param>
+ /// <param name="variableName">The name of the variable.</param>
+ /// <returns>
+ /// <c>null</c> if the server does not support the <see cref="IServerVariablesFeature"/> feature.
+ /// May return null or empty if the variable does not exist or is not set.
+ /// </returns>
+ public static string? GetServerVariable(this HttpContext context, string variableName)
{
- /// <summary>
- /// Gets the value of a server variable for the current request.
- /// </summary>
- /// <param name="context">The http context for the request.</param>
- /// <param name="variableName">The name of the variable.</param>
- /// <returns>
- /// <c>null</c> if the server does not support the <see cref="IServerVariablesFeature"/> feature.
- /// May return null or empty if the variable does not exist or is not set.
- /// </returns>
- public static string? GetServerVariable(this HttpContext context, string variableName)
- {
- var feature = context.Features.Get<IServerVariablesFeature>();
-
- if (feature == null)
- {
- return null;
- }
+ var feature = context.Features.Get<IServerVariablesFeature>();
- return feature[variableName];
+ if (feature == null)
+ {
+ return null;
}
+
+ return feature[variableName];
}
}
diff --git a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs
index e2adc83d71..cc2b51dfc3 100644
--- a/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpRequestJsonExtensions.cs
@@ -14,218 +14,217 @@ using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods to read the request body as JSON.
+/// </summary>
+public static class HttpRequestJsonExtensions
{
/// <summary>
- /// Extension methods to read the request body as JSON.
+ /// Read JSON from the request and deserialize to the specified type.
+ /// If the request's content-type is not a known JSON type then an error will be thrown.
/// </summary>
- public static class HttpRequestJsonExtensions
+ /// <typeparam name="TValue">The type of object to read.</typeparam>
+ /// <param name="request">The request to read from.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static ValueTask<TValue?> ReadFromJsonAsync<TValue>(
+ this HttpRequest request,
+ CancellationToken cancellationToken = default)
{
- /// <summary>
- /// Read JSON from the request and deserialize to the specified type.
- /// If the request's content-type is not a known JSON type then an error will be thrown.
- /// </summary>
- /// <typeparam name="TValue">The type of object to read.</typeparam>
- /// <param name="request">The request to read from.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static ValueTask<TValue?> ReadFromJsonAsync<TValue>(
- this HttpRequest request,
- CancellationToken cancellationToken = default)
- {
- return request.ReadFromJsonAsync<TValue>(options: null, cancellationToken);
- }
-
- /// <summary>
- /// Read JSON from the request and deserialize to the specified type.
- /// If the request's content-type is not a known JSON type then an error will be thrown.
- /// </summary>
- /// <typeparam name="TValue">The type of object to read.</typeparam>
- /// <param name="request">The request to read from.</param>
- /// <param name="options">The serializer options use when deserializing the content.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static async ValueTask<TValue?> ReadFromJsonAsync<TValue>(
- this HttpRequest request,
- JsonSerializerOptions? options,
- CancellationToken cancellationToken = default)
- {
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
+ return request.ReadFromJsonAsync<TValue>(options: null, cancellationToken);
+ }
- if (!request.HasJsonContentType(out var charset))
- {
- throw CreateContentTypeError(request);
- }
+ /// <summary>
+ /// Read JSON from the request and deserialize to the specified type.
+ /// If the request's content-type is not a known JSON type then an error will be thrown.
+ /// </summary>
+ /// <typeparam name="TValue">The type of object to read.</typeparam>
+ /// <param name="request">The request to read from.</param>
+ /// <param name="options">The serializer options use when deserializing the content.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static async ValueTask<TValue?> ReadFromJsonAsync<TValue>(
+ this HttpRequest request,
+ JsonSerializerOptions? options,
+ CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
- options ??= ResolveSerializerOptions(request.HttpContext);
+ if (!request.HasJsonContentType(out var charset))
+ {
+ throw CreateContentTypeError(request);
+ }
- var encoding = GetEncodingFromCharset(charset);
- var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
+ options ??= ResolveSerializerOptions(request.HttpContext);
- try
- {
- return await JsonSerializer.DeserializeAsync<TValue>(inputStream, options, cancellationToken);
- }
- finally
+ var encoding = GetEncodingFromCharset(charset);
+ var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
+
+ try
+ {
+ return await JsonSerializer.DeserializeAsync<TValue>(inputStream, options, cancellationToken);
+ }
+ finally
+ {
+ if (usesTranscodingStream)
{
- if (usesTranscodingStream)
- {
- await inputStream.DisposeAsync();
- }
+ await inputStream.DisposeAsync();
}
}
+ }
- /// <summary>
- /// Read JSON from the request and deserialize to the specified type.
- /// If the request's content-type is not a known JSON type then an error will be thrown.
- /// </summary>
- /// <param name="request">The request to read from.</param>
- /// <param name="type">The type of object to read.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static ValueTask<object?> ReadFromJsonAsync(
- this HttpRequest request,
- Type type,
- CancellationToken cancellationToken = default)
- {
- return request.ReadFromJsonAsync(type, options: null, cancellationToken);
- }
-
- /// <summary>
- /// Read JSON from the request and deserialize to the specified type.
- /// If the request's content-type is not a known JSON type then an error will be thrown.
- /// </summary>
- /// <param name="request">The request to read from.</param>
- /// <param name="type">The type of object to read.</param>
- /// <param name="options">The serializer options use when deserializing the content.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static async ValueTask<object?> ReadFromJsonAsync(
- this HttpRequest request,
- Type type,
- JsonSerializerOptions? options,
- CancellationToken cancellationToken = default)
- {
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
+ /// <summary>
+ /// Read JSON from the request and deserialize to the specified type.
+ /// If the request's content-type is not a known JSON type then an error will be thrown.
+ /// </summary>
+ /// <param name="request">The request to read from.</param>
+ /// <param name="type">The type of object to read.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static ValueTask<object?> ReadFromJsonAsync(
+ this HttpRequest request,
+ Type type,
+ CancellationToken cancellationToken = default)
+ {
+ return request.ReadFromJsonAsync(type, options: null, cancellationToken);
+ }
- if (!request.HasJsonContentType(out var charset))
- {
- throw CreateContentTypeError(request);
- }
+ /// <summary>
+ /// Read JSON from the request and deserialize to the specified type.
+ /// If the request's content-type is not a known JSON type then an error will be thrown.
+ /// </summary>
+ /// <param name="request">The request to read from.</param>
+ /// <param name="type">The type of object to read.</param>
+ /// <param name="options">The serializer options use when deserializing the content.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static async ValueTask<object?> ReadFromJsonAsync(
+ this HttpRequest request,
+ Type type,
+ JsonSerializerOptions? options,
+ CancellationToken cancellationToken = default)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
- options ??= ResolveSerializerOptions(request.HttpContext);
+ if (!request.HasJsonContentType(out var charset))
+ {
+ throw CreateContentTypeError(request);
+ }
- var encoding = GetEncodingFromCharset(charset);
- var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
+ options ??= ResolveSerializerOptions(request.HttpContext);
- try
- {
- return await JsonSerializer.DeserializeAsync(inputStream, type, options, cancellationToken);
- }
- finally
- {
- if (usesTranscodingStream)
- {
- await inputStream.DisposeAsync();
- }
- }
- }
+ var encoding = GetEncodingFromCharset(charset);
+ var (inputStream, usesTranscodingStream) = GetInputStream(request.HttpContext, encoding);
- /// <summary>
- /// Checks the Content-Type header for JSON types.
- /// </summary>
- /// <returns>true if the Content-Type header represents a JSON content type; otherwise, false.</returns>
- public static bool HasJsonContentType(this HttpRequest request)
+ try
{
- return request.HasJsonContentType(out _);
+ return await JsonSerializer.DeserializeAsync(inputStream, type, options, cancellationToken);
}
-
- private static bool HasJsonContentType(this HttpRequest request, out StringSegment charset)
+ finally
{
- if (request == null)
+ if (usesTranscodingStream)
{
- throw new ArgumentNullException(nameof(request));
- }
-
- if (!MediaTypeHeaderValue.TryParse(request.ContentType, out var mt))
- {
- charset = StringSegment.Empty;
- return false;
+ await inputStream.DisposeAsync();
}
+ }
+ }
- // Matches application/json
- if (mt.MediaType.Equals(JsonConstants.JsonContentType, StringComparison.OrdinalIgnoreCase))
- {
- charset = mt.Charset;
- return true;
- }
+ /// <summary>
+ /// Checks the Content-Type header for JSON types.
+ /// </summary>
+ /// <returns>true if the Content-Type header represents a JSON content type; otherwise, false.</returns>
+ public static bool HasJsonContentType(this HttpRequest request)
+ {
+ return request.HasJsonContentType(out _);
+ }
- // Matches +json, e.g. application/ld+json
- if (mt.Suffix.Equals("json", StringComparison.OrdinalIgnoreCase))
- {
- charset = mt.Charset;
- return true;
- }
+ private static bool HasJsonContentType(this HttpRequest request, out StringSegment charset)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+ if (!MediaTypeHeaderValue.TryParse(request.ContentType, out var mt))
+ {
charset = StringSegment.Empty;
return false;
}
-
- private static JsonSerializerOptions ResolveSerializerOptions(HttpContext httpContext)
+ // Matches application/json
+ if (mt.MediaType.Equals(JsonConstants.JsonContentType, StringComparison.OrdinalIgnoreCase))
{
- // Attempt to resolve options from DI then fallback to default options
- return httpContext.RequestServices?.GetService<IOptions<JsonOptions>>()?.Value?.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
+ charset = mt.Charset;
+ return true;
}
- private static InvalidOperationException CreateContentTypeError(HttpRequest request)
+ // Matches +json, e.g. application/ld+json
+ if (mt.Suffix.Equals("json", StringComparison.OrdinalIgnoreCase))
{
- return new InvalidOperationException($"Unable to read the request as JSON because the request content type '{request.ContentType}' is not a known JSON content type.");
+ charset = mt.Charset;
+ return true;
}
- private static (Stream inputStream, bool usesTranscodingStream) GetInputStream(HttpContext httpContext, Encoding? encoding)
- {
- if (encoding == null || encoding.CodePage == Encoding.UTF8.CodePage)
- {
- return (httpContext.Request.Body, false);
- }
+ charset = StringSegment.Empty;
+ return false;
+ }
- var inputStream = Encoding.CreateTranscodingStream(httpContext.Request.Body, encoding, Encoding.UTF8, leaveOpen: true);
- return (inputStream, true);
+
+ private static JsonSerializerOptions ResolveSerializerOptions(HttpContext httpContext)
+ {
+ // Attempt to resolve options from DI then fallback to default options
+ return httpContext.RequestServices?.GetService<IOptions<JsonOptions>>()?.Value?.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
+ }
+
+ private static InvalidOperationException CreateContentTypeError(HttpRequest request)
+ {
+ return new InvalidOperationException($"Unable to read the request as JSON because the request content type '{request.ContentType}' is not a known JSON content type.");
+ }
+
+ private static (Stream inputStream, bool usesTranscodingStream) GetInputStream(HttpContext httpContext, Encoding? encoding)
+ {
+ if (encoding == null || encoding.CodePage == Encoding.UTF8.CodePage)
+ {
+ return (httpContext.Request.Body, false);
}
- private static Encoding? GetEncodingFromCharset(StringSegment charset)
+ var inputStream = Encoding.CreateTranscodingStream(httpContext.Request.Body, encoding, Encoding.UTF8, leaveOpen: true);
+ return (inputStream, true);
+ }
+
+ private static Encoding? GetEncodingFromCharset(StringSegment charset)
+ {
+ if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase))
{
- if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase))
- {
- // This is an optimization for utf-8 that prevents the Substring caused by
- // charset.Value
- return Encoding.UTF8;
- }
+ // This is an optimization for utf-8 that prevents the Substring caused by
+ // charset.Value
+ return Encoding.UTF8;
+ }
- try
- {
- // charset.Value might be an invalid encoding name as in charset=invalid.
- return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null;
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException($"Unable to read the request as JSON because the request content type charset '{charset}' is not a known encoding.", ex);
- }
+ try
+ {
+ // charset.Value might be an invalid encoding name as in charset=invalid.
+ return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Unable to read the request as JSON because the request content type charset '{charset}' is not a known encoding.", ex);
}
}
}
diff --git a/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs b/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs
index 17236850fc..87fa8e3c88 100644
--- a/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs
@@ -4,30 +4,29 @@
using System;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+/// <summary>
+/// Extension methods for working with multipart form requests.
+/// </summary>
+public static class HttpRequestMultipartExtensions
{
/// <summary>
- /// Extension methods for working with multipart form requests.
+ /// Gets the mutipart boundary from the <c>Content-Type</c> header.
/// </summary>
- public static class HttpRequestMultipartExtensions
+ /// <param name="request">The <see cref="HttpRequest"/>.</param>
+ /// <returns>The multipart boundary.</returns>
+ public static string GetMultipartBoundary(this HttpRequest request)
{
- /// <summary>
- /// Gets the mutipart boundary from the <c>Content-Type</c> header.
- /// </summary>
- /// <param name="request">The <see cref="HttpRequest"/>.</param>
- /// <returns>The multipart boundary.</returns>
- public static string GetMultipartBoundary(this HttpRequest request)
+ if (request == null)
{
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
+ throw new ArgumentNullException(nameof(request));
+ }
- if (!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaType))
- {
- return string.Empty;
- }
- return HeaderUtilities.RemoveQuotes(mediaType.Boundary).ToString();
+ if (!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaType))
+ {
+ return string.Empty;
}
+ return HeaderUtilities.RemoveQuotes(mediaType.Boundary).ToString();
}
}
diff --git a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
index 1336cc8f1b..d9eea4aad9 100644
--- a/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
+++ b/src/Http/Http.Extensions/src/HttpResponseJsonExtensions.cs
@@ -10,200 +10,199 @@ using Microsoft.AspNetCore.Http.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides extension methods for writing a JSON serialized value to the HTTP response.
+/// </summary>
+public static partial class HttpResponseJsonExtensions
{
/// <summary>
- /// Provides extension methods for writing a JSON serialized value to the HTTP response.
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// <c>application/json; charset=utf-8</c>.
+ /// </summary>
+ /// <typeparam name="TValue">The type of object to write.</typeparam>
+ /// <param name="response">The response to write JSON to.</param>
+ /// <param name="value">The value to write as JSON.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsJsonAsync<TValue>(
+ this HttpResponse response,
+ TValue value,
+ CancellationToken cancellationToken = default)
+ {
+ return response.WriteAsJsonAsync<TValue>(value, options: null, contentType: null, cancellationToken);
+ }
+
+ /// <summary>
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// <c>application/json; charset=utf-8</c>.
+ /// </summary>
+ /// <typeparam name="TValue">The type of object to write.</typeparam>
+ /// <param name="response">The response to write JSON to.</param>
+ /// <param name="value">The value to write as JSON.</param>
+ /// <param name="options">The serializer options use when serializing the value.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsJsonAsync<TValue>(
+ this HttpResponse response,
+ TValue value,
+ JsonSerializerOptions? options,
+ CancellationToken cancellationToken = default)
+ {
+ return response.WriteAsJsonAsync<TValue>(value, options, contentType: null, cancellationToken);
+ }
+
+ /// <summary>
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// the specified content-type.
/// </summary>
- public static partial class HttpResponseJsonExtensions
+ /// <typeparam name="TValue">The type of object to write.</typeparam>
+ /// <param name="response">The response to write JSON to.</param>
+ /// <param name="value">The value to write as JSON.</param>
+ /// <param name="options">The serializer options use when serializing the value.</param>
+ /// <param name="contentType">The content-type to set on the response.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsJsonAsync<TValue>(
+ this HttpResponse response,
+ TValue value,
+ JsonSerializerOptions? options,
+ string? contentType,
+ CancellationToken cancellationToken = default)
{
- /// <summary>
- /// Write the specified value as JSON to the response body. The response content-type will be set to
- /// <c>application/json; charset=utf-8</c>.
- /// </summary>
- /// <typeparam name="TValue">The type of object to write.</typeparam>
- /// <param name="response">The response to write JSON to.</param>
- /// <param name="value">The value to write as JSON.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsJsonAsync<TValue>(
- this HttpResponse response,
- TValue value,
- CancellationToken cancellationToken = default)
+ if (response == null)
{
- return response.WriteAsJsonAsync<TValue>(value, options: null, contentType: null, cancellationToken);
+ throw new ArgumentNullException(nameof(response));
}
- /// <summary>
- /// Write the specified value as JSON to the response body. The response content-type will be set to
- /// <c>application/json; charset=utf-8</c>.
- /// </summary>
- /// <typeparam name="TValue">The type of object to write.</typeparam>
- /// <param name="response">The response to write JSON to.</param>
- /// <param name="value">The value to write as JSON.</param>
- /// <param name="options">The serializer options use when serializing the value.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsJsonAsync<TValue>(
- this HttpResponse response,
- TValue value,
- JsonSerializerOptions? options,
- CancellationToken cancellationToken = default)
+ options ??= ResolveSerializerOptions(response.HttpContext);
+
+ response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
+ // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
+ if (!cancellationToken.CanBeCanceled)
{
- return response.WriteAsJsonAsync<TValue>(value, options, contentType: null, cancellationToken);
+ return WriteAsJsonAsyncSlow<TValue>(response.Body, value, options, response.HttpContext.RequestAborted);
}
- /// <summary>
- /// Write the specified value as JSON to the response body. The response content-type will be set to
- /// the specified content-type.
- /// </summary>
- /// <typeparam name="TValue">The type of object to write.</typeparam>
- /// <param name="response">The response to write JSON to.</param>
- /// <param name="value">The value to write as JSON.</param>
- /// <param name="options">The serializer options use when serializing the value.</param>
- /// <param name="contentType">The content-type to set on the response.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsJsonAsync<TValue>(
- this HttpResponse response,
- TValue value,
- JsonSerializerOptions? options,
- string? contentType,
- CancellationToken cancellationToken = default)
- {
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
+ return JsonSerializer.SerializeAsync<TValue>(response.Body, value, options, cancellationToken);
+ }
- options ??= ResolveSerializerOptions(response.HttpContext);
+ private static async Task WriteAsJsonAsyncSlow<TValue>(
+ Stream body,
+ TValue value,
+ JsonSerializerOptions? options,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ await JsonSerializer.SerializeAsync<TValue>(body, value, options, cancellationToken);
+ }
+ catch (OperationCanceledException) { }
+ }
- response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
- // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!cancellationToken.CanBeCanceled)
- {
- return WriteAsJsonAsyncSlow<TValue>(response.Body, value, options, response.HttpContext.RequestAborted);
- }
+ /// <summary>
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// <c>application/json; charset=utf-8</c>.
+ /// </summary>
+ /// <param name="response">The response to write JSON to.</param>
+ /// <param name="value">The value to write as JSON.</param>
+ /// <param name="type">The type of object to write.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsJsonAsync(
+ this HttpResponse response,
+ object? value,
+ Type type,
+ CancellationToken cancellationToken = default)
+ {
+ return response.WriteAsJsonAsync(value, type, options: null, contentType: null, cancellationToken);
+ }
- return JsonSerializer.SerializeAsync<TValue>(response.Body, value, options, cancellationToken);
- }
+ /// <summary>
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// <c>application/json; charset=utf-8</c>.
+ /// </summary>
+ /// <param name="response">The response to write JSON to.</param>
+ /// <param name="value">The value to write as JSON.</param>
+ /// <param name="type">The type of object to write.</param>
+ /// <param name="options">The serializer options use when serializing the value.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsJsonAsync(
+ this HttpResponse response,
+ object? value,
+ Type type,
+ JsonSerializerOptions? options,
+ CancellationToken cancellationToken = default)
+ {
+ return response.WriteAsJsonAsync(value, type, options, contentType: null, cancellationToken);
+ }
- private static async Task WriteAsJsonAsyncSlow<TValue>(
- Stream body,
- TValue value,
- JsonSerializerOptions? options,
- CancellationToken cancellationToken)
+ /// <summary>
+ /// Write the specified value as JSON to the response body. The response content-type will be set to
+ /// the specified content-type.
+ /// </summary>
+ /// <param name="response">The response to write JSON to.</param>
+ /// <param name="value">The value to write as JSON.</param>
+ /// <param name="type">The type of object to write.</param>
+ /// <param name="options">The serializer options use when serializing the value.</param>
+ /// <param name="contentType">The content-type to set on the response.</param>
+ /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
+ /// <returns>The task object representing the asynchronous operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task WriteAsJsonAsync(
+ this HttpResponse response,
+ object? value,
+ Type type,
+ JsonSerializerOptions? options,
+ string? contentType,
+ CancellationToken cancellationToken = default)
+ {
+ if (response == null)
{
- try
- {
- await JsonSerializer.SerializeAsync<TValue>(body, value, options, cancellationToken);
- }
- catch (OperationCanceledException) { }
+ throw new ArgumentNullException(nameof(response));
}
-
- /// <summary>
- /// Write the specified value as JSON to the response body. The response content-type will be set to
- /// <c>application/json; charset=utf-8</c>.
- /// </summary>
- /// <param name="response">The response to write JSON to.</param>
- /// <param name="value">The value to write as JSON.</param>
- /// <param name="type">The type of object to write.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsJsonAsync(
- this HttpResponse response,
- object? value,
- Type type,
- CancellationToken cancellationToken = default)
+ if (type == null)
{
- return response.WriteAsJsonAsync(value, type, options: null, contentType: null, cancellationToken);
+ throw new ArgumentNullException(nameof(type));
}
- /// <summary>
- /// Write the specified value as JSON to the response body. The response content-type will be set to
- /// <c>application/json; charset=utf-8</c>.
- /// </summary>
- /// <param name="response">The response to write JSON to.</param>
- /// <param name="value">The value to write as JSON.</param>
- /// <param name="type">The type of object to write.</param>
- /// <param name="options">The serializer options use when serializing the value.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsJsonAsync(
- this HttpResponse response,
- object? value,
- Type type,
- JsonSerializerOptions? options,
- CancellationToken cancellationToken = default)
- {
- return response.WriteAsJsonAsync(value, type, options, contentType: null, cancellationToken);
- }
+ options ??= ResolveSerializerOptions(response.HttpContext);
- /// <summary>
- /// Write the specified value as JSON to the response body. The response content-type will be set to
- /// the specified content-type.
- /// </summary>
- /// <param name="response">The response to write JSON to.</param>
- /// <param name="value">The value to write as JSON.</param>
- /// <param name="type">The type of object to write.</param>
- /// <param name="options">The serializer options use when serializing the value.</param>
- /// <param name="contentType">The content-type to set on the response.</param>
- /// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the operation.</param>
- /// <returns>The task object representing the asynchronous operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task WriteAsJsonAsync(
- this HttpResponse response,
- object? value,
- Type type,
- JsonSerializerOptions? options,
- string? contentType,
- CancellationToken cancellationToken = default)
- {
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
- if (type == null)
- {
- throw new ArgumentNullException(nameof(type));
- }
-
- options ??= ResolveSerializerOptions(response.HttpContext);
-
- response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
-
- // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
- if (!cancellationToken.CanBeCanceled)
- {
- return WriteAsJsonAsyncSlow(response.Body, value, type, options, response.HttpContext.RequestAborted);
- }
-
- return JsonSerializer.SerializeAsync(response.Body, value, type, options, cancellationToken);
- }
+ response.ContentType = contentType ?? JsonConstants.JsonContentTypeWithCharset;
- private static async Task WriteAsJsonAsyncSlow(
- Stream body,
- object? value,
- Type type,
- JsonSerializerOptions? options,
- CancellationToken cancellationToken)
+ // if no user provided token, pass the RequestAborted token and ignore OperationCanceledException
+ if (!cancellationToken.CanBeCanceled)
{
- try
- {
- await JsonSerializer.SerializeAsync(body, value, type, options, cancellationToken);
- }
- catch (OperationCanceledException) { }
+ return WriteAsJsonAsyncSlow(response.Body, value, type, options, response.HttpContext.RequestAborted);
}
- private static JsonSerializerOptions ResolveSerializerOptions(HttpContext httpContext)
+ return JsonSerializer.SerializeAsync(response.Body, value, type, options, cancellationToken);
+ }
+
+ private static async Task WriteAsJsonAsyncSlow(
+ Stream body,
+ object? value,
+ Type type,
+ JsonSerializerOptions? options,
+ CancellationToken cancellationToken)
+ {
+ try
{
- // Attempt to resolve options from DI then fallback to default options
- return httpContext.RequestServices?.GetService<IOptions<JsonOptions>>()?.Value?.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
+ await JsonSerializer.SerializeAsync(body, value, type, options, cancellationToken);
}
+ catch (OperationCanceledException) { }
+ }
+
+ private static JsonSerializerOptions ResolveSerializerOptions(HttpContext httpContext)
+ {
+ // Attempt to resolve options from DI then fallback to default options
+ return httpContext.RequestServices?.GetService<IOptions<JsonOptions>>()?.Value?.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
}
}
diff --git a/src/Http/Http.Extensions/src/HttpValidationProblemDetails.cs b/src/Http/Http.Extensions/src/HttpValidationProblemDetails.cs
index be1543d916..5bab6886c7 100644
--- a/src/Http/Http.Extensions/src/HttpValidationProblemDetails.cs
+++ b/src/Http/Http.Extensions/src/HttpValidationProblemDetails.cs
@@ -4,40 +4,39 @@
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A <see cref="ProblemDetails"/> for validation errors.
+/// </summary>
+[JsonConverter(typeof(HttpValidationProblemDetailsJsonConverter))]
+public class HttpValidationProblemDetails : ProblemDetails
{
/// <summary>
- /// A <see cref="ProblemDetails"/> for validation errors.
+ /// Initializes a new instance of <see cref="HttpValidationProblemDetails"/>.
/// </summary>
- [JsonConverter(typeof(HttpValidationProblemDetailsJsonConverter))]
- public class HttpValidationProblemDetails : ProblemDetails
+ public HttpValidationProblemDetails()
+ : this(new Dictionary<string, string[]>(StringComparer.Ordinal))
{
- /// <summary>
- /// Initializes a new instance of <see cref="HttpValidationProblemDetails"/>.
- /// </summary>
- public HttpValidationProblemDetails()
- : this(new Dictionary<string, string[]>(StringComparer.Ordinal))
- {
- }
-
- /// <summary>
- /// Initializes a new instance of <see cref="HttpValidationProblemDetails"/> using the specified <paramref name="errors"/>.
- /// </summary>
- /// <param name="errors">The validation errors.</param>
- public HttpValidationProblemDetails(IDictionary<string, string[]> errors)
- : this(new Dictionary<string, string[]>(errors, StringComparer.Ordinal))
- {
- }
+ }
- private HttpValidationProblemDetails(Dictionary<string, string[]> errors)
- {
- Title = "One or more validation errors occurred.";
- Errors = errors;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="HttpValidationProblemDetails"/> using the specified <paramref name="errors"/>.
+ /// </summary>
+ /// <param name="errors">The validation errors.</param>
+ public HttpValidationProblemDetails(IDictionary<string, string[]> errors)
+ : this(new Dictionary<string, string[]>(errors, StringComparer.Ordinal))
+ {
+ }
- /// <summary>
- /// Gets the validation errors associated with this instance of <see cref="HttpValidationProblemDetails"/>.
- /// </summary>
- public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
+ private HttpValidationProblemDetails(Dictionary<string, string[]> errors)
+ {
+ Title = "One or more validation errors occurred.";
+ Errors = errors;
}
+
+ /// <summary>
+ /// Gets the validation errors associated with this instance of <see cref="HttpValidationProblemDetails"/>.
+ /// </summary>
+ public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
}
diff --git a/src/Http/Http.Extensions/src/JsonConstants.cs b/src/Http/Http.Extensions/src/JsonConstants.cs
index 4f66d5228e..4e8411b180 100644
--- a/src/Http/Http.Extensions/src/JsonConstants.cs
+++ b/src/Http/Http.Extensions/src/JsonConstants.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal static class JsonConstants
{
- internal static class JsonConstants
- {
- public const string JsonContentType = "application/json";
- public const string JsonContentTypeWithCharset = "application/json; charset=utf-8";
- }
+ public const string JsonContentType = "application/json";
+ public const string JsonContentTypeWithCharset = "application/json; charset=utf-8";
}
diff --git a/src/Http/Http.Extensions/src/JsonOptions.cs b/src/Http/Http.Extensions/src/JsonOptions.cs
index 2fa2cc87c5..220ad7938f 100644
--- a/src/Http/Http.Extensions/src/JsonOptions.cs
+++ b/src/Http/Http.Extensions/src/JsonOptions.cs
@@ -6,27 +6,26 @@ using System.Text.Json;
#nullable enable
-namespace Microsoft.AspNetCore.Http.Json
+namespace Microsoft.AspNetCore.Http.Json;
+
+/// <summary>
+/// Options to configure JSON serialization settings for <see cref="HttpRequestJsonExtensions"/>
+/// and <see cref="HttpResponseJsonExtensions"/>.
+/// </summary>
+public class JsonOptions
{
- /// <summary>
- /// Options to configure JSON serialization settings for <see cref="HttpRequestJsonExtensions"/>
- /// and <see cref="HttpResponseJsonExtensions"/>.
- /// </summary>
- public class JsonOptions
+ internal static readonly JsonSerializerOptions DefaultSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
- internal static readonly JsonSerializerOptions DefaultSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
- {
- // Web defaults don't use the relex JSON escaping encoder.
- //
- // Because these options are for producing content that is written directly to the request
- // (and not embedded in an HTML page for example), we can use UnsafeRelaxedJsonEscaping.
- Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
- };
+ // Web defaults don't use the relex JSON escaping encoder.
+ //
+ // Because these options are for producing content that is written directly to the request
+ // (and not embedded in an HTML page for example), we can use UnsafeRelaxedJsonEscaping.
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ };
- // Use a copy so the defaults are not modified.
- /// <summary>
- /// Gets the <see cref="JsonSerializerOptions"/>.
- /// </summary>
- public JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions(DefaultSerializerOptions);
- }
+ // Use a copy so the defaults are not modified.
+ /// <summary>
+ /// Gets the <see cref="JsonSerializerOptions"/>.
+ /// </summary>
+ public JsonSerializerOptions SerializerOptions { get; } = new JsonSerializerOptions(DefaultSerializerOptions);
}
diff --git a/src/Http/Http.Extensions/src/ProblemDetails.cs b/src/Http/Http.Extensions/src/ProblemDetails.cs
index d04ea9f429..9776f1205f 100644
--- a/src/Http/Http.Extensions/src/ProblemDetails.cs
+++ b/src/Http/Http.Extensions/src/ProblemDetails.cs
@@ -4,61 +4,60 @@
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Mvc;
+
+/// <summary>
+/// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
+/// </summary>
+[JsonConverter(typeof(ProblemDetailsJsonConverter))]
+public class ProblemDetails
{
/// <summary>
- /// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807.
+ /// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when
+ /// dereferenced, it provide human-readable documentation for the problem type
+ /// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
+ /// "about:blank".
/// </summary>
- [JsonConverter(typeof(ProblemDetailsJsonConverter))]
- public class ProblemDetails
- {
- /// <summary>
- /// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when
- /// dereferenced, it provide human-readable documentation for the problem type
- /// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
- /// "about:blank".
- /// </summary>
- [JsonPropertyName("type")]
- public string? Type { get; set; }
+ [JsonPropertyName("type")]
+ public string? Type { get; set; }
- /// <summary>
- /// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence
- /// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
- /// see[RFC7231], Section 3.4).
- /// </summary>
- [JsonPropertyName("title")]
- public string? Title { get; set; }
+ /// <summary>
+ /// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence
+ /// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
+ /// see[RFC7231], Section 3.4).
+ /// </summary>
+ [JsonPropertyName("title")]
+ public string? Title { get; set; }
- /// <summary>
- /// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
- /// </summary>
- [JsonPropertyName("status")]
- public int? Status { get; set; }
+ /// <summary>
+ /// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
+ /// </summary>
+ [JsonPropertyName("status")]
+ public int? Status { get; set; }
- /// <summary>
- /// A human-readable explanation specific to this occurrence of the problem.
- /// </summary>
- [JsonPropertyName("detail")]
- public string? Detail { get; set; }
+ /// <summary>
+ /// A human-readable explanation specific to this occurrence of the problem.
+ /// </summary>
+ [JsonPropertyName("detail")]
+ public string? Detail { get; set; }
- /// <summary>
- /// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
- /// </summary>
- [JsonPropertyName("instance")]
- public string? Instance { get; set; }
+ /// <summary>
+ /// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
+ /// </summary>
+ [JsonPropertyName("instance")]
+ public string? Instance { get; set; }
- /// <summary>
- /// Gets the <see cref="IDictionary{TKey, TValue}"/> for extension members.
- /// <para>
- /// Problem type definitions MAY extend the problem details object with additional members. Extension members appear in the same namespace as
- /// other members of a problem type.
- /// </para>
- /// </summary>
- /// <remarks>
- /// The round-tripping behavior for <see cref="Extensions"/> is determined by the implementation of the Input \ Output formatters.
- /// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters.
- /// </remarks>
- [JsonExtensionData]
- public IDictionary<string, object?> Extensions { get; } = new Dictionary<string, object?>(StringComparer.Ordinal);
- }
+ /// <summary>
+ /// Gets the <see cref="IDictionary{TKey, TValue}"/> for extension members.
+ /// <para>
+ /// Problem type definitions MAY extend the problem details object with additional members. Extension members appear in the same namespace as
+ /// other members of a problem type.
+ /// </para>
+ /// </summary>
+ /// <remarks>
+ /// The round-tripping behavior for <see cref="Extensions"/> is determined by the implementation of the Input \ Output formatters.
+ /// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters.
+ /// </remarks>
+ [JsonExtensionData]
+ public IDictionary<string, object?> Extensions { get; } = new Dictionary<string, object?>(StringComparer.Ordinal);
}
diff --git a/src/Http/Http.Extensions/src/QueryBuilder.cs b/src/Http/Http.Extensions/src/QueryBuilder.cs
index 930c0dd9ec..7175bd16ea 100644
--- a/src/Http/Http.Extensions/src/QueryBuilder.cs
+++ b/src/Http/Http.Extensions/src/QueryBuilder.cs
@@ -8,114 +8,113 @@ using System.Text;
using System.Text.Encodings.Web;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+// The IEnumerable interface is required for the collection initialization syntax: new QueryBuilder() { { "key", "value" } };
+/// <summary>
+/// Allows constructing a query string.
+/// </summary>
+public class QueryBuilder : IEnumerable<KeyValuePair<string, string>>
{
- // The IEnumerable interface is required for the collection initialization syntax: new QueryBuilder() { { "key", "value" } };
+ private readonly IList<KeyValuePair<string, string>> _params;
+
/// <summary>
- /// Allows constructing a query string.
+ /// Initializes a new instance of <see cref="QueryBuilder"/>.
/// </summary>
- public class QueryBuilder : IEnumerable<KeyValuePair<string, string>>
+ public QueryBuilder()
{
- private readonly IList<KeyValuePair<string, string>> _params;
-
- /// <summary>
- /// Initializes a new instance of <see cref="QueryBuilder"/>.
- /// </summary>
- public QueryBuilder()
- {
- _params = new List<KeyValuePair<string, string>>();
- }
-
- /// <summary>
- /// Initializes a new instance of <see cref="QueryBuilder"/>.
- /// </summary>
- /// <param name="parameters">The parameters to initialize the instance with.</param>
- public QueryBuilder(IEnumerable<KeyValuePair<string, string>> parameters)
- {
- _params = new List<KeyValuePair<string, string>>(parameters);
- }
+ _params = new List<KeyValuePair<string, string>>();
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="QueryBuilder"/>.
- /// </summary>
- /// <param name="parameters">The parameters to initialize the instance with.</param>
- public QueryBuilder(IEnumerable<KeyValuePair<string, StringValues>> parameters)
- : this(parameters.SelectMany(kvp => kvp.Value, (kvp, v) => KeyValuePair.Create(kvp.Key, v ?? string.Empty)))
- {
+ /// <summary>
+ /// Initializes a new instance of <see cref="QueryBuilder"/>.
+ /// </summary>
+ /// <param name="parameters">The parameters to initialize the instance with.</param>
+ public QueryBuilder(IEnumerable<KeyValuePair<string, string>> parameters)
+ {
+ _params = new List<KeyValuePair<string, string>>(parameters);
+ }
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="QueryBuilder"/>.
+ /// </summary>
+ /// <param name="parameters">The parameters to initialize the instance with.</param>
+ public QueryBuilder(IEnumerable<KeyValuePair<string, StringValues>> parameters)
+ : this(parameters.SelectMany(kvp => kvp.Value, (kvp, v) => KeyValuePair.Create(kvp.Key, v ?? string.Empty)))
+ {
- /// <summary>
- /// Adds a query string token to the instance.
- /// </summary>
- /// <param name="key">The query key.</param>
- /// <param name="values">The sequence of query values.</param>
- public void Add(string key, IEnumerable<string> values)
- {
- foreach (var value in values)
- {
- _params.Add(new KeyValuePair<string, string>(key, value));
- }
- }
+ }
- /// <summary>
- /// Adds a query string token to the instance.
- /// </summary>
- /// <param name="key">The query key.</param>
- /// <param name="value">The query value.</param>
- public void Add(string key, string value)
+ /// <summary>
+ /// Adds a query string token to the instance.
+ /// </summary>
+ /// <param name="key">The query key.</param>
+ /// <param name="values">The sequence of query values.</param>
+ public void Add(string key, IEnumerable<string> values)
+ {
+ foreach (var value in values)
{
_params.Add(new KeyValuePair<string, string>(key, value));
}
+ }
- /// <inheritdoc/>
- public override string ToString()
- {
- var builder = new StringBuilder();
- bool first = true;
- for (var 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();
- }
+ /// <summary>
+ /// Adds a query string token to the instance.
+ /// </summary>
+ /// <param name="key">The query key.</param>
+ /// <param name="value">The query value.</param>
+ public void Add(string key, string value)
+ {
+ _params.Add(new KeyValuePair<string, string>(key, value));
+ }
- /// <summary>
- /// Constructs a <see cref="QueryString"/> from this <see cref="QueryBuilder"/>.
- /// </summary>
- /// <returns>The <see cref="QueryString"/>.</returns>
- public QueryString ToQueryString()
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ var builder = new StringBuilder();
+ bool first = true;
+ for (var i = 0; i < _params.Count; i++)
{
- return new QueryString(ToString());
+ 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));
}
- /// <inheritdoc/>
- public override int GetHashCode()
- {
- return ToQueryString().GetHashCode();
- }
+ return builder.ToString();
+ }
- /// <inheritdoc/>
- public override bool Equals(object? obj)
- {
- return ToQueryString().Equals(obj);
- }
+ /// <summary>
+ /// Constructs a <see cref="QueryString"/> from this <see cref="QueryBuilder"/>.
+ /// </summary>
+ /// <returns>The <see cref="QueryString"/>.</returns>
+ public QueryString ToQueryString()
+ {
+ return new QueryString(ToString());
+ }
- /// <inheritdoc/>
- public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
- {
- return _params.GetEnumerator();
- }
+ /// <inheritdoc/>
+ public override int GetHashCode()
+ {
+ return ToQueryString().GetHashCode();
+ }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return _params.GetEnumerator();
- }
+ /// <inheritdoc/>
+ public override bool Equals(object? obj)
+ {
+ return ToQueryString().Equals(obj);
+ }
+
+ /// <inheritdoc/>
+ public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
+ {
+ return _params.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _params.GetEnumerator();
}
}
diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
index 227be2f576..5864b8bd27 100644
--- a/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
+++ b/src/Http/Http.Extensions/src/RequestDelegateFactory.cs
@@ -15,549 +15,509 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Creates <see cref="RequestDelegate"/> implementations from <see cref="Delegate"/> request handlers.
+/// </summary>
+public static partial class RequestDelegateFactory
{
+ private static readonly ParameterBindingMethodCache ParameterBindingMethodCache = new();
+
+ private static readonly MethodInfo ExecuteTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTask), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteValueTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteValueTaskMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteValueTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteTaskResultOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteValueResultTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo ExecuteObjectReturnMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteObjectReturn), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo GetRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
+ private static readonly MethodInfo GetServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
+ private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteResultWriteResponse), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteWriteStringResponseAsync), BindingFlags.NonPublic | BindingFlags.Static)!;
+ private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = GetMethodInfo<Func<HttpResponse, object, Task>>((response, value) => HttpResponseJsonExtensions.WriteAsJsonAsync(response, value, default));
+
+ private static readonly MethodInfo LogParameterBindingFailedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, sourceValue, shouldThrow) =>
+ Log.ParameterBindingFailed(httpContext, parameterType, parameterName, sourceValue, shouldThrow));
+ private static readonly MethodInfo LogRequiredParameterNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, source, shouldThrow) =>
+ Log.RequiredParameterNotProvided(httpContext, parameterType, parameterName, source, shouldThrow));
+ private static readonly MethodInfo LogImplicitBodyNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, bool>>((httpContext, parameterName, shouldThrow) =>
+ Log.ImplicitBodyNotProvided(httpContext, parameterName, shouldThrow));
+
+ private static readonly ParameterExpression TargetExpr = Expression.Parameter(typeof(object), "target");
+ private static readonly ParameterExpression BodyValueExpr = Expression.Parameter(typeof(object), "bodyValue");
+ private static readonly ParameterExpression WasParamCheckFailureExpr = Expression.Variable(typeof(bool), "wasParamCheckFailure");
+ private static readonly ParameterExpression BoundValuesArrayExpr = Expression.Parameter(typeof(object[]), "boundValues");
+
+ private static readonly ParameterExpression HttpContextExpr = ParameterBindingMethodCache.HttpContextExpr;
+ private static readonly MemberExpression RequestServicesExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestServices))!);
+ private static readonly MemberExpression HttpRequestExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Request))!);
+ private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
+ private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestAborted))!);
+ private static readonly MemberExpression UserExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.User))!);
+ private static readonly MemberExpression RouteValuesExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.RouteValues))!);
+ private static readonly MemberExpression QueryExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Query))!);
+ private static readonly MemberExpression HeadersExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Headers))!);
+ private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
+ private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
+
+ private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.TempSourceStringExpr;
+ private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
+ private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
+ private static readonly string[] DefaultAcceptsContentType = new[] { "application/json" };
+
/// <summary>
- /// Creates <see cref="RequestDelegate"/> implementations from <see cref="Delegate"/> request handlers.
+ /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="handler"/>.
/// </summary>
- public static partial class RequestDelegateFactory
- {
- private static readonly ParameterBindingMethodCache ParameterBindingMethodCache = new();
-
- private static readonly MethodInfo ExecuteTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTask), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteValueTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfT), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteValueTaskMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTask), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteValueTaskOfStringMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskOfString), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteTaskResultOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteValueResultTaskOfTMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteValueTaskResult), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo ExecuteObjectReturnMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteObjectReturn), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo GetRequiredServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetRequiredService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
- private static readonly MethodInfo GetServiceMethod = typeof(ServiceProviderServiceExtensions).GetMethod(nameof(ServiceProviderServiceExtensions.GetService), BindingFlags.Public | BindingFlags.Static, new Type[] { typeof(IServiceProvider) })!;
- private static readonly MethodInfo ResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteResultWriteResponse), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo StringResultWriteResponseAsyncMethod = typeof(RequestDelegateFactory).GetMethod(nameof(ExecuteWriteStringResponseAsync), BindingFlags.NonPublic | BindingFlags.Static)!;
- private static readonly MethodInfo JsonResultWriteResponseAsyncMethod = GetMethodInfo<Func<HttpResponse, object, Task>>((response, value) => HttpResponseJsonExtensions.WriteAsJsonAsync(response, value, default));
-
- private static readonly MethodInfo LogParameterBindingFailedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, sourceValue, shouldThrow) =>
- Log.ParameterBindingFailed(httpContext, parameterType, parameterName, sourceValue, shouldThrow));
- private static readonly MethodInfo LogRequiredParameterNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, string, string, bool>>((httpContext, parameterType, parameterName, source, shouldThrow) =>
- Log.RequiredParameterNotProvided(httpContext, parameterType, parameterName, source, shouldThrow));
- private static readonly MethodInfo LogImplicitBodyNotProvidedMethod = GetMethodInfo<Action<HttpContext, string, bool>>((httpContext, parameterName, shouldThrow) =>
- Log.ImplicitBodyNotProvided(httpContext, parameterName, shouldThrow));
-
- private static readonly ParameterExpression TargetExpr = Expression.Parameter(typeof(object), "target");
- private static readonly ParameterExpression BodyValueExpr = Expression.Parameter(typeof(object), "bodyValue");
- private static readonly ParameterExpression WasParamCheckFailureExpr = Expression.Variable(typeof(bool), "wasParamCheckFailure");
- private static readonly ParameterExpression BoundValuesArrayExpr = Expression.Parameter(typeof(object[]), "boundValues");
-
- private static readonly ParameterExpression HttpContextExpr = ParameterBindingMethodCache.HttpContextExpr;
- private static readonly MemberExpression RequestServicesExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestServices))!);
- private static readonly MemberExpression HttpRequestExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Request))!);
- private static readonly MemberExpression HttpResponseExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.Response))!);
- private static readonly MemberExpression RequestAbortedExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.RequestAborted))!);
- private static readonly MemberExpression UserExpr = Expression.Property(HttpContextExpr, typeof(HttpContext).GetProperty(nameof(HttpContext.User))!);
- private static readonly MemberExpression RouteValuesExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.RouteValues))!);
- private static readonly MemberExpression QueryExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Query))!);
- private static readonly MemberExpression HeadersExpr = Expression.Property(HttpRequestExpr, typeof(HttpRequest).GetProperty(nameof(HttpRequest.Headers))!);
- private static readonly MemberExpression StatusCodeExpr = Expression.Property(HttpResponseExpr, typeof(HttpResponse).GetProperty(nameof(HttpResponse.StatusCode))!);
- private static readonly MemberExpression CompletedTaskExpr = Expression.Property(null, (PropertyInfo)GetMemberInfo<Func<Task>>(() => Task.CompletedTask));
-
- private static readonly ParameterExpression TempSourceStringExpr = ParameterBindingMethodCache.TempSourceStringExpr;
- private static readonly BinaryExpression TempSourceStringNotNullExpr = Expression.NotEqual(TempSourceStringExpr, Expression.Constant(null));
- private static readonly BinaryExpression TempSourceStringNullExpr = Expression.Equal(TempSourceStringExpr, Expression.Constant(null));
- private static readonly string[] DefaultAcceptsContentType = new[] { "application/json" };
-
- /// <summary>
- /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="handler"/>.
- /// </summary>
- /// <param name="handler">A request handler with any number of custom parameters that often produces a response with its return value.</param>
- /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
- /// <returns>The <see cref="RequestDelegateResult"/>.</returns>
+ /// <param name="handler">A request handler with any number of custom parameters that often produces a response with its return value.</param>
+ /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
+ /// <returns>The <see cref="RequestDelegateResult"/>.</returns>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
- public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null)
+ public static RequestDelegateResult Create(Delegate handler, RequestDelegateFactoryOptions? options = null)
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+ {
+ if (handler is null)
{
- if (handler is null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
+ throw new ArgumentNullException(nameof(handler));
+ }
- var targetExpression = handler.Target switch
- {
- object => Expression.Convert(TargetExpr, handler.Target.GetType()),
- null => null,
- };
+ var targetExpression = handler.Target switch
+ {
+ object => Expression.Convert(TargetExpr, handler.Target.GetType()),
+ null => null,
+ };
- var factoryContext = CreateFactoryContext(options);
- var targetableRequestDelegate = CreateTargetableRequestDelegate(handler.Method, targetExpression, factoryContext);
+ var factoryContext = CreateFactoryContext(options);
+ var targetableRequestDelegate = CreateTargetableRequestDelegate(handler.Method, targetExpression, factoryContext);
- return new RequestDelegateResult(httpContext => targetableRequestDelegate(handler.Target, httpContext), factoryContext.Metadata);
- }
+ return new RequestDelegateResult(httpContext => targetableRequestDelegate(handler.Target, httpContext), factoryContext.Metadata);
+ }
- /// <summary>
- /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
- /// </summary>
- /// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
- /// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
- /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
- /// <returns>The <see cref="RequestDelegate"/>.</returns>
+ /// <summary>
+ /// Creates a <see cref="RequestDelegate"/> implementation for <paramref name="methodInfo"/>.
+ /// </summary>
+ /// <param name="methodInfo">A request handler with any number of custom parameters that often produces a response with its return value.</param>
+ /// <param name="targetFactory">Creates the <see langword="this"/> for the non-static method.</param>
+ /// <param name="options">The <see cref="RequestDelegateFactoryOptions"/> used to configure the behavior of the handler.</param>
+ /// <returns>The <see cref="RequestDelegate"/>.</returns>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
- public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory = null, RequestDelegateFactoryOptions? options = null)
+ public static RequestDelegateResult Create(MethodInfo methodInfo, Func<HttpContext, object>? targetFactory = null, RequestDelegateFactoryOptions? options = null)
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+ {
+ if (methodInfo is null)
{
- if (methodInfo is null)
- {
- throw new ArgumentNullException(nameof(methodInfo));
- }
+ throw new ArgumentNullException(nameof(methodInfo));
+ }
- if (methodInfo.DeclaringType is null)
- {
- throw new ArgumentException($"{nameof(methodInfo)} does not have a declaring type.");
- }
+ if (methodInfo.DeclaringType is null)
+ {
+ throw new ArgumentException($"{nameof(methodInfo)} does not have a declaring type.");
+ }
- var factoryContext = CreateFactoryContext(options);
+ var factoryContext = CreateFactoryContext(options);
- if (targetFactory is null)
+ if (targetFactory is null)
+ {
+ if (methodInfo.IsStatic)
{
- if (methodInfo.IsStatic)
- {
- var untargetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression: null, factoryContext);
-
- return new RequestDelegateResult(httpContext => untargetableRequestDelegate(null, httpContext), factoryContext.Metadata);
- }
+ var untargetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression: null, factoryContext);
- targetFactory = context => Activator.CreateInstance(methodInfo.DeclaringType)!;
+ return new RequestDelegateResult(httpContext => untargetableRequestDelegate(null, httpContext), factoryContext.Metadata);
}
- var targetExpression = Expression.Convert(TargetExpr, methodInfo.DeclaringType);
- var targetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression, factoryContext);
-
- return new RequestDelegateResult(httpContext => targetableRequestDelegate(targetFactory(httpContext), httpContext), factoryContext.Metadata);
+ targetFactory = context => Activator.CreateInstance(methodInfo.DeclaringType)!;
}
- private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions? options) =>
- new()
- {
- ServiceProviderIsService = options?.ServiceProvider?.GetService<IServiceProviderIsService>(),
- RouteParameters = options?.RouteParameterNames?.ToList(),
- ThrowOnBadRequest = options?.ThrowOnBadRequest ?? false,
- DisableInferredFromBody = options?.DisableInferBodyFromParameters ?? false,
- };
+ var targetExpression = Expression.Convert(TargetExpr, methodInfo.DeclaringType);
+ var targetableRequestDelegate = CreateTargetableRequestDelegate(methodInfo, targetExpression, factoryContext);
- private static Func<object?, HttpContext, Task> CreateTargetableRequestDelegate(MethodInfo methodInfo, Expression? targetExpression, FactoryContext factoryContext)
+ return new RequestDelegateResult(httpContext => targetableRequestDelegate(targetFactory(httpContext), httpContext), factoryContext.Metadata);
+ }
+
+ private static FactoryContext CreateFactoryContext(RequestDelegateFactoryOptions? options) =>
+ new()
{
- // Non void return type
+ ServiceProviderIsService = options?.ServiceProvider?.GetService<IServiceProviderIsService>(),
+ RouteParameters = options?.RouteParameterNames?.ToList(),
+ ThrowOnBadRequest = options?.ThrowOnBadRequest ?? false,
+ DisableInferredFromBody = options?.DisableInferBodyFromParameters ?? false,
+ };
- // Task Invoke(HttpContext httpContext)
- // {
- // // Action parameters are bound from the request, services, etc... based on attribute and type information.
- // return ExecuteTask(handler(...), httpContext);
- // }
+ private static Func<object?, HttpContext, Task> CreateTargetableRequestDelegate(MethodInfo methodInfo, Expression? targetExpression, FactoryContext factoryContext)
+ {
+ // Non void return type
- // void return type
+ // Task Invoke(HttpContext httpContext)
+ // {
+ // // Action parameters are bound from the request, services, etc... based on attribute and type information.
+ // return ExecuteTask(handler(...), httpContext);
+ // }
- // Task Invoke(HttpContext httpContext)
- // {
- // handler(...);
- // return default;
- // }
+ // void return type
- var arguments = CreateArguments(methodInfo.GetParameters(), factoryContext);
+ // Task Invoke(HttpContext httpContext)
+ // {
+ // handler(...);
+ // return default;
+ // }
- var responseWritingMethodCall = factoryContext.ParamCheckExpressions.Count > 0 ?
- CreateParamCheckingResponseWritingMethodCall(methodInfo, targetExpression, arguments, factoryContext) :
- CreateResponseWritingMethodCall(methodInfo, targetExpression, arguments);
+ var arguments = CreateArguments(methodInfo.GetParameters(), factoryContext);
- if (factoryContext.UsingTempSourceString)
- {
- responseWritingMethodCall = Expression.Block(new[] { TempSourceStringExpr }, responseWritingMethodCall);
- }
+ var responseWritingMethodCall = factoryContext.ParamCheckExpressions.Count > 0 ?
+ CreateParamCheckingResponseWritingMethodCall(methodInfo, targetExpression, arguments, factoryContext) :
+ CreateResponseWritingMethodCall(methodInfo, targetExpression, arguments);
- return HandleRequestBodyAndCompileRequestDelegate(responseWritingMethodCall, factoryContext);
+ if (factoryContext.UsingTempSourceString)
+ {
+ responseWritingMethodCall = Expression.Block(new[] { TempSourceStringExpr }, responseWritingMethodCall);
}
- private static Expression[] CreateArguments(ParameterInfo[]? parameters, FactoryContext factoryContext)
+ return HandleRequestBodyAndCompileRequestDelegate(responseWritingMethodCall, factoryContext);
+ }
+
+ private static Expression[] CreateArguments(ParameterInfo[]? parameters, FactoryContext factoryContext)
+ {
+ if (parameters is null || parameters.Length == 0)
{
- if (parameters is null || parameters.Length == 0)
- {
- return Array.Empty<Expression>();
- }
+ return Array.Empty<Expression>();
+ }
- var args = new Expression[parameters.Length];
+ var args = new Expression[parameters.Length];
- for (var i = 0; i < parameters.Length; i++)
- {
- args[i] = CreateArgument(parameters[i], factoryContext);
- }
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ args[i] = CreateArgument(parameters[i], factoryContext);
+ }
- if (factoryContext.HasInferredBody && factoryContext.DisableInferredFromBody)
- {
- var errorMessage = BuildErrorMessageForInferredBodyParameter(factoryContext);
- throw new InvalidOperationException(errorMessage);
- }
- if (factoryContext.HasMultipleBodyParameters)
- {
- var errorMessage = BuildErrorMessageForMultipleBodyParameters(factoryContext);
- throw new InvalidOperationException(errorMessage);
- }
+ if (factoryContext.HasInferredBody && factoryContext.DisableInferredFromBody)
+ {
+ var errorMessage = BuildErrorMessageForInferredBodyParameter(factoryContext);
+ throw new InvalidOperationException(errorMessage);
+ }
+ if (factoryContext.HasMultipleBodyParameters)
+ {
+ var errorMessage = BuildErrorMessageForMultipleBodyParameters(factoryContext);
+ throw new InvalidOperationException(errorMessage);
+ }
- return args;
+ return args;
+ }
+
+ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext factoryContext)
+ {
+ if (parameter.Name is null)
+ {
+ throw new InvalidOperationException($"Encountered a parameter of type '{parameter.ParameterType}' without a name. Parameters must have a name.");
}
- private static Expression CreateArgument(ParameterInfo parameter, FactoryContext factoryContext)
+ var parameterCustomAttributes = parameter.GetCustomAttributes();
+
+ if (parameterCustomAttributes.OfType<IFromRouteMetadata>().FirstOrDefault() is { } routeAttribute)
{
- if (parameter.Name is null)
+ var routeName = routeAttribute.Name ?? parameter.Name;
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteAttribute);
+ if (factoryContext.RouteParameters is { } routeParams && !routeParams.Contains(routeName, StringComparer.OrdinalIgnoreCase))
{
- throw new InvalidOperationException($"Encountered a parameter of type '{parameter.ParameterType}' without a name. Parameters must have a name.");
+ throw new InvalidOperationException($"'{routeName}' is not a route parameter.");
}
- var parameterCustomAttributes = parameter.GetCustomAttributes();
-
- if (parameterCustomAttributes.OfType<IFromRouteMetadata>().FirstOrDefault() is { } routeAttribute)
+ return BindParameterFromProperty(parameter, RouteValuesExpr, routeName, factoryContext, "route");
+ }
+ else if (parameterCustomAttributes.OfType<IFromQueryMetadata>().FirstOrDefault() is { } queryAttribute)
+ {
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryAttribute);
+ return BindParameterFromProperty(parameter, QueryExpr, queryAttribute.Name ?? parameter.Name, factoryContext, "query string");
+ }
+ else if (parameterCustomAttributes.OfType<IFromHeaderMetadata>().FirstOrDefault() is { } headerAttribute)
+ {
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.HeaderAttribute);
+ return BindParameterFromProperty(parameter, HeadersExpr, headerAttribute.Name ?? parameter.Name, factoryContext, "header");
+ }
+ else if (parameterCustomAttributes.OfType<IFromBodyMetadata>().FirstOrDefault() is { } bodyAttribute)
+ {
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyAttribute);
+ return BindParameterFromBody(parameter, bodyAttribute.AllowEmpty, factoryContext);
+ }
+ else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)))
+ {
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceAttribute);
+ return BindParameterFromService(parameter, factoryContext);
+ }
+ else if (parameter.ParameterType == typeof(HttpContext))
+ {
+ return HttpContextExpr;
+ }
+ else if (parameter.ParameterType == typeof(HttpRequest))
+ {
+ return HttpRequestExpr;
+ }
+ else if (parameter.ParameterType == typeof(HttpResponse))
+ {
+ return HttpResponseExpr;
+ }
+ else if (parameter.ParameterType == typeof(ClaimsPrincipal))
+ {
+ return UserExpr;
+ }
+ else if (parameter.ParameterType == typeof(CancellationToken))
+ {
+ return RequestAbortedExpr;
+ }
+ else if (ParameterBindingMethodCache.HasBindAsyncMethod(parameter))
+ {
+ return BindParameterFromBindAsync(parameter, factoryContext);
+ }
+ else if (parameter.ParameterType == typeof(string) || ParameterBindingMethodCache.HasTryParseMethod(parameter))
+ {
+ // 1. We bind from route values only, if route parameters are non-null and the parameter name is in that set.
+ // 2. We bind from query only, if route parameters are non-null and the parameter name is NOT in that set.
+ // 3. Otherwise, we fallback to route or query if route parameters is null (it means we don't know what route parameters are defined). This case only happens
+ // when RDF.Create is manually invoked.
+ if (factoryContext.RouteParameters is { } routeParams)
{
- var routeName = routeAttribute.Name ?? parameter.Name;
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteAttribute);
- if (factoryContext.RouteParameters is { } routeParams && !routeParams.Contains(routeName, StringComparer.OrdinalIgnoreCase))
+ if (routeParams.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase))
{
- throw new InvalidOperationException($"'{routeName}' is not a route parameter.");
+ // We're in the fallback case and we have a parameter and route parameter match so don't fallback
+ // to query string in this case
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteParameter);
+ return BindParameterFromProperty(parameter, RouteValuesExpr, parameter.Name, factoryContext, "route");
}
-
- return BindParameterFromProperty(parameter, RouteValuesExpr, routeName, factoryContext, "route");
- }
- else if (parameterCustomAttributes.OfType<IFromQueryMetadata>().FirstOrDefault() is { } queryAttribute)
- {
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryAttribute);
- return BindParameterFromProperty(parameter, QueryExpr, queryAttribute.Name ?? parameter.Name, factoryContext, "query string");
- }
- else if (parameterCustomAttributes.OfType<IFromHeaderMetadata>().FirstOrDefault() is { } headerAttribute)
- {
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.HeaderAttribute);
- return BindParameterFromProperty(parameter, HeadersExpr, headerAttribute.Name ?? parameter.Name, factoryContext, "header");
- }
- else if (parameterCustomAttributes.OfType<IFromBodyMetadata>().FirstOrDefault() is { } bodyAttribute)
- {
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyAttribute);
- return BindParameterFromBody(parameter, bodyAttribute.AllowEmpty, factoryContext);
- }
- else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)))
- {
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceAttribute);
- return BindParameterFromService(parameter, factoryContext);
- }
- else if (parameter.ParameterType == typeof(HttpContext))
- {
- return HttpContextExpr;
- }
- else if (parameter.ParameterType == typeof(HttpRequest))
- {
- return HttpRequestExpr;
- }
- else if (parameter.ParameterType == typeof(HttpResponse))
- {
- return HttpResponseExpr;
- }
- else if (parameter.ParameterType == typeof(ClaimsPrincipal))
- {
- return UserExpr;
- }
- else if (parameter.ParameterType == typeof(CancellationToken))
- {
- return RequestAbortedExpr;
- }
- else if (ParameterBindingMethodCache.HasBindAsyncMethod(parameter))
- {
- return BindParameterFromBindAsync(parameter, factoryContext);
- }
- else if (parameter.ParameterType == typeof(string) || ParameterBindingMethodCache.HasTryParseMethod(parameter))
- {
- // 1. We bind from route values only, if route parameters are non-null and the parameter name is in that set.
- // 2. We bind from query only, if route parameters are non-null and the parameter name is NOT in that set.
- // 3. Otherwise, we fallback to route or query if route parameters is null (it means we don't know what route parameters are defined). This case only happens
- // when RDF.Create is manually invoked.
- if (factoryContext.RouteParameters is { } routeParams)
+ else
{
- if (routeParams.Contains(parameter.Name, StringComparer.OrdinalIgnoreCase))
- {
- // We're in the fallback case and we have a parameter and route parameter match so don't fallback
- // to query string in this case
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteParameter);
- return BindParameterFromProperty(parameter, RouteValuesExpr, parameter.Name, factoryContext, "route");
- }
- else
- {
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryStringParameter);
- return BindParameterFromProperty(parameter, QueryExpr, parameter.Name, factoryContext, "query string");
- }
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.QueryStringParameter);
+ return BindParameterFromProperty(parameter, QueryExpr, parameter.Name, factoryContext, "query string");
}
-
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteOrQueryStringParameter);
- return BindParameterFromRouteValueOrQueryString(parameter, parameter.Name, factoryContext);
}
- else
+
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.RouteOrQueryStringParameter);
+ return BindParameterFromRouteValueOrQueryString(parameter, parameter.Name, factoryContext);
+ }
+ else
+ {
+ if (factoryContext.ServiceProviderIsService is IServiceProviderIsService serviceProviderIsService)
{
- if (factoryContext.ServiceProviderIsService is IServiceProviderIsService serviceProviderIsService)
+ if (serviceProviderIsService.IsService(parameter.ParameterType))
{
- if (serviceProviderIsService.IsService(parameter.ParameterType))
- {
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceParameter);
- return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
- }
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.ServiceParameter);
+ return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
}
-
- factoryContext.HasInferredBody = true;
- factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyParameter);
- return BindParameterFromBody(parameter, allowEmpty: false, factoryContext);
}
+
+ factoryContext.HasInferredBody = true;
+ factoryContext.TrackedParameters.Add(parameter.Name, RequestDelegateFactoryConstants.BodyParameter);
+ return BindParameterFromBody(parameter, allowEmpty: false, factoryContext);
}
+ }
- private static Expression CreateMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments) =>
- target is null ?
- Expression.Call(methodInfo, arguments) :
- Expression.Call(target, methodInfo, arguments);
+ private static Expression CreateMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments) =>
+ target is null ?
+ Expression.Call(methodInfo, arguments) :
+ Expression.Call(target, methodInfo, arguments);
+
+ private static Expression CreateResponseWritingMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments)
+ {
+ var callMethod = CreateMethodCall(methodInfo, target, arguments);
+ return AddResponseWritingToMethodCall(callMethod, methodInfo.ReturnType);
+ }
- private static Expression CreateResponseWritingMethodCall(MethodInfo methodInfo, Expression? target, Expression[] arguments)
+ // If we're calling TryParse or validating parameter optionality and
+ // wasParamCheckFailure indicates it failed, set a 400 StatusCode instead of calling the method.
+ private static Expression CreateParamCheckingResponseWritingMethodCall(
+ MethodInfo methodInfo, Expression? target, Expression[] arguments, FactoryContext factoryContext)
+ {
+ // {
+ // string tempSourceString;
+ // bool wasParamCheckFailure = false;
+ //
+ // // Assume "int param1" is the first parameter, "[FromRoute] int? param2 = 42" is the second parameter ...
+ // int param1_local;
+ // int? param2_local;
+ // // ...
+ //
+ // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
+ //
+ // if (tempSourceString != null)
+ // {
+ // if (!int.TryParse(tempSourceString, out param1_local))
+ // {
+ // wasParamCheckFailure = true;
+ // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
+ // }
+ // }
+ //
+ // tempSourceString = httpContext.RouteValue["param2"];
+ // // ...
+ //
+ // return wasParamCheckFailure ?
+ // {
+ // httpContext.Response.StatusCode = 400;
+ // return Task.CompletedTask;
+ // } :
+ // {
+ // // Logic generated by AddResponseWritingToMethodCall() that calls handler(param1_local, param2_local, ...)
+ // };
+ // }
+
+ var localVariables = new ParameterExpression[factoryContext.ExtraLocals.Count + 1];
+ var checkParamAndCallMethod = new Expression[factoryContext.ParamCheckExpressions.Count + 1];
+
+ for (var i = 0; i < factoryContext.ExtraLocals.Count; i++)
{
- var callMethod = CreateMethodCall(methodInfo, target, arguments);
- return AddResponseWritingToMethodCall(callMethod, methodInfo.ReturnType);
+ localVariables[i] = factoryContext.ExtraLocals[i];
}
- // If we're calling TryParse or validating parameter optionality and
- // wasParamCheckFailure indicates it failed, set a 400 StatusCode instead of calling the method.
- private static Expression CreateParamCheckingResponseWritingMethodCall(
- MethodInfo methodInfo, Expression? target, Expression[] arguments, FactoryContext factoryContext)
+ for (var i = 0; i < factoryContext.ParamCheckExpressions.Count; i++)
{
- // {
- // string tempSourceString;
- // bool wasParamCheckFailure = false;
- //
- // // Assume "int param1" is the first parameter, "[FromRoute] int? param2 = 42" is the second parameter ...
- // int param1_local;
- // int? param2_local;
- // // ...
- //
- // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
- //
- // if (tempSourceString != null)
- // {
- // if (!int.TryParse(tempSourceString, out param1_local))
- // {
- // wasParamCheckFailure = true;
- // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
- // }
- // }
- //
- // tempSourceString = httpContext.RouteValue["param2"];
- // // ...
- //
- // return wasParamCheckFailure ?
- // {
- // httpContext.Response.StatusCode = 400;
- // return Task.CompletedTask;
- // } :
- // {
- // // Logic generated by AddResponseWritingToMethodCall() that calls handler(param1_local, param2_local, ...)
- // };
- // }
-
- var localVariables = new ParameterExpression[factoryContext.ExtraLocals.Count + 1];
- var checkParamAndCallMethod = new Expression[factoryContext.ParamCheckExpressions.Count + 1];
-
- for (var i = 0; i < factoryContext.ExtraLocals.Count; i++)
- {
- localVariables[i] = factoryContext.ExtraLocals[i];
- }
+ checkParamAndCallMethod[i] = factoryContext.ParamCheckExpressions[i];
+ }
- for (var i = 0; i < factoryContext.ParamCheckExpressions.Count; i++)
- {
- checkParamAndCallMethod[i] = factoryContext.ParamCheckExpressions[i];
- }
+ localVariables[factoryContext.ExtraLocals.Count] = WasParamCheckFailureExpr;
- localVariables[factoryContext.ExtraLocals.Count] = WasParamCheckFailureExpr;
+ var set400StatusAndReturnCompletedTask = Expression.Block(
+ Expression.Assign(StatusCodeExpr, Expression.Constant(400)),
+ CompletedTaskExpr);
- var set400StatusAndReturnCompletedTask = Expression.Block(
- Expression.Assign(StatusCodeExpr, Expression.Constant(400)),
- CompletedTaskExpr);
+ var methodCall = CreateMethodCall(methodInfo, target, arguments);
- var methodCall = CreateMethodCall(methodInfo, target, arguments);
+ var checkWasParamCheckFailure = Expression.Condition(WasParamCheckFailureExpr,
+ set400StatusAndReturnCompletedTask,
+ AddResponseWritingToMethodCall(methodCall, methodInfo.ReturnType));
- var checkWasParamCheckFailure = Expression.Condition(WasParamCheckFailureExpr,
- set400StatusAndReturnCompletedTask,
- AddResponseWritingToMethodCall(methodCall, methodInfo.ReturnType));
+ checkParamAndCallMethod[factoryContext.ParamCheckExpressions.Count] = checkWasParamCheckFailure;
- checkParamAndCallMethod[factoryContext.ParamCheckExpressions.Count] = checkWasParamCheckFailure;
+ return Expression.Block(localVariables, checkParamAndCallMethod);
+ }
- return Expression.Block(localVariables, checkParamAndCallMethod);
+ private static Expression AddResponseWritingToMethodCall(Expression methodCall, Type returnType)
+ {
+ // Exact request delegate match
+ if (returnType == typeof(void))
+ {
+ return Expression.Block(methodCall, CompletedTaskExpr);
}
-
- private static Expression AddResponseWritingToMethodCall(Expression methodCall, Type returnType)
+ else if (returnType == typeof(object))
{
- // Exact request delegate match
- if (returnType == typeof(void))
- {
- return Expression.Block(methodCall, CompletedTaskExpr);
- }
- else if (returnType == typeof(object))
- {
- return Expression.Call(ExecuteObjectReturnMethod, methodCall, HttpContextExpr);
- }
- else if (returnType == typeof(ValueTask<object>))
+ return Expression.Call(ExecuteObjectReturnMethod, methodCall, HttpContextExpr);
+ }
+ else if (returnType == typeof(ValueTask<object>))
+ {
+ // REVIEW: We can avoid this box if it becomes a performance issue
+ var box = Expression.TypeAs(methodCall, typeof(object));
+ return Expression.Call(ExecuteObjectReturnMethod, box, HttpContextExpr);
+ }
+ else if (returnType == typeof(Task<object>))
+ {
+ var convert = Expression.Convert(methodCall, typeof(object));
+ return Expression.Call(ExecuteObjectReturnMethod, convert, HttpContextExpr);
+ }
+ else if (AwaitableInfo.IsTypeAwaitable(returnType, out _))
+ {
+ if (returnType == typeof(Task))
{
- // REVIEW: We can avoid this box if it becomes a performance issue
- var box = Expression.TypeAs(methodCall, typeof(object));
- return Expression.Call(ExecuteObjectReturnMethod, box, HttpContextExpr);
+ return methodCall;
}
- else if (returnType == typeof(Task<object>))
+ else if (returnType == typeof(ValueTask))
{
- var convert = Expression.Convert(methodCall, typeof(object));
- return Expression.Call(ExecuteObjectReturnMethod, convert, HttpContextExpr);
+ return Expression.Call(
+ ExecuteValueTaskMethod,
+ methodCall);
}
- else if (AwaitableInfo.IsTypeAwaitable(returnType, out _))
+ else if (returnType.IsGenericType &&
+ returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
- if (returnType == typeof(Task))
+ var typeArg = returnType.GetGenericArguments()[0];
+
+ if (typeof(IResult).IsAssignableFrom(typeArg))
{
- return methodCall;
+ return Expression.Call(
+ ExecuteTaskResultOfTMethod.MakeGenericMethod(typeArg),
+ methodCall,
+ HttpContextExpr);
}
- else if (returnType == typeof(ValueTask))
+ // ExecuteTask<T>(handler(..), httpContext);
+ else if (typeArg == typeof(string))
{
return Expression.Call(
- ExecuteValueTaskMethod,
- methodCall);
+ ExecuteTaskOfStringMethod,
+ methodCall,
+ HttpContextExpr);
}
- else if (returnType.IsGenericType &&
- returnType.GetGenericTypeDefinition() == typeof(Task<>))
+ else
{
- var typeArg = returnType.GetGenericArguments()[0];
-
- if (typeof(IResult).IsAssignableFrom(typeArg))
- {
- return Expression.Call(
- ExecuteTaskResultOfTMethod.MakeGenericMethod(typeArg),
- methodCall,
- HttpContextExpr);
- }
- // ExecuteTask<T>(handler(..), httpContext);
- else if (typeArg == typeof(string))
- {
- return Expression.Call(
- ExecuteTaskOfStringMethod,
- methodCall,
- HttpContextExpr);
- }
- else
- {
- return Expression.Call(
- ExecuteTaskOfTMethod.MakeGenericMethod(typeArg),
- methodCall,
- HttpContextExpr);
- }
+ return Expression.Call(
+ ExecuteTaskOfTMethod.MakeGenericMethod(typeArg),
+ methodCall,
+ HttpContextExpr);
}
- else if (returnType.IsGenericType &&
- returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
- {
- var typeArg = returnType.GetGenericArguments()[0];
+ }
+ else if (returnType.IsGenericType &&
+ returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
+ {
+ var typeArg = returnType.GetGenericArguments()[0];
- if (typeof(IResult).IsAssignableFrom(typeArg))
- {
- return Expression.Call(
- ExecuteValueResultTaskOfTMethod.MakeGenericMethod(typeArg),
- methodCall,
- HttpContextExpr);
- }
- // ExecuteTask<T>(handler(..), httpContext);
- else if (typeArg == typeof(string))
- {
- return Expression.Call(
- ExecuteValueTaskOfStringMethod,
- methodCall,
- HttpContextExpr);
- }
- else
- {
- return Expression.Call(
- ExecuteValueTaskOfTMethod.MakeGenericMethod(typeArg),
- methodCall,
- HttpContextExpr);
- }
+ if (typeof(IResult).IsAssignableFrom(typeArg))
+ {
+ return Expression.Call(
+ ExecuteValueResultTaskOfTMethod.MakeGenericMethod(typeArg),
+ methodCall,
+ HttpContextExpr);
}
- else
+ // ExecuteTask<T>(handler(..), httpContext);
+ else if (typeArg == typeof(string))
{
- // TODO: Handle custom awaitables
- throw new NotSupportedException($"Unsupported return type: {returnType}");
+ return Expression.Call(
+ ExecuteValueTaskOfStringMethod,
+ methodCall,
+ HttpContextExpr);
}
- }
- else if (typeof(IResult).IsAssignableFrom(returnType))
- {
- if (returnType.IsValueType)
+ else
{
- var box = Expression.TypeAs(methodCall, typeof(IResult));
- return Expression.Call(ResultWriteResponseAsyncMethod, box, HttpContextExpr);
+ return Expression.Call(
+ ExecuteValueTaskOfTMethod.MakeGenericMethod(typeArg),
+ methodCall,
+ HttpContextExpr);
}
- return Expression.Call(ResultWriteResponseAsyncMethod, methodCall, HttpContextExpr);
- }
- else if (returnType == typeof(string))
- {
- return Expression.Call(StringResultWriteResponseAsyncMethod, HttpContextExpr, methodCall);
- }
- else if (returnType.IsValueType)
- {
- var box = Expression.TypeAs(methodCall, typeof(object));
- return Expression.Call(JsonResultWriteResponseAsyncMethod, HttpResponseExpr, box, Expression.Constant(CancellationToken.None));
}
else
{
- return Expression.Call(JsonResultWriteResponseAsyncMethod, HttpResponseExpr, methodCall, Expression.Constant(CancellationToken.None));
+ // TODO: Handle custom awaitables
+ throw new NotSupportedException($"Unsupported return type: {returnType}");
}
}
-
- private static Func<object?, HttpContext, Task> HandleRequestBodyAndCompileRequestDelegate(Expression responseWritingMethodCall, FactoryContext factoryContext)
+ else if (typeof(IResult).IsAssignableFrom(returnType))
{
- if (factoryContext.JsonRequestBodyParameter is null)
+ if (returnType.IsValueType)
{
- if (factoryContext.ParameterBinders.Count > 0)
- {
- // We need to generate the code for reading from the custom binders calling into the delegate
- var continuation = Expression.Lambda<Func<object?, HttpContext, object?[], Task>>(
- responseWritingMethodCall, TargetExpr, HttpContextExpr, BoundValuesArrayExpr).Compile();
-
- // Looping over arrays is faster
- var binders = factoryContext.ParameterBinders.ToArray();
- var count = binders.Length;
-
- return async (target, httpContext) =>
- {
- var boundValues = new object?[count];
-
- for (var i = 0; i < count; i++)
- {
- boundValues[i] = await binders[i](httpContext);
- }
-
- await continuation(target, httpContext, boundValues);
- };
- }
-
- return Expression.Lambda<Func<object?, HttpContext, Task>>(
- responseWritingMethodCall, TargetExpr, HttpContextExpr).Compile();
- }
-
- var bodyType = factoryContext.JsonRequestBodyParameter.ParameterType;
- var parameterTypeName = TypeNameHelper.GetTypeDisplayName(factoryContext.JsonRequestBodyParameter.ParameterType, fullName: false);
- var parameterName = factoryContext.JsonRequestBodyParameter.Name;
-
- Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");
-
- object? defaultBodyValue = null;
-
- if (factoryContext.AllowEmptyRequestBody && bodyType.IsValueType)
- {
- defaultBodyValue = Activator.CreateInstance(bodyType);
+ var box = Expression.TypeAs(methodCall, typeof(IResult));
+ return Expression.Call(ResultWriteResponseAsyncMethod, box, HttpContextExpr);
}
+ return Expression.Call(ResultWriteResponseAsyncMethod, methodCall, HttpContextExpr);
+ }
+ else if (returnType == typeof(string))
+ {
+ return Expression.Call(StringResultWriteResponseAsyncMethod, HttpContextExpr, methodCall);
+ }
+ else if (returnType.IsValueType)
+ {
+ var box = Expression.TypeAs(methodCall, typeof(object));
+ return Expression.Call(JsonResultWriteResponseAsyncMethod, HttpResponseExpr, box, Expression.Constant(CancellationToken.None));
+ }
+ else
+ {
+ return Expression.Call(JsonResultWriteResponseAsyncMethod, HttpResponseExpr, methodCall, Expression.Constant(CancellationToken.None));
+ }
+ }
+ private static Func<object?, HttpContext, Task> HandleRequestBodyAndCompileRequestDelegate(Expression responseWritingMethodCall, FactoryContext factoryContext)
+ {
+ if (factoryContext.JsonRequestBodyParameter is null)
+ {
if (factoryContext.ParameterBinders.Count > 0)
{
- // We need to generate the code for reading from the body before calling into the delegate
- var continuation = Expression.Lambda<Func<object?, HttpContext, object?, object?[], Task>>(
- responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr, BoundValuesArrayExpr).Compile();
+ // We need to generate the code for reading from the custom binders calling into the delegate
+ var continuation = Expression.Lambda<Func<object?, HttpContext, object?[], Task>>(
+ responseWritingMethodCall, TargetExpr, HttpContextExpr, BoundValuesArrayExpr).Compile();
// Looping over arrays is faster
var binders = factoryContext.ParameterBinders.ToArray();
@@ -565,7 +525,6 @@ namespace Microsoft.AspNetCore.Http
return async (target, httpContext) =>
{
- // Run these first so that they can potentially read and rewind the body
var boundValues = new object?[count];
for (var i = 0; i < count; i++)
@@ -573,812 +532,852 @@ namespace Microsoft.AspNetCore.Http
boundValues[i] = await binders[i](httpContext);
}
- var bodyValue = defaultBodyValue;
- var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();
- if (feature?.CanHaveBody == true)
- {
- if (!httpContext.Request.HasJsonContentType())
- {
- Log.UnexpectedContentType(httpContext, httpContext.Request.ContentType, factoryContext.ThrowOnBadRequest);
- httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
- return;
- }
- try
- {
- bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
- }
- catch (IOException ex)
- {
- Log.RequestBodyIOException(httpContext, ex);
- return;
- }
- catch (JsonException ex)
- {
- Log.InvalidJsonRequestBody(httpContext, parameterTypeName, parameterName, ex, factoryContext.ThrowOnBadRequest);
- httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
- return;
- }
- }
-
- await continuation(target, httpContext, bodyValue, boundValues);
+ await continuation(target, httpContext, boundValues);
};
}
- else
- {
- // We need to generate the code for reading from the body before calling into the delegate
- var continuation = Expression.Lambda<Func<object?, HttpContext, object?, Task>>(
- responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile();
- return async (target, httpContext) =>
- {
- var bodyValue = defaultBodyValue;
- var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();
- if (feature?.CanHaveBody == true)
- {
- if (!httpContext.Request.HasJsonContentType())
- {
- Log.UnexpectedContentType(httpContext, httpContext.Request.ContentType, factoryContext.ThrowOnBadRequest);
- httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
- return;
- }
- try
- {
- bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
- }
- catch (IOException ex)
- {
- Log.RequestBodyIOException(httpContext, ex);
- return;
- }
- catch (JsonException ex)
- {
-
- Log.InvalidJsonRequestBody(httpContext, parameterTypeName, parameterName, ex, factoryContext.ThrowOnBadRequest);
- httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
- return;
- }
- }
- await continuation(target, httpContext, bodyValue);
- };
- }
+ return Expression.Lambda<Func<object?, HttpContext, Task>>(
+ responseWritingMethodCall, TargetExpr, HttpContextExpr).Compile();
}
- private static Expression GetValueFromProperty(Expression sourceExpression, string key)
+ var bodyType = factoryContext.JsonRequestBodyParameter.ParameterType;
+ var parameterTypeName = TypeNameHelper.GetTypeDisplayName(factoryContext.JsonRequestBodyParameter.ParameterType, fullName: false);
+ var parameterName = factoryContext.JsonRequestBodyParameter.Name;
+
+ Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");
+
+ object? defaultBodyValue = null;
+
+ if (factoryContext.AllowEmptyRequestBody && bodyType.IsValueType)
{
- var itemProperty = sourceExpression.Type.GetProperty("Item");
- var indexArguments = new[] { Expression.Constant(key) };
- var indexExpression = Expression.MakeIndex(sourceExpression, itemProperty, indexArguments);
- return Expression.Convert(indexExpression, typeof(string));
+ defaultBodyValue = Activator.CreateInstance(bodyType);
}
- private static Expression BindParameterFromService(ParameterInfo parameter, FactoryContext factoryContext)
+ if (factoryContext.ParameterBinders.Count > 0)
{
- var isOptional = IsOptionalParameter(parameter, factoryContext);
+ // We need to generate the code for reading from the body before calling into the delegate
+ var continuation = Expression.Lambda<Func<object?, HttpContext, object?, object?[], Task>>(
+ responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr, BoundValuesArrayExpr).Compile();
- if (isOptional)
+ // Looping over arrays is faster
+ var binders = factoryContext.ParameterBinders.ToArray();
+ var count = binders.Length;
+
+ return async (target, httpContext) =>
{
- return Expression.Call(GetServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
- }
- return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
- }
+ // Run these first so that they can potentially read and rewind the body
+ var boundValues = new object?[count];
- private static Expression BindParameterFromValue(ParameterInfo parameter, Expression valueExpression, FactoryContext factoryContext, string source)
- {
- var isOptional = IsOptionalParameter(parameter, factoryContext);
+ for (var i = 0; i < count; i++)
+ {
+ boundValues[i] = await binders[i](httpContext);
+ }
- var argument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");
+ var bodyValue = defaultBodyValue;
+ var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();
+ if (feature?.CanHaveBody == true)
+ {
+ if (!httpContext.Request.HasJsonContentType())
+ {
+ Log.UnexpectedContentType(httpContext, httpContext.Request.ContentType, factoryContext.ThrowOnBadRequest);
+ httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
+ return;
+ }
+ try
+ {
+ bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
+ }
+ catch (IOException ex)
+ {
+ Log.RequestBodyIOException(httpContext, ex);
+ return;
+ }
+ catch (JsonException ex)
+ {
+ Log.InvalidJsonRequestBody(httpContext, parameterTypeName, parameterName, ex, factoryContext.ThrowOnBadRequest);
+ httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
+ return;
+ }
+ }
- var parameterTypeNameConstant = Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false));
- var parameterNameConstant = Expression.Constant(parameter.Name);
- var sourceConstant = Expression.Constant(source);
+ await continuation(target, httpContext, bodyValue, boundValues);
+ };
+ }
+ else
+ {
+ // We need to generate the code for reading from the body before calling into the delegate
+ var continuation = Expression.Lambda<Func<object?, HttpContext, object?, Task>>(
+ responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile();
- if (parameter.ParameterType == typeof(string))
+ return async (target, httpContext) =>
{
- if (!isOptional)
+ var bodyValue = defaultBodyValue;
+ var feature = httpContext.Features.Get<IHttpRequestBodyDetectionFeature>();
+ if (feature?.CanHaveBody == true)
{
- // The following is produced if the parameter is required:
- //
- // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
- // if (tempSourceString == null)
- // {
- // wasParamCheckFailure = true;
- // Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
- // }
- var checkRequiredStringParameterBlock = Expression.Block(
- Expression.Assign(argument, valueExpression),
- Expression.IfThen(Expression.Equal(argument, Expression.Constant(null)),
- Expression.Block(
- Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
- Expression.Call(LogRequiredParameterNotProvidedMethod,
- HttpContextExpr, parameterTypeNameConstant, parameterNameConstant, sourceConstant,
- Expression.Constant(factoryContext.ThrowOnBadRequest))
- )
- )
- );
+ if (!httpContext.Request.HasJsonContentType())
+ {
+ Log.UnexpectedContentType(httpContext, httpContext.Request.ContentType, factoryContext.ThrowOnBadRequest);
+ httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
+ return;
+ }
+ try
+ {
+ bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
+ }
+ catch (IOException ex)
+ {
+ Log.RequestBodyIOException(httpContext, ex);
+ return;
+ }
+ catch (JsonException ex)
+ {
- factoryContext.ExtraLocals.Add(argument);
- factoryContext.ParamCheckExpressions.Add(checkRequiredStringParameterBlock);
- return argument;
+ Log.InvalidJsonRequestBody(httpContext, parameterTypeName, parameterName, ex, factoryContext.ThrowOnBadRequest);
+ httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
+ return;
+ }
}
+ await continuation(target, httpContext, bodyValue);
+ };
+ }
+ }
- // Allow nullable parameters that don't have a default value
- var nullability = factoryContext.NullabilityContext.Create(parameter);
- if (nullability.ReadState != NullabilityState.NotNull && !parameter.HasDefaultValue)
- {
- return valueExpression;
- }
+ private static Expression GetValueFromProperty(Expression sourceExpression, string key)
+ {
+ var itemProperty = sourceExpression.Type.GetProperty("Item");
+ var indexArguments = new[] { Expression.Constant(key) };
+ var indexExpression = Expression.MakeIndex(sourceExpression, itemProperty, indexArguments);
+ return Expression.Convert(indexExpression, typeof(string));
+ }
- // The following is produced if the parameter is optional. Note that we convert the
- // default value to the target ParameterType to address scenarios where the user is
- // is setting null as the default value in a context where nullability is disabled.
- //
- // param1_local = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
- // param1_local != null ? param1_local : Convert(null, Int32)
- return Expression.Block(
- Expression.Condition(Expression.NotEqual(valueExpression, Expression.Constant(null)),
- valueExpression,
- Expression.Convert(Expression.Constant(parameter.DefaultValue), parameter.ParameterType)));
- }
+ private static Expression BindParameterFromService(ParameterInfo parameter, FactoryContext factoryContext)
+ {
+ var isOptional = IsOptionalParameter(parameter, factoryContext);
- factoryContext.UsingTempSourceString = true;
+ if (isOptional)
+ {
+ return Expression.Call(GetServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
+ }
+ return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
+ }
- var underlyingNullableType = Nullable.GetUnderlyingType(parameter.ParameterType);
- var isNotNullable = underlyingNullableType is null;
+ private static Expression BindParameterFromValue(ParameterInfo parameter, Expression valueExpression, FactoryContext factoryContext, string source)
+ {
+ var isOptional = IsOptionalParameter(parameter, factoryContext);
- var nonNullableParameterType = underlyingNullableType ?? parameter.ParameterType;
- var tryParseMethodCall = ParameterBindingMethodCache.FindTryParseMethod(nonNullableParameterType);
+ var argument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");
- if (tryParseMethodCall is null)
- {
- var typeName = TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false);
- throw new InvalidOperationException($"No public static bool {typeName}.TryParse(string, out {typeName}) method found for {parameter.Name}.");
- }
+ var parameterTypeNameConstant = Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false));
+ var parameterNameConstant = Expression.Constant(parameter.Name);
+ var sourceConstant = Expression.Constant(source);
- // string tempSourceString;
- // bool wasParamCheckFailure = false;
- //
- // // Assume "int param1" is the first parameter and "[FromRoute] int? param2 = 42" is the second parameter.
- // int param1_local;
- // int? param2_local;
- //
- // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
- //
- // if (tempSourceString != null)
- // {
- // if (!int.TryParse(tempSourceString, out param1_local))
- // {
- // wasParamCheckFailure = true;
- // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
- // }
- // }
- //
- // tempSourceString = httpContext.RouteValue["param2"];
- //
- // if (tempSourceString != null)
- // {
- // if (int.TryParse(tempSourceString, out int parsedValue))
- // {
- // param2_local = parsedValue;
- // }
- // else
- // {
- // wasParamCheckFailure = true;
- // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
- // }
- // }
- // else
- // {
- // param2_local = 42;
- // }
-
- // If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot use the parameter directly.
- var parsedValue = isNotNullable ? argument : Expression.Variable(nonNullableParameterType, "parsedValue");
-
- var failBlock = Expression.Block(
- Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
- Expression.Call(LogParameterBindingFailedMethod,
- HttpContextExpr, parameterTypeNameConstant, parameterNameConstant,
- TempSourceStringExpr, Expression.Constant(factoryContext.ThrowOnBadRequest)));
-
- var tryParseCall = tryParseMethodCall(parsedValue);
-
- // The following code is generated if the parameter is required and
- // the method should not be matched.
- //
- // if (tempSourceString == null)
- // {
- // wasParamCheckFailure = true;
- // Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
- // }
- var checkRequiredParaseableParameterBlock = Expression.Block(
- Expression.IfThen(TempSourceStringNullExpr,
- Expression.Block(
- Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
- Expression.Call(LogRequiredParameterNotProvidedMethod,
- HttpContextExpr, parameterTypeNameConstant, parameterNameConstant, sourceConstant,
- Expression.Constant(factoryContext.ThrowOnBadRequest))
+ if (parameter.ParameterType == typeof(string))
+ {
+ if (!isOptional)
+ {
+ // The following is produced if the parameter is required:
+ //
+ // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
+ // if (tempSourceString == null)
+ // {
+ // wasParamCheckFailure = true;
+ // Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
+ // }
+ var checkRequiredStringParameterBlock = Expression.Block(
+ Expression.Assign(argument, valueExpression),
+ Expression.IfThen(Expression.Equal(argument, Expression.Constant(null)),
+ Expression.Block(
+ Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
+ Expression.Call(LogRequiredParameterNotProvidedMethod,
+ HttpContextExpr, parameterTypeNameConstant, parameterNameConstant, sourceConstant,
+ Expression.Constant(factoryContext.ThrowOnBadRequest))
+ )
)
- )
- );
+ );
- // If the parameter is nullable, we need to assign the "parsedValue" local to the nullable parameter on success.
- Expression tryParseExpression = isNotNullable ?
- Expression.IfThen(Expression.Not(tryParseCall), failBlock) :
- Expression.Block(new[] { parsedValue },
- Expression.IfThenElse(tryParseCall,
- Expression.Assign(argument, Expression.Convert(parsedValue, parameter.ParameterType)),
- failBlock));
+ factoryContext.ExtraLocals.Add(argument);
+ factoryContext.ParamCheckExpressions.Add(checkRequiredStringParameterBlock);
+ return argument;
+ }
- var ifNotNullTryParse = !parameter.HasDefaultValue ?
- Expression.IfThen(TempSourceStringNotNullExpr, tryParseExpression) :
- Expression.IfThenElse(TempSourceStringNotNullExpr,
- tryParseExpression,
- Expression.Assign(argument, Expression.Constant(parameter.DefaultValue)));
+ // Allow nullable parameters that don't have a default value
+ var nullability = factoryContext.NullabilityContext.Create(parameter);
+ if (nullability.ReadState != NullabilityState.NotNull && !parameter.HasDefaultValue)
+ {
+ return valueExpression;
+ }
- var fullParamCheckBlock = !isOptional
- ? Expression.Block(
- // tempSourceString = httpContext.RequestValue["id"];
- Expression.Assign(TempSourceStringExpr, valueExpression),
- // if (tempSourceString == null) { ... } only produced when parameter is required
- checkRequiredParaseableParameterBlock,
- // if (tempSourceString != null) { ... }
- ifNotNullTryParse)
- : Expression.Block(
- // tempSourceString = httpContext.RequestValue["id"];
- Expression.Assign(TempSourceStringExpr, valueExpression),
- // if (tempSourceString != null) { ... }
- ifNotNullTryParse);
+ // The following is produced if the parameter is optional. Note that we convert the
+ // default value to the target ParameterType to address scenarios where the user is
+ // is setting null as the default value in a context where nullability is disabled.
+ //
+ // param1_local = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
+ // param1_local != null ? param1_local : Convert(null, Int32)
+ return Expression.Block(
+ Expression.Condition(Expression.NotEqual(valueExpression, Expression.Constant(null)),
+ valueExpression,
+ Expression.Convert(Expression.Constant(parameter.DefaultValue), parameter.ParameterType)));
+ }
- factoryContext.ExtraLocals.Add(argument);
- factoryContext.ParamCheckExpressions.Add(fullParamCheckBlock);
+ factoryContext.UsingTempSourceString = true;
- return argument;
- }
+ var underlyingNullableType = Nullable.GetUnderlyingType(parameter.ParameterType);
+ var isNotNullable = underlyingNullableType is null;
- private static Expression BindParameterFromProperty(ParameterInfo parameter, MemberExpression property, string key, FactoryContext factoryContext, string source) =>
- BindParameterFromValue(parameter, GetValueFromProperty(property, key), factoryContext, source);
+ var nonNullableParameterType = underlyingNullableType ?? parameter.ParameterType;
+ var tryParseMethodCall = ParameterBindingMethodCache.FindTryParseMethod(nonNullableParameterType);
- private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo parameter, string key, FactoryContext factoryContext)
+ if (tryParseMethodCall is null)
{
- var routeValue = GetValueFromProperty(RouteValuesExpr, key);
- var queryValue = GetValueFromProperty(QueryExpr, key);
- return BindParameterFromValue(parameter, Expression.Coalesce(routeValue, queryValue), factoryContext, "route or query string");
+ var typeName = TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false);
+ throw new InvalidOperationException($"No public static bool {typeName}.TryParse(string, out {typeName}) method found for {parameter.Name}.");
}
- private static Expression BindParameterFromBindAsync(ParameterInfo parameter, FactoryContext factoryContext)
- {
- // We reference the boundValues array by parameter index here
- var nullability = factoryContext.NullabilityContext.Create(parameter);
- var isOptional = IsOptionalParameter(parameter, factoryContext);
+ // string tempSourceString;
+ // bool wasParamCheckFailure = false;
+ //
+ // // Assume "int param1" is the first parameter and "[FromRoute] int? param2 = 42" is the second parameter.
+ // int param1_local;
+ // int? param2_local;
+ //
+ // tempSourceString = httpContext.RouteValue["param1"] ?? httpContext.Query["param1"];
+ //
+ // if (tempSourceString != null)
+ // {
+ // if (!int.TryParse(tempSourceString, out param1_local))
+ // {
+ // wasParamCheckFailure = true;
+ // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
+ // }
+ // }
+ //
+ // tempSourceString = httpContext.RouteValue["param2"];
+ //
+ // if (tempSourceString != null)
+ // {
+ // if (int.TryParse(tempSourceString, out int parsedValue))
+ // {
+ // param2_local = parsedValue;
+ // }
+ // else
+ // {
+ // wasParamCheckFailure = true;
+ // Log.ParameterBindingFailed(httpContext, "Int32", "id", tempSourceString)
+ // }
+ // }
+ // else
+ // {
+ // param2_local = 42;
+ // }
+
+ // If the parameter is nullable, create a "parsedValue" local to TryParse into since we cannot use the parameter directly.
+ var parsedValue = isNotNullable ? argument : Expression.Variable(nonNullableParameterType, "parsedValue");
+
+ var failBlock = Expression.Block(
+ Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
+ Expression.Call(LogParameterBindingFailedMethod,
+ HttpContextExpr, parameterTypeNameConstant, parameterNameConstant,
+ TempSourceStringExpr, Expression.Constant(factoryContext.ThrowOnBadRequest)));
+
+ var tryParseCall = tryParseMethodCall(parsedValue);
+
+ // The following code is generated if the parameter is required and
+ // the method should not be matched.
+ //
+ // if (tempSourceString == null)
+ // {
+ // wasParamCheckFailure = true;
+ // Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
+ // }
+ var checkRequiredParaseableParameterBlock = Expression.Block(
+ Expression.IfThen(TempSourceStringNullExpr,
+ Expression.Block(
+ Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
+ Expression.Call(LogRequiredParameterNotProvidedMethod,
+ HttpContextExpr, parameterTypeNameConstant, parameterNameConstant, sourceConstant,
+ Expression.Constant(factoryContext.ThrowOnBadRequest))
+ )
+ )
+ );
+
+ // If the parameter is nullable, we need to assign the "parsedValue" local to the nullable parameter on success.
+ Expression tryParseExpression = isNotNullable ?
+ Expression.IfThen(Expression.Not(tryParseCall), failBlock) :
+ Expression.Block(new[] { parsedValue },
+ Expression.IfThenElse(tryParseCall,
+ Expression.Assign(argument, Expression.Convert(parsedValue, parameter.ParameterType)),
+ failBlock));
+
+ var ifNotNullTryParse = !parameter.HasDefaultValue ?
+ Expression.IfThen(TempSourceStringNotNullExpr, tryParseExpression) :
+ Expression.IfThenElse(TempSourceStringNotNullExpr,
+ tryParseExpression,
+ Expression.Assign(argument, Expression.Constant(parameter.DefaultValue)));
+
+ var fullParamCheckBlock = !isOptional
+ ? Expression.Block(
+ // tempSourceString = httpContext.RequestValue["id"];
+ Expression.Assign(TempSourceStringExpr, valueExpression),
+ // if (tempSourceString == null) { ... } only produced when parameter is required
+ checkRequiredParaseableParameterBlock,
+ // if (tempSourceString != null) { ... }
+ ifNotNullTryParse)
+ : Expression.Block(
+ // tempSourceString = httpContext.RequestValue["id"];
+ Expression.Assign(TempSourceStringExpr, valueExpression),
+ // if (tempSourceString != null) { ... }
+ ifNotNullTryParse);
+
+ factoryContext.ExtraLocals.Add(argument);
+ factoryContext.ParamCheckExpressions.Add(fullParamCheckBlock);
+
+ return argument;
+ }
- // Get the BindAsync method for the type.
- var bindAsyncMethod = ParameterBindingMethodCache.FindBindAsyncMethod(parameter);
- // We know BindAsync exists because there's no way to opt-in without defining the method on the type.
- Debug.Assert(bindAsyncMethod.Expression is not null);
+ private static Expression BindParameterFromProperty(ParameterInfo parameter, MemberExpression property, string key, FactoryContext factoryContext, string source) =>
+ BindParameterFromValue(parameter, GetValueFromProperty(property, key), factoryContext, source);
- // Compile the delegate to the BindAsync method for this parameter index
- var bindAsyncDelegate = Expression.Lambda<Func<HttpContext, ValueTask<object?>>>(bindAsyncMethod.Expression, HttpContextExpr).Compile();
- factoryContext.ParameterBinders.Add(bindAsyncDelegate);
+ private static Expression BindParameterFromRouteValueOrQueryString(ParameterInfo parameter, string key, FactoryContext factoryContext)
+ {
+ var routeValue = GetValueFromProperty(RouteValuesExpr, key);
+ var queryValue = GetValueFromProperty(QueryExpr, key);
+ return BindParameterFromValue(parameter, Expression.Coalesce(routeValue, queryValue), factoryContext, "route or query string");
+ }
- // boundValues[index]
- var boundValueExpr = Expression.ArrayIndex(BoundValuesArrayExpr, Expression.Constant(factoryContext.ParameterBinders.Count - 1));
+ private static Expression BindParameterFromBindAsync(ParameterInfo parameter, FactoryContext factoryContext)
+ {
+ // We reference the boundValues array by parameter index here
+ var nullability = factoryContext.NullabilityContext.Create(parameter);
+ var isOptional = IsOptionalParameter(parameter, factoryContext);
- if (!isOptional)
- {
- var typeName = TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false);
- var message = bindAsyncMethod.ParamCount == 2 ? $"{typeName}.BindAsync(HttpContext, ParameterInfo)" : $"{typeName}.BindAsync(HttpContext)";
- var checkRequiredBodyBlock = Expression.Block(
- Expression.IfThen(
- Expression.Equal(boundValueExpr, Expression.Constant(null)),
- Expression.Block(
- Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
- Expression.Call(LogRequiredParameterNotProvidedMethod,
- HttpContextExpr,
- Expression.Constant(typeName),
- Expression.Constant(parameter.Name),
- Expression.Constant(message),
- Expression.Constant(factoryContext.ThrowOnBadRequest))
- )
- )
- );
+ // Get the BindAsync method for the type.
+ var bindAsyncMethod = ParameterBindingMethodCache.FindBindAsyncMethod(parameter);
+ // We know BindAsync exists because there's no way to opt-in without defining the method on the type.
+ Debug.Assert(bindAsyncMethod.Expression is not null);
- factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
- }
+ // Compile the delegate to the BindAsync method for this parameter index
+ var bindAsyncDelegate = Expression.Lambda<Func<HttpContext, ValueTask<object?>>>(bindAsyncMethod.Expression, HttpContextExpr).Compile();
+ factoryContext.ParameterBinders.Add(bindAsyncDelegate);
+
+ // boundValues[index]
+ var boundValueExpr = Expression.ArrayIndex(BoundValuesArrayExpr, Expression.Constant(factoryContext.ParameterBinders.Count - 1));
+
+ if (!isOptional)
+ {
+ var typeName = TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false);
+ var message = bindAsyncMethod.ParamCount == 2 ? $"{typeName}.BindAsync(HttpContext, ParameterInfo)" : $"{typeName}.BindAsync(HttpContext)";
+ var checkRequiredBodyBlock = Expression.Block(
+ Expression.IfThen(
+ Expression.Equal(boundValueExpr, Expression.Constant(null)),
+ Expression.Block(
+ Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
+ Expression.Call(LogRequiredParameterNotProvidedMethod,
+ HttpContextExpr,
+ Expression.Constant(typeName),
+ Expression.Constant(parameter.Name),
+ Expression.Constant(message),
+ Expression.Constant(factoryContext.ThrowOnBadRequest))
+ )
+ )
+ );
- // (ParameterType)boundValues[i]
- return Expression.Convert(boundValueExpr, parameter.ParameterType);
+ factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
}
- private static Expression BindParameterFromBody(ParameterInfo parameter, bool allowEmpty, FactoryContext factoryContext)
+ // (ParameterType)boundValues[i]
+ return Expression.Convert(boundValueExpr, parameter.ParameterType);
+ }
+
+ private static Expression BindParameterFromBody(ParameterInfo parameter, bool allowEmpty, FactoryContext factoryContext)
+ {
+ if (factoryContext.JsonRequestBodyParameter is not null)
{
- if (factoryContext.JsonRequestBodyParameter is not null)
- {
- factoryContext.HasMultipleBodyParameters = true;
- var parameterName = parameter.Name;
+ factoryContext.HasMultipleBodyParameters = true;
+ var parameterName = parameter.Name;
- Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");
+ Debug.Assert(parameterName is not null, "CreateArgument() should throw if parameter.Name is null.");
- if (factoryContext.TrackedParameters.ContainsKey(parameterName))
- {
- factoryContext.TrackedParameters.Remove(parameterName);
- factoryContext.TrackedParameters.Add(parameterName, "UNKNOWN");
- }
+ if (factoryContext.TrackedParameters.ContainsKey(parameterName))
+ {
+ factoryContext.TrackedParameters.Remove(parameterName);
+ factoryContext.TrackedParameters.Add(parameterName, "UNKNOWN");
}
+ }
- var isOptional = IsOptionalParameter(parameter, factoryContext);
+ var isOptional = IsOptionalParameter(parameter, factoryContext);
- factoryContext.JsonRequestBodyParameter = parameter;
- factoryContext.AllowEmptyRequestBody = allowEmpty || isOptional;
- factoryContext.Metadata.Add(new AcceptsMetadata(parameter.ParameterType, factoryContext.AllowEmptyRequestBody, DefaultAcceptsContentType));
+ factoryContext.JsonRequestBodyParameter = parameter;
+ factoryContext.AllowEmptyRequestBody = allowEmpty || isOptional;
+ factoryContext.Metadata.Add(new AcceptsMetadata(parameter.ParameterType, factoryContext.AllowEmptyRequestBody, DefaultAcceptsContentType));
- if (!factoryContext.AllowEmptyRequestBody)
- {
- if (factoryContext.HasInferredBody)
- {
- // if (bodyValue == null)
- // {
- // wasParamCheckFailure = true;
- // Log.ImplicitBodyNotProvided(httpContext, "todo", ThrowOnBadRequest);
- // }
- factoryContext.ParamCheckExpressions.Add(Expression.Block(
- Expression.IfThen(
- Expression.Equal(BodyValueExpr, Expression.Constant(null)),
- Expression.Block(
- Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
- Expression.Call(LogImplicitBodyNotProvidedMethod,
- HttpContextExpr,
- Expression.Constant(parameter.Name),
- Expression.Constant(factoryContext.ThrowOnBadRequest)
- )
- )
- )
- ));
- }
- else
- {
- // If the parameter is required or the user has not explicitly
- // set allowBody to be empty then validate that it is required.
- //
- // if (bodyValue == null)
- // {
- // wasParamCheckFailure = true;
- // Log.RequiredParameterNotProvided(httpContext, "Todo", "todo", "body", ThrowOnBadRequest);
- // }
- var checkRequiredBodyBlock = Expression.Block(
- Expression.IfThen(
+ if (!factoryContext.AllowEmptyRequestBody)
+ {
+ if (factoryContext.HasInferredBody)
+ {
+ // if (bodyValue == null)
+ // {
+ // wasParamCheckFailure = true;
+ // Log.ImplicitBodyNotProvided(httpContext, "todo", ThrowOnBadRequest);
+ // }
+ factoryContext.ParamCheckExpressions.Add(Expression.Block(
+ Expression.IfThen(
Expression.Equal(BodyValueExpr, Expression.Constant(null)),
- Expression.Block(
- Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
- Expression.Call(LogRequiredParameterNotProvidedMethod,
- HttpContextExpr,
- Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false)),
- Expression.Constant(parameter.Name),
- Expression.Constant("body"),
- Expression.Constant(factoryContext.ThrowOnBadRequest))
+ Expression.Block(
+ Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
+ Expression.Call(LogImplicitBodyNotProvidedMethod,
+ HttpContextExpr,
+ Expression.Constant(parameter.Name),
+ Expression.Constant(factoryContext.ThrowOnBadRequest)
)
)
- );
- factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
- }
-
+ )
+ ));
}
- else if (parameter.HasDefaultValue)
+ else
{
- // Convert(bodyValue ?? SomeDefault, Todo)
- return Expression.Convert(
- Expression.Coalesce(BodyValueExpr, Expression.Constant(parameter.DefaultValue)),
- parameter.ParameterType);
+ // If the parameter is required or the user has not explicitly
+ // set allowBody to be empty then validate that it is required.
+ //
+ // if (bodyValue == null)
+ // {
+ // wasParamCheckFailure = true;
+ // Log.RequiredParameterNotProvided(httpContext, "Todo", "todo", "body", ThrowOnBadRequest);
+ // }
+ var checkRequiredBodyBlock = Expression.Block(
+ Expression.IfThen(
+ Expression.Equal(BodyValueExpr, Expression.Constant(null)),
+ Expression.Block(
+ Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
+ Expression.Call(LogRequiredParameterNotProvidedMethod,
+ HttpContextExpr,
+ Expression.Constant(TypeNameHelper.GetTypeDisplayName(parameter.ParameterType, fullName: false)),
+ Expression.Constant(parameter.Name),
+ Expression.Constant("body"),
+ Expression.Constant(factoryContext.ThrowOnBadRequest))
+ )
+ )
+ );
+ factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
}
- // Convert(bodyValue, Todo)
- return Expression.Convert(BodyValueExpr, parameter.ParameterType);
}
-
- private static bool IsOptionalParameter(ParameterInfo parameter, FactoryContext factoryContext)
+ else if (parameter.HasDefaultValue)
{
- // - Parameters representing value or reference types with a default value
- // under any nullability context are treated as optional.
- // - Value type parameters without a default value in an oblivious
- // nullability context are required.
- // - Reference type parameters without a default value in an oblivious
- // nullability context are optional.
- var nullability = factoryContext.NullabilityContext.Create(parameter);
- return parameter.HasDefaultValue
- || nullability.ReadState != NullabilityState.NotNull;
+ // Convert(bodyValue ?? SomeDefault, Todo)
+ return Expression.Convert(
+ Expression.Coalesce(BodyValueExpr, Expression.Constant(parameter.DefaultValue)),
+ parameter.ParameterType);
}
- private static MethodInfo GetMethodInfo<T>(Expression<T> expr)
+ // Convert(bodyValue, Todo)
+ return Expression.Convert(BodyValueExpr, parameter.ParameterType);
+ }
+
+ private static bool IsOptionalParameter(ParameterInfo parameter, FactoryContext factoryContext)
+ {
+ // - Parameters representing value or reference types with a default value
+ // under any nullability context are treated as optional.
+ // - Value type parameters without a default value in an oblivious
+ // nullability context are required.
+ // - Reference type parameters without a default value in an oblivious
+ // nullability context are optional.
+ var nullability = factoryContext.NullabilityContext.Create(parameter);
+ return parameter.HasDefaultValue
+ || nullability.ReadState != NullabilityState.NotNull;
+ }
+
+ private static MethodInfo GetMethodInfo<T>(Expression<T> expr)
+ {
+ var mc = (MethodCallExpression)expr.Body;
+ return mc.Method;
+ }
+
+ private static MemberInfo GetMemberInfo<T>(Expression<T> expr)
+ {
+ var mc = (MemberExpression)expr.Body;
+ return mc.Member;
+ }
+
+ // The result of the method is null so we fallback to some runtime logic.
+ // First we check if the result is IResult, Task<IResult> or ValueTask<IResult>. If
+ // it is, we await if necessary then execute the result.
+ // Then we check to see if it's Task<object> or ValueTask<object>. If it is, we await
+ // if necessary and restart the cycle until we've reached a terminal state (unknown type).
+ // We currently don't handle Task<unknown> or ValueTask<unknown>. We can support this later if this
+ // ends up being a common scenario.
+ private static async Task ExecuteObjectReturn(object? obj, HttpContext httpContext)
+ {
+ // See if we need to unwrap Task<object> or ValueTask<object>
+ if (obj is Task<object> taskObj)
{
- var mc = (MethodCallExpression)expr.Body;
- return mc.Method;
+ obj = await taskObj;
}
-
- private static MemberInfo GetMemberInfo<T>(Expression<T> expr)
+ else if (obj is ValueTask<object> valueTaskObj)
{
- var mc = (MemberExpression)expr.Body;
- return mc.Member;
+ obj = await valueTaskObj;
}
-
- // The result of the method is null so we fallback to some runtime logic.
- // First we check if the result is IResult, Task<IResult> or ValueTask<IResult>. If
- // it is, we await if necessary then execute the result.
- // Then we check to see if it's Task<object> or ValueTask<object>. If it is, we await
- // if necessary and restart the cycle until we've reached a terminal state (unknown type).
- // We currently don't handle Task<unknown> or ValueTask<unknown>. We can support this later if this
- // ends up being a common scenario.
- private static async Task ExecuteObjectReturn(object? obj, HttpContext httpContext)
+ else if (obj is Task<IResult?> task)
{
- // See if we need to unwrap Task<object> or ValueTask<object>
- if (obj is Task<object> taskObj)
- {
- obj = await taskObj;
- }
- else if (obj is ValueTask<object> valueTaskObj)
- {
- obj = await valueTaskObj;
- }
- else if (obj is Task<IResult?> task)
- {
- await ExecuteTaskResult(task, httpContext);
- return;
- }
- else if (obj is ValueTask<IResult?> valueTask)
- {
- await ExecuteValueTaskResult(valueTask, httpContext);
- return;
- }
- else if (obj is Task<string?> taskString)
- {
- await ExecuteTaskOfString(taskString, httpContext);
- return;
- }
- else if (obj is ValueTask<string?> valueTaskString)
- {
- await ExecuteValueTaskOfString(valueTaskString, httpContext);
- return;
- }
-
- // Terminal built ins
- if (obj is IResult result)
- {
- await ExecuteResultWriteResponse(result, httpContext);
- }
- else if (obj is string stringValue)
- {
- SetPlaintextContentType(httpContext);
- await httpContext.Response.WriteAsync(stringValue);
- }
- else
- {
- // Otherwise, we JSON serialize when we reach the terminal state
- await httpContext.Response.WriteAsJsonAsync(obj);
- }
+ await ExecuteTaskResult(task, httpContext);
+ return;
}
-
- private static Task ExecuteTask<T>(Task<T> task, HttpContext httpContext)
+ else if (obj is ValueTask<IResult?> valueTask)
{
- EnsureRequestTaskNotNull(task);
-
- static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext)
- {
- await httpContext.Response.WriteAsJsonAsync(await task);
- }
-
- if (task.IsCompletedSuccessfully)
- {
- return httpContext.Response.WriteAsJsonAsync(task.GetAwaiter().GetResult());
- }
-
- return ExecuteAwaited(task, httpContext);
+ await ExecuteValueTaskResult(valueTask, httpContext);
+ return;
}
-
- private static Task ExecuteTaskOfString(Task<string?> task, HttpContext httpContext)
+ else if (obj is Task<string?> taskString)
{
- SetPlaintextContentType(httpContext);
- EnsureRequestTaskNotNull(task);
-
- static async Task ExecuteAwaited(Task<string> task, HttpContext httpContext)
- {
- await httpContext.Response.WriteAsync(await task);
- }
-
- if (task.IsCompletedSuccessfully)
- {
- return httpContext.Response.WriteAsync(task.GetAwaiter().GetResult()!);
- }
-
- return ExecuteAwaited(task!, httpContext);
+ await ExecuteTaskOfString(taskString, httpContext);
+ return;
}
-
- private static Task ExecuteWriteStringResponseAsync(HttpContext httpContext, string text)
+ else if (obj is ValueTask<string?> valueTaskString)
{
- SetPlaintextContentType(httpContext);
- return httpContext.Response.WriteAsync(text);
+ await ExecuteValueTaskOfString(valueTaskString, httpContext);
+ return;
}
- private static Task ExecuteValueTask(ValueTask task)
+ // Terminal built ins
+ if (obj is IResult result)
{
- static async Task ExecuteAwaited(ValueTask task)
- {
- await task;
- }
-
- if (task.IsCompletedSuccessfully)
- {
- task.GetAwaiter().GetResult();
- }
-
- return ExecuteAwaited(task);
+ await ExecuteResultWriteResponse(result, httpContext);
}
-
- private static Task ExecuteValueTaskOfT<T>(ValueTask<T> task, HttpContext httpContext)
+ else if (obj is string stringValue)
{
- static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext)
- {
- await httpContext.Response.WriteAsJsonAsync(await task);
- }
+ SetPlaintextContentType(httpContext);
+ await httpContext.Response.WriteAsync(stringValue);
+ }
+ else
+ {
+ // Otherwise, we JSON serialize when we reach the terminal state
+ await httpContext.Response.WriteAsJsonAsync(obj);
+ }
+ }
- if (task.IsCompletedSuccessfully)
- {
- return httpContext.Response.WriteAsJsonAsync(task.GetAwaiter().GetResult());
- }
+ private static Task ExecuteTask<T>(Task<T> task, HttpContext httpContext)
+ {
+ EnsureRequestTaskNotNull(task);
- return ExecuteAwaited(task, httpContext);
+ static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext)
+ {
+ await httpContext.Response.WriteAsJsonAsync(await task);
}
- private static Task ExecuteValueTaskOfString(ValueTask<string?> task, HttpContext httpContext)
+ if (task.IsCompletedSuccessfully)
{
- SetPlaintextContentType(httpContext);
+ return httpContext.Response.WriteAsJsonAsync(task.GetAwaiter().GetResult());
+ }
- static async Task ExecuteAwaited(ValueTask<string> task, HttpContext httpContext)
- {
- await httpContext.Response.WriteAsync(await task);
- }
+ return ExecuteAwaited(task, httpContext);
+ }
- if (task.IsCompletedSuccessfully)
- {
- return httpContext.Response.WriteAsync(task.GetAwaiter().GetResult()!);
- }
+ private static Task ExecuteTaskOfString(Task<string?> task, HttpContext httpContext)
+ {
+ SetPlaintextContentType(httpContext);
+ EnsureRequestTaskNotNull(task);
- return ExecuteAwaited(task!, httpContext);
+ static async Task ExecuteAwaited(Task<string> task, HttpContext httpContext)
+ {
+ await httpContext.Response.WriteAsync(await task);
}
- private static Task ExecuteValueTaskResult<T>(ValueTask<T?> task, HttpContext httpContext) where T : IResult
+ if (task.IsCompletedSuccessfully)
{
- static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext)
- {
- await EnsureRequestResultNotNull(await task).ExecuteAsync(httpContext);
- }
+ return httpContext.Response.WriteAsync(task.GetAwaiter().GetResult()!);
+ }
- if (task.IsCompletedSuccessfully)
- {
- return EnsureRequestResultNotNull(task.GetAwaiter().GetResult()).ExecuteAsync(httpContext);
- }
+ return ExecuteAwaited(task!, httpContext);
+ }
- return ExecuteAwaited(task!, httpContext);
- }
+ private static Task ExecuteWriteStringResponseAsync(HttpContext httpContext, string text)
+ {
+ SetPlaintextContentType(httpContext);
+ return httpContext.Response.WriteAsync(text);
+ }
- private static async Task ExecuteTaskResult<T>(Task<T?> task, HttpContext httpContext) where T : IResult
+ private static Task ExecuteValueTask(ValueTask task)
+ {
+ static async Task ExecuteAwaited(ValueTask task)
{
- EnsureRequestTaskOfNotNull(task);
-
- await EnsureRequestResultNotNull(await task).ExecuteAsync(httpContext);
+ await task;
}
- private static async Task ExecuteResultWriteResponse(IResult? result, HttpContext httpContext)
+ if (task.IsCompletedSuccessfully)
{
- await EnsureRequestResultNotNull(result).ExecuteAsync(httpContext);
+ task.GetAwaiter().GetResult();
}
- private class FactoryContext
+ return ExecuteAwaited(task);
+ }
+
+ private static Task ExecuteValueTaskOfT<T>(ValueTask<T> task, HttpContext httpContext)
+ {
+ static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext)
{
- // Options
- public IServiceProviderIsService? ServiceProviderIsService { get; init; }
- public List<string>? RouteParameters { get; init; }
- public bool ThrowOnBadRequest { get; init; }
- public bool DisableInferredFromBody { get; init; }
+ await httpContext.Response.WriteAsJsonAsync(await task);
+ }
- // Temporary State
- public ParameterInfo? JsonRequestBodyParameter { get; set; }
- public bool AllowEmptyRequestBody { get; set; }
+ if (task.IsCompletedSuccessfully)
+ {
+ return httpContext.Response.WriteAsJsonAsync(task.GetAwaiter().GetResult());
+ }
- public bool UsingTempSourceString { get; set; }
- public List<ParameterExpression> ExtraLocals { get; } = new();
- public List<Expression> ParamCheckExpressions { get; } = new();
- public List<Func<HttpContext, ValueTask<object?>>> ParameterBinders { get; } = new();
+ return ExecuteAwaited(task, httpContext);
+ }
- public Dictionary<string, string> TrackedParameters { get; } = new();
- public bool HasMultipleBodyParameters { get; set; }
- public bool HasInferredBody { get; set; }
+ private static Task ExecuteValueTaskOfString(ValueTask<string?> task, HttpContext httpContext)
+ {
+ SetPlaintextContentType(httpContext);
- public List<object> Metadata { get; } = new();
+ static async Task ExecuteAwaited(ValueTask<string> task, HttpContext httpContext)
+ {
+ await httpContext.Response.WriteAsync(await task);
+ }
- public NullabilityInfoContext NullabilityContext { get; } = new();
+ if (task.IsCompletedSuccessfully)
+ {
+ return httpContext.Response.WriteAsync(task.GetAwaiter().GetResult()!);
}
- private static class RequestDelegateFactoryConstants
+ return ExecuteAwaited(task!, httpContext);
+ }
+
+ private static Task ExecuteValueTaskResult<T>(ValueTask<T?> task, HttpContext httpContext) where T : IResult
+ {
+ static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext)
{
- public const string RouteAttribute = "Route (Attribute)";
- public const string QueryAttribute = "Query (Attribute)";
- public const string HeaderAttribute = "Header (Attribute)";
- public const string BodyAttribute = "Body (Attribute)";
- public const string ServiceAttribute = "Service (Attribute)";
- public const string RouteParameter = "Route (Inferred)";
- public const string QueryStringParameter = "Query String (Inferred)";
- public const string ServiceParameter = "Services (Inferred)";
- public const string BodyParameter = "Body (Inferred)";
- public const string RouteOrQueryStringParameter = "Route or Query String (Inferred)";
+ await EnsureRequestResultNotNull(await task).ExecuteAsync(httpContext);
}
- private static partial class Log
+ if (task.IsCompletedSuccessfully)
{
- private const string InvalidJsonRequestBodyMessage = @"Failed to read parameter ""{ParameterType} {ParameterName}"" from the request body as JSON.";
- private const string InvalidJsonRequestBodyExceptionMessage = @"Failed to read parameter ""{0} {1}"" from the request body as JSON.";
+ return EnsureRequestResultNotNull(task.GetAwaiter().GetResult()).ExecuteAsync(httpContext);
+ }
- private const string ParameterBindingFailedLogMessage = @"Failed to bind parameter ""{ParameterType} {ParameterName}"" from ""{SourceValue}"".";
- private const string ParameterBindingFailedExceptionMessage = @"Failed to bind parameter ""{0} {1}"" from ""{2}"".";
+ return ExecuteAwaited(task!, httpContext);
+ }
- private const string RequiredParameterNotProvidedLogMessage = @"Required parameter ""{ParameterType} {ParameterName}"" was not provided from {Source}.";
- private const string RequiredParameterNotProvidedExceptionMessage = @"Required parameter ""{0} {1}"" was not provided from {2}.";
+ private static async Task ExecuteTaskResult<T>(Task<T?> task, HttpContext httpContext) where T : IResult
+ {
+ EnsureRequestTaskOfNotNull(task);
- private const string UnexpectedContentTypeLogMessage = @"Expected a supported JSON media type but got ""{ContentType}"".";
- private const string UnexpectedContentTypeExceptionMessage = @"Expected a supported JSON media type but got ""{0}"".";
+ await EnsureRequestResultNotNull(await task).ExecuteAsync(httpContext);
+ }
- private const string ImplicitBodyNotProvidedLogMessage = @"Implicit body inferred for parameter ""{ParameterName}"" but no body was provided. Did you mean to use a Service instead?";
- private const string ImplicitBodyNotProvidedExceptionMessage = @"Implicit body inferred for parameter ""{0}"" but no body was provided. Did you mean to use a Service instead?";
+ private static async Task ExecuteResultWriteResponse(IResult? result, HttpContext httpContext)
+ {
+ await EnsureRequestResultNotNull(result).ExecuteAsync(httpContext);
+ }
- // This doesn't take a shouldThrow parameter because an IOException indicates an aborted request rather than a "bad" request so
- // a BadHttpRequestException feels wrong. The client shouldn't be able to read the Developer Exception Page at any rate.
- public static void RequestBodyIOException(HttpContext httpContext, IOException exception)
- => RequestBodyIOException(GetLogger(httpContext), exception);
+ private class FactoryContext
+ {
+ // Options
+ public IServiceProviderIsService? ServiceProviderIsService { get; init; }
+ public List<string>? RouteParameters { get; init; }
+ public bool ThrowOnBadRequest { get; init; }
+ public bool DisableInferredFromBody { get; init; }
- [LoggerMessage(1, LogLevel.Debug, "Reading the request body failed with an IOException.", EventName = "RequestBodyIOException")]
- private static partial void RequestBodyIOException(ILogger logger, IOException exception);
+ // Temporary State
+ public ParameterInfo? JsonRequestBodyParameter { get; set; }
+ public bool AllowEmptyRequestBody { get; set; }
- public static void InvalidJsonRequestBody(HttpContext httpContext, string parameterTypeName, string parameterName, Exception exception, bool shouldThrow)
- {
- if (shouldThrow)
- {
- var message = string.Format(CultureInfo.InvariantCulture, InvalidJsonRequestBodyExceptionMessage, parameterTypeName, parameterName);
- throw new BadHttpRequestException(message, exception);
- }
+ public bool UsingTempSourceString { get; set; }
+ public List<ParameterExpression> ExtraLocals { get; } = new();
+ public List<Expression> ParamCheckExpressions { get; } = new();
+ public List<Func<HttpContext, ValueTask<object?>>> ParameterBinders { get; } = new();
- InvalidJsonRequestBody(GetLogger(httpContext), parameterTypeName, parameterName, exception);
- }
+ public Dictionary<string, string> TrackedParameters { get; } = new();
+ public bool HasMultipleBodyParameters { get; set; }
+ public bool HasInferredBody { get; set; }
- [LoggerMessage(2, LogLevel.Debug, InvalidJsonRequestBodyMessage, EventName = "InvalidJsonRequestBody")]
- private static partial void InvalidJsonRequestBody(ILogger logger, string parameterType, string parameterName, Exception exception);
+ public List<object> Metadata { get; } = new();
- public static void ParameterBindingFailed(HttpContext httpContext, string parameterTypeName, string parameterName, string sourceValue, bool shouldThrow)
- {
- if (shouldThrow)
- {
- var message = string.Format(CultureInfo.InvariantCulture, ParameterBindingFailedExceptionMessage, parameterTypeName, parameterName, sourceValue);
- throw new BadHttpRequestException(message);
- }
+ public NullabilityInfoContext NullabilityContext { get; } = new();
+ }
- ParameterBindingFailed(GetLogger(httpContext), parameterTypeName, parameterName, sourceValue);
- }
+ private static class RequestDelegateFactoryConstants
+ {
+ public const string RouteAttribute = "Route (Attribute)";
+ public const string QueryAttribute = "Query (Attribute)";
+ public const string HeaderAttribute = "Header (Attribute)";
+ public const string BodyAttribute = "Body (Attribute)";
+ public const string ServiceAttribute = "Service (Attribute)";
+ public const string RouteParameter = "Route (Inferred)";
+ public const string QueryStringParameter = "Query String (Inferred)";
+ public const string ServiceParameter = "Services (Inferred)";
+ public const string BodyParameter = "Body (Inferred)";
+ public const string RouteOrQueryStringParameter = "Route or Query String (Inferred)";
+ }
- [LoggerMessage(3, LogLevel.Debug, ParameterBindingFailedLogMessage, EventName = "ParameterBindingFailed")]
- private static partial void ParameterBindingFailed(ILogger logger, string parameterType, string parameterName, string sourceValue);
+ private static partial class Log
+ {
+ private const string InvalidJsonRequestBodyMessage = @"Failed to read parameter ""{ParameterType} {ParameterName}"" from the request body as JSON.";
+ private const string InvalidJsonRequestBodyExceptionMessage = @"Failed to read parameter ""{0} {1}"" from the request body as JSON.";
- public static void RequiredParameterNotProvided(HttpContext httpContext, string parameterTypeName, string parameterName, string source, bool shouldThrow)
- {
- if (shouldThrow)
- {
- var message = string.Format(CultureInfo.InvariantCulture, RequiredParameterNotProvidedExceptionMessage, parameterTypeName, parameterName, source);
- throw new BadHttpRequestException(message);
- }
+ private const string ParameterBindingFailedLogMessage = @"Failed to bind parameter ""{ParameterType} {ParameterName}"" from ""{SourceValue}"".";
+ private const string ParameterBindingFailedExceptionMessage = @"Failed to bind parameter ""{0} {1}"" from ""{2}"".";
- RequiredParameterNotProvided(GetLogger(httpContext), parameterTypeName, parameterName, source);
- }
+ private const string RequiredParameterNotProvidedLogMessage = @"Required parameter ""{ParameterType} {ParameterName}"" was not provided from {Source}.";
+ private const string RequiredParameterNotProvidedExceptionMessage = @"Required parameter ""{0} {1}"" was not provided from {2}.";
- [LoggerMessage(4, LogLevel.Debug, RequiredParameterNotProvidedLogMessage, EventName = "RequiredParameterNotProvided")]
- private static partial void RequiredParameterNotProvided(ILogger logger, string parameterType, string parameterName, string source);
+ private const string UnexpectedContentTypeLogMessage = @"Expected a supported JSON media type but got ""{ContentType}"".";
+ private const string UnexpectedContentTypeExceptionMessage = @"Expected a supported JSON media type but got ""{0}"".";
- public static void ImplicitBodyNotProvided(HttpContext httpContext, string parameterName, bool shouldThrow)
- {
- if (shouldThrow)
- {
- var message = string.Format(CultureInfo.InvariantCulture, ImplicitBodyNotProvidedExceptionMessage, parameterName);
- throw new BadHttpRequestException(message);
- }
+ private const string ImplicitBodyNotProvidedLogMessage = @"Implicit body inferred for parameter ""{ParameterName}"" but no body was provided. Did you mean to use a Service instead?";
+ private const string ImplicitBodyNotProvidedExceptionMessage = @"Implicit body inferred for parameter ""{0}"" but no body was provided. Did you mean to use a Service instead?";
- ImplicitBodyNotProvided(GetLogger(httpContext), parameterName);
- }
+ // This doesn't take a shouldThrow parameter because an IOException indicates an aborted request rather than a "bad" request so
+ // a BadHttpRequestException feels wrong. The client shouldn't be able to read the Developer Exception Page at any rate.
+ public static void RequestBodyIOException(HttpContext httpContext, IOException exception)
+ => RequestBodyIOException(GetLogger(httpContext), exception);
- [LoggerMessage(5, LogLevel.Debug, ImplicitBodyNotProvidedLogMessage, EventName = "ImplicitBodyNotProvided")]
- private static partial void ImplicitBodyNotProvided(ILogger logger, string parameterName);
+ [LoggerMessage(1, LogLevel.Debug, "Reading the request body failed with an IOException.", EventName = "RequestBodyIOException")]
+ private static partial void RequestBodyIOException(ILogger logger, IOException exception);
- public static void UnexpectedContentType(HttpContext httpContext, string? contentType, bool shouldThrow)
+ public static void InvalidJsonRequestBody(HttpContext httpContext, string parameterTypeName, string parameterName, Exception exception, bool shouldThrow)
+ {
+ if (shouldThrow)
{
- if (shouldThrow)
- {
- var message = string.Format(CultureInfo.InvariantCulture, UnexpectedContentTypeExceptionMessage, contentType);
- throw new BadHttpRequestException(message, StatusCodes.Status415UnsupportedMediaType);
- }
-
- UnexpectedContentType(GetLogger(httpContext), contentType ?? "(none)");
+ var message = string.Format(CultureInfo.InvariantCulture, InvalidJsonRequestBodyExceptionMessage, parameterTypeName, parameterName);
+ throw new BadHttpRequestException(message, exception);
}
- [LoggerMessage(6, LogLevel.Debug, UnexpectedContentTypeLogMessage, EventName = "UnexpectedContentType")]
- private static partial void UnexpectedContentType(ILogger logger, string contentType);
+ InvalidJsonRequestBody(GetLogger(httpContext), parameterTypeName, parameterName, exception);
+ }
+
+ [LoggerMessage(2, LogLevel.Debug, InvalidJsonRequestBodyMessage, EventName = "InvalidJsonRequestBody")]
+ private static partial void InvalidJsonRequestBody(ILogger logger, string parameterType, string parameterName, Exception exception);
- private static ILogger GetLogger(HttpContext httpContext)
+ public static void ParameterBindingFailed(HttpContext httpContext, string parameterTypeName, string parameterName, string sourceValue, bool shouldThrow)
+ {
+ if (shouldThrow)
{
- var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
- return loggerFactory.CreateLogger(typeof(RequestDelegateFactory));
+ var message = string.Format(CultureInfo.InvariantCulture, ParameterBindingFailedExceptionMessage, parameterTypeName, parameterName, sourceValue);
+ throw new BadHttpRequestException(message);
}
+
+ ParameterBindingFailed(GetLogger(httpContext), parameterTypeName, parameterName, sourceValue);
}
- private static void EnsureRequestTaskOfNotNull<T>(Task<T?> task) where T : IResult
+ [LoggerMessage(3, LogLevel.Debug, ParameterBindingFailedLogMessage, EventName = "ParameterBindingFailed")]
+ private static partial void ParameterBindingFailed(ILogger logger, string parameterType, string parameterName, string sourceValue);
+
+ public static void RequiredParameterNotProvided(HttpContext httpContext, string parameterTypeName, string parameterName, string source, bool shouldThrow)
{
- if (task is null)
+ if (shouldThrow)
{
- throw new InvalidOperationException("The IResult in Task<IResult> response must not be null.");
+ var message = string.Format(CultureInfo.InvariantCulture, RequiredParameterNotProvidedExceptionMessage, parameterTypeName, parameterName, source);
+ throw new BadHttpRequestException(message);
}
+
+ RequiredParameterNotProvided(GetLogger(httpContext), parameterTypeName, parameterName, source);
}
- private static void EnsureRequestTaskNotNull(Task? task)
+ [LoggerMessage(4, LogLevel.Debug, RequiredParameterNotProvidedLogMessage, EventName = "RequiredParameterNotProvided")]
+ private static partial void RequiredParameterNotProvided(ILogger logger, string parameterType, string parameterName, string source);
+
+ public static void ImplicitBodyNotProvided(HttpContext httpContext, string parameterName, bool shouldThrow)
{
- if (task is null)
+ if (shouldThrow)
{
- throw new InvalidOperationException("The Task returned by the Delegate must not be null.");
+ var message = string.Format(CultureInfo.InvariantCulture, ImplicitBodyNotProvidedExceptionMessage, parameterName);
+ throw new BadHttpRequestException(message);
}
+
+ ImplicitBodyNotProvided(GetLogger(httpContext), parameterName);
}
- private static IResult EnsureRequestResultNotNull(IResult? result)
+ [LoggerMessage(5, LogLevel.Debug, ImplicitBodyNotProvidedLogMessage, EventName = "ImplicitBodyNotProvided")]
+ private static partial void ImplicitBodyNotProvided(ILogger logger, string parameterName);
+
+ public static void UnexpectedContentType(HttpContext httpContext, string? contentType, bool shouldThrow)
{
- if (result is null)
+ if (shouldThrow)
{
- throw new InvalidOperationException("The IResult returned by the Delegate must not be null.");
+ var message = string.Format(CultureInfo.InvariantCulture, UnexpectedContentTypeExceptionMessage, contentType);
+ throw new BadHttpRequestException(message, StatusCodes.Status415UnsupportedMediaType);
}
- return result;
+ UnexpectedContentType(GetLogger(httpContext), contentType ?? "(none)");
}
- private static void SetPlaintextContentType(HttpContext httpContext)
+ [LoggerMessage(6, LogLevel.Debug, UnexpectedContentTypeLogMessage, EventName = "UnexpectedContentType")]
+ private static partial void UnexpectedContentType(ILogger logger, string contentType);
+
+ private static ILogger GetLogger(HttpContext httpContext)
{
- httpContext.Response.ContentType ??= "text/plain; charset=utf-8";
+ var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
+ return loggerFactory.CreateLogger(typeof(RequestDelegateFactory));
}
+ }
- private static string BuildErrorMessageForMultipleBodyParameters(FactoryContext factoryContext)
+ private static void EnsureRequestTaskOfNotNull<T>(Task<T?> task) where T : IResult
+ {
+ if (task is null)
{
- var errorMessage = new StringBuilder();
- errorMessage.AppendLine("Failure to infer one or more parameters.");
- errorMessage.AppendLine("Below is the list of parameters that we found: ");
- errorMessage.AppendLine();
- errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
- errorMessage.AppendLine("---------------------------------------------------------------------------------");
+ throw new InvalidOperationException("The IResult in Task<IResult> response must not be null.");
+ }
+ }
- foreach (var kv in factoryContext.TrackedParameters)
- {
- errorMessage.AppendLine(FormattableString.Invariant($"{kv.Key,-19} | {kv.Value,-15}"));
- }
- errorMessage.AppendLine().AppendLine();
- errorMessage.AppendLine("Did you mean to register the \"UNKNOWN\" parameters as a Service?")
- .AppendLine();
- return errorMessage.ToString();
+ private static void EnsureRequestTaskNotNull(Task? task)
+ {
+ if (task is null)
+ {
+ throw new InvalidOperationException("The Task returned by the Delegate must not be null.");
+ }
+ }
+
+ private static IResult EnsureRequestResultNotNull(IResult? result)
+ {
+ if (result is null)
+ {
+ throw new InvalidOperationException("The IResult returned by the Delegate must not be null.");
}
- private static string BuildErrorMessageForInferredBodyParameter(FactoryContext factoryContext)
+ return result;
+ }
+
+ private static void SetPlaintextContentType(HttpContext httpContext)
+ {
+ httpContext.Response.ContentType ??= "text/plain; charset=utf-8";
+ }
+
+ private static string BuildErrorMessageForMultipleBodyParameters(FactoryContext factoryContext)
+ {
+ var errorMessage = new StringBuilder();
+ errorMessage.AppendLine("Failure to infer one or more parameters.");
+ errorMessage.AppendLine("Below is the list of parameters that we found: ");
+ errorMessage.AppendLine();
+ errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
+ errorMessage.AppendLine("---------------------------------------------------------------------------------");
+
+ foreach (var kv in factoryContext.TrackedParameters)
{
- var errorMessage = new StringBuilder();
- errorMessage.AppendLine("Body was inferred but the method does not allow inferred body parameters.");
- errorMessage.AppendLine("Below is the list of parameters that we found: ");
- errorMessage.AppendLine();
- errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
- errorMessage.AppendLine("---------------------------------------------------------------------------------");
+ errorMessage.AppendLine(FormattableString.Invariant($"{kv.Key,-19} | {kv.Value,-15}"));
+ }
+ errorMessage.AppendLine().AppendLine();
+ errorMessage.AppendLine("Did you mean to register the \"UNKNOWN\" parameters as a Service?")
+ .AppendLine();
+ return errorMessage.ToString();
+ }
- foreach (var kv in factoryContext.TrackedParameters)
- {
- errorMessage.AppendLine(FormattableString.Invariant($"{kv.Key,-19} | {kv.Value,-15}"));
- }
- errorMessage.AppendLine().AppendLine();
- errorMessage.AppendLine("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?")
- .AppendLine();
- return errorMessage.ToString();
+ private static string BuildErrorMessageForInferredBodyParameter(FactoryContext factoryContext)
+ {
+ var errorMessage = new StringBuilder();
+ errorMessage.AppendLine("Body was inferred but the method does not allow inferred body parameters.");
+ errorMessage.AppendLine("Below is the list of parameters that we found: ");
+ errorMessage.AppendLine();
+ errorMessage.AppendLine(FormattableString.Invariant($"{"Parameter",-20}| {"Source",-30}"));
+ errorMessage.AppendLine("---------------------------------------------------------------------------------");
+
+ foreach (var kv in factoryContext.TrackedParameters)
+ {
+ errorMessage.AppendLine(FormattableString.Invariant($"{kv.Key,-19} | {kv.Value,-15}"));
}
+ errorMessage.AppendLine().AppendLine();
+ errorMessage.AppendLine("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?")
+ .AppendLine();
+ return errorMessage.ToString();
}
}
diff --git a/src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs b/src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs
index 6d41ece481..892cbd2c7e 100644
--- a/src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs
+++ b/src/Http/Http.Extensions/src/RequestDelegateFactoryOptions.cs
@@ -4,32 +4,31 @@
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Options for controlling the behavior of the <see cref="RequestDelegate" /> when created using <see cref="RequestDelegateFactory" />.
+/// </summary>
+public sealed class RequestDelegateFactoryOptions
{
/// <summary>
- /// Options for controlling the behavior of the <see cref="RequestDelegate" /> when created using <see cref="RequestDelegateFactory" />.
+ /// The <see cref="IServiceProvider"/> instance used to detect if handler parameters are services.
/// </summary>
- public sealed class RequestDelegateFactoryOptions
- {
- /// <summary>
- /// The <see cref="IServiceProvider"/> instance used to detect if handler parameters are services.
- /// </summary>
- public IServiceProvider? ServiceProvider { get; init; }
+ public IServiceProvider? ServiceProvider { get; init; }
- /// <summary>
- /// The list of route parameter names that are specified for this handler.
- /// </summary>
- public IEnumerable<string>? RouteParameterNames { get; init; }
+ /// <summary>
+ /// The list of route parameter names that are specified for this handler.
+ /// </summary>
+ public IEnumerable<string>? RouteParameterNames { get; init; }
- /// <summary>
- /// Controls whether the <see cref="RequestDelegate"/> should throw a <see cref="BadHttpRequestException"/> in addition to
- /// writing a <see cref="LogLevel.Debug"/> log when handling invalid requests.
- /// </summary>
- public bool ThrowOnBadRequest { get; init; }
+ /// <summary>
+ /// Controls whether the <see cref="RequestDelegate"/> should throw a <see cref="BadHttpRequestException"/> in addition to
+ /// writing a <see cref="LogLevel.Debug"/> log when handling invalid requests.
+ /// </summary>
+ public bool ThrowOnBadRequest { get; init; }
- /// <summary>
- /// Prevent the <see cref="RequestDelegateFactory" /> from inferring a parameter should be bound from the request body without an attribute that implements <see cref="IFromBodyMetadata"/>.
- /// </summary>
- public bool DisableInferBodyFromParameters { get; init; }
- }
+ /// <summary>
+ /// Prevent the <see cref="RequestDelegateFactory" /> from inferring a parameter should be bound from the request body without an attribute that implements <see cref="IFromBodyMetadata"/>.
+ /// </summary>
+ public bool DisableInferBodyFromParameters { get; init; }
}
diff --git a/src/Http/Http.Extensions/src/RequestHeaders.cs b/src/Http/Http.Extensions/src/RequestHeaders.cs
index dcccc0231f..a26757c8d7 100644
--- a/src/Http/Http.Extensions/src/RequestHeaders.cs
+++ b/src/Http/Http.Extensions/src/RequestHeaders.cs
@@ -6,436 +6,435 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Headers
+namespace Microsoft.AspNetCore.Http.Headers;
+
+/// <summary>
+/// Strongly typed HTTP request headers.
+/// </summary>
+public class RequestHeaders
{
/// <summary>
- /// Strongly typed HTTP request headers.
+ /// Initializes a new instance of <see cref="RequestHeaders"/>.
/// </summary>
- public class RequestHeaders
+ /// <param name="headers">The request headers.</param>
+ public RequestHeaders(IHeaderDictionary headers)
{
- /// <summary>
- /// Initializes a new instance of <see cref="RequestHeaders"/>.
- /// </summary>
- /// <param name="headers">The request headers.</param>
- public RequestHeaders(IHeaderDictionary headers)
+ if (headers == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
-
- Headers = headers;
+ throw new ArgumentNullException(nameof(headers));
}
- /// <summary>
- /// Gets the backing request header dictionary.
- /// </summary>
- public IHeaderDictionary Headers { get; }
+ Headers = headers;
+ }
+
+ /// <summary>
+ /// Gets the backing request header dictionary.
+ /// </summary>
+ public IHeaderDictionary Headers { get; }
- /// <summary>
- /// Gets or sets the <c>Accept</c> header for an HTTP request.
- /// </summary>
- public IList<MediaTypeHeaderValue> Accept
+ /// <summary>
+ /// Gets or sets the <c>Accept</c> header for an HTTP request.
+ /// </summary>
+ public IList<MediaTypeHeaderValue> Accept
+ {
+ get
{
- get
- {
- return Headers.Accept.GetList<MediaTypeHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.Accept, value);
- }
+ return Headers.Accept.GetList<MediaTypeHeaderValue>();
}
-
- /// <summary>
- /// Gets or sets the <c>Accept-Charset</c> header for an HTTP request.
- /// </summary>
- public IList<StringWithQualityHeaderValue> AcceptCharset
+ set
{
- get
- {
- return Headers.AcceptCharset.GetList<StringWithQualityHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.AcceptCharset, value);
- }
+ Headers.SetList(HeaderNames.Accept, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Accept-Encoding</c> header for an HTTP request.
- /// </summary>
- public IList<StringWithQualityHeaderValue> AcceptEncoding
+ /// <summary>
+ /// Gets or sets the <c>Accept-Charset</c> header for an HTTP request.
+ /// </summary>
+ public IList<StringWithQualityHeaderValue> AcceptCharset
+ {
+ get
{
- get
- {
- return Headers.AcceptEncoding.GetList<StringWithQualityHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.AcceptEncoding, value);
- }
+ return Headers.AcceptCharset.GetList<StringWithQualityHeaderValue>();
}
-
- /// <summary>
- /// Gets or sets the <c>Accept-Language</c> header for an HTTP request.
- /// </summary>
- public IList<StringWithQualityHeaderValue> AcceptLanguage
+ set
{
- get
- {
- return Headers.AcceptLanguage.GetList<StringWithQualityHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.AcceptLanguage, value);
- }
+ Headers.SetList(HeaderNames.AcceptCharset, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Cache-Control</c> header for an HTTP request.
- /// </summary>
- public CacheControlHeaderValue? CacheControl
+ /// <summary>
+ /// Gets or sets the <c>Accept-Encoding</c> header for an HTTP request.
+ /// </summary>
+ public IList<StringWithQualityHeaderValue> AcceptEncoding
+ {
+ get
{
- get
- {
- return Headers.Get<CacheControlHeaderValue>(HeaderNames.CacheControl);
- }
- set
- {
- Headers.Set(HeaderNames.CacheControl, value);
- }
+ return Headers.AcceptEncoding.GetList<StringWithQualityHeaderValue>();
}
-
- /// <summary>
- /// Gets or sets the <c>Content-Disposition</c> header for an HTTP request.
- /// </summary>
- public ContentDispositionHeaderValue? ContentDisposition
+ set
{
- get
- {
- return Headers.Get<ContentDispositionHeaderValue>(HeaderNames.ContentDisposition);
- }
- set
- {
- Headers.Set(HeaderNames.ContentDisposition, value);
- }
+ Headers.SetList(HeaderNames.AcceptEncoding, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Content-Length</c> header for an HTTP request.
- /// </summary>
- public long? ContentLength
+ /// <summary>
+ /// Gets or sets the <c>Accept-Language</c> header for an HTTP request.
+ /// </summary>
+ public IList<StringWithQualityHeaderValue> AcceptLanguage
+ {
+ get
{
- get
- {
- return Headers.ContentLength;
- }
- set
- {
- Headers.ContentLength = value;
- }
+ return Headers.AcceptLanguage.GetList<StringWithQualityHeaderValue>();
}
-
- /// <summary>
- /// Gets or sets the <c>Content-Range</c> header for an HTTP request.
- /// </summary>
- public ContentRangeHeaderValue? ContentRange
+ set
{
- get
- {
- return Headers.Get<ContentRangeHeaderValue>(HeaderNames.ContentRange);
- }
- set
- {
- Headers.Set(HeaderNames.ContentRange, value);
- }
+ Headers.SetList(HeaderNames.AcceptLanguage, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Content-Type</c> header for an HTTP request.
- /// </summary>
- public MediaTypeHeaderValue? ContentType
+ /// <summary>
+ /// Gets or sets the <c>Cache-Control</c> header for an HTTP request.
+ /// </summary>
+ public CacheControlHeaderValue? CacheControl
+ {
+ get
{
- get
- {
- return Headers.Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
- }
- set
- {
- Headers.Set(HeaderNames.ContentType, value);
- }
+ return Headers.Get<CacheControlHeaderValue>(HeaderNames.CacheControl);
}
-
- /// <summary>
- /// Gets or sets the <c>Cookie</c> header for an HTTP request.
- /// </summary>
- public IList<CookieHeaderValue> Cookie
+ set
{
- get
- {
- return Headers.Cookie.GetList<CookieHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.Cookie, value);
- }
+ Headers.Set(HeaderNames.CacheControl, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Date</c> header for an HTTP request.
- /// </summary>
- public DateTimeOffset? Date
+ /// <summary>
+ /// Gets or sets the <c>Content-Disposition</c> header for an HTTP request.
+ /// </summary>
+ public ContentDispositionHeaderValue? ContentDisposition
+ {
+ get
{
- get
- {
- return Headers.GetDate(HeaderNames.Date);
- }
- set
- {
- Headers.SetDate(HeaderNames.Date, value);
- }
+ return Headers.Get<ContentDispositionHeaderValue>(HeaderNames.ContentDisposition);
}
+ set
+ {
+ Headers.Set(HeaderNames.ContentDisposition, value);
+ }
+ }
- /// <summary>
- /// Gets or sets the <c>Expires</c> header for an HTTP request.
- /// </summary>
- public DateTimeOffset? Expires
+ /// <summary>
+ /// Gets or sets the <c>Content-Length</c> header for an HTTP request.
+ /// </summary>
+ public long? ContentLength
+ {
+ get
{
- get
- {
- return Headers.GetDate(HeaderNames.Expires);
- }
- set
- {
- Headers.SetDate(HeaderNames.Expires, value);
- }
+ return Headers.ContentLength;
+ }
+ set
+ {
+ Headers.ContentLength = value;
}
+ }
- /// <summary>
- /// Gets or sets the <c>Host</c> header for an HTTP request.
- /// </summary>
- public HostString Host
+ /// <summary>
+ /// Gets or sets the <c>Content-Range</c> header for an HTTP request.
+ /// </summary>
+ public ContentRangeHeaderValue? ContentRange
+ {
+ get
{
- get
- {
- return HostString.FromUriComponent(Headers.Host.ToString());
- }
- set
- {
- Headers.Host = value.ToUriComponent();
- }
+ return Headers.Get<ContentRangeHeaderValue>(HeaderNames.ContentRange);
}
+ set
+ {
+ Headers.Set(HeaderNames.ContentRange, value);
+ }
+ }
- /// <summary>
- /// Gets or sets the <c>If-Match</c> header for an HTTP request.
- /// </summary>
- public IList<EntityTagHeaderValue> IfMatch
+ /// <summary>
+ /// Gets or sets the <c>Content-Type</c> header for an HTTP request.
+ /// </summary>
+ public MediaTypeHeaderValue? ContentType
+ {
+ get
{
- get
- {
- return Headers.IfMatch.GetList<EntityTagHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.IfMatch, value);
- }
+ return Headers.Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+ }
+ set
+ {
+ Headers.Set(HeaderNames.ContentType, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>If-Modified-Since</c> header for an HTTP request.
- /// </summary>
- public DateTimeOffset? IfModifiedSince
+ /// <summary>
+ /// Gets or sets the <c>Cookie</c> header for an HTTP request.
+ /// </summary>
+ public IList<CookieHeaderValue> Cookie
+ {
+ get
{
- get
- {
- return Headers.GetDate(HeaderNames.IfModifiedSince);
- }
- set
- {
- Headers.SetDate(HeaderNames.IfModifiedSince, value);
- }
+ return Headers.Cookie.GetList<CookieHeaderValue>();
}
+ set
+ {
+ Headers.SetList(HeaderNames.Cookie, value);
+ }
+ }
- /// <summary>
- /// Gets or sets the <c>If-None-Match</c> header for an HTTP request.
- /// </summary>
- public IList<EntityTagHeaderValue> IfNoneMatch
+ /// <summary>
+ /// Gets or sets the <c>Date</c> header for an HTTP request.
+ /// </summary>
+ public DateTimeOffset? Date
+ {
+ get
{
- get
- {
- return Headers.IfNoneMatch.GetList<EntityTagHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.IfNoneMatch, value);
- }
+ return Headers.GetDate(HeaderNames.Date);
+ }
+ set
+ {
+ Headers.SetDate(HeaderNames.Date, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>If-Range</c> header for an HTTP request.
- /// </summary>
- public RangeConditionHeaderValue? IfRange
+ /// <summary>
+ /// Gets or sets the <c>Expires</c> header for an HTTP request.
+ /// </summary>
+ public DateTimeOffset? Expires
+ {
+ get
{
- get
- {
- return Headers.Get<RangeConditionHeaderValue>(HeaderNames.IfRange);
- }
- set
- {
- Headers.Set(HeaderNames.IfRange, value);
- }
+ return Headers.GetDate(HeaderNames.Expires);
+ }
+ set
+ {
+ Headers.SetDate(HeaderNames.Expires, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>If-Unmodified-Since</c> header for an HTTP request.
- /// </summary>
- public DateTimeOffset? IfUnmodifiedSince
+ /// <summary>
+ /// Gets or sets the <c>Host</c> header for an HTTP request.
+ /// </summary>
+ public HostString Host
+ {
+ get
{
- get
- {
- return Headers.GetDate(HeaderNames.IfUnmodifiedSince);
- }
- set
- {
- Headers.SetDate(HeaderNames.IfUnmodifiedSince, value);
- }
+ return HostString.FromUriComponent(Headers.Host.ToString());
+ }
+ set
+ {
+ Headers.Host = value.ToUriComponent();
}
+ }
- /// <summary>
- /// Gets or sets the <c>Last-Modified</c> header for an HTTP request.
- /// </summary>
- public DateTimeOffset? LastModified
+ /// <summary>
+ /// Gets or sets the <c>If-Match</c> header for an HTTP request.
+ /// </summary>
+ public IList<EntityTagHeaderValue> IfMatch
+ {
+ get
{
- get
- {
- return Headers.GetDate(HeaderNames.LastModified);
- }
- set
- {
- Headers.SetDate(HeaderNames.LastModified, value);
- }
+ return Headers.IfMatch.GetList<EntityTagHeaderValue>();
+ }
+ set
+ {
+ Headers.SetList(HeaderNames.IfMatch, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Range</c> header for an HTTP request.
- /// </summary>
- public RangeHeaderValue? Range
+ /// <summary>
+ /// Gets or sets the <c>If-Modified-Since</c> header for an HTTP request.
+ /// </summary>
+ public DateTimeOffset? IfModifiedSince
+ {
+ get
{
- get
- {
- return Headers.Get<RangeHeaderValue>(HeaderNames.Range);
- }
- set
- {
- Headers.Set(HeaderNames.Range, value);
- }
+ return Headers.GetDate(HeaderNames.IfModifiedSince);
+ }
+ set
+ {
+ Headers.SetDate(HeaderNames.IfModifiedSince, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Referer</c> header for an HTTP request.
- /// </summary>
- public Uri? Referer
+ /// <summary>
+ /// Gets or sets the <c>If-None-Match</c> header for an HTTP request.
+ /// </summary>
+ public IList<EntityTagHeaderValue> IfNoneMatch
+ {
+ get
{
- get
- {
- if (Uri.TryCreate(Headers.Referer, UriKind.RelativeOrAbsolute, out var uri))
- {
- return uri;
- }
- return null;
- }
- set
- {
- Headers.Set(HeaderNames.Referer, value == null ? null : UriHelper.Encode(value));
- }
+ return Headers.IfNoneMatch.GetList<EntityTagHeaderValue>();
+ }
+ set
+ {
+ Headers.SetList(HeaderNames.IfNoneMatch, value);
}
+ }
- /// <summary>
- /// Gets the value of header with <paramref name="name"/>.
- /// </summary>
- /// <remarks><typeparamref name="T"/> must contain a TryParse method with the signature <c>public static bool TryParse(string, out T)</c>.</remarks>
- /// <typeparam name="T">The type of the header.
- /// The given type must have a static TryParse method.</typeparam>
- /// <param name="name">The name of the header to retrieve.</param>
- /// <returns>The value of the header.</returns>
- public T? Get<T>(string name)
+ /// <summary>
+ /// Gets or sets the <c>If-Range</c> header for an HTTP request.
+ /// </summary>
+ public RangeConditionHeaderValue? IfRange
+ {
+ get
{
- return Headers.Get<T>(name);
+ return Headers.Get<RangeConditionHeaderValue>(HeaderNames.IfRange);
}
+ set
+ {
+ Headers.Set(HeaderNames.IfRange, value);
+ }
+ }
- /// <summary>
- /// Gets the values of header with <paramref name="name"/>.
- /// </summary>
- /// <remarks><typeparamref name="T"/> must contain a TryParseList method with the signature <c>public static bool TryParseList(IList&lt;string&gt;, out IList&lt;T&gt;)</c>.</remarks>
- /// <typeparam name="T">The type of the header.
- /// The given type must have a static TryParseList method.</typeparam>
- /// <param name="name">The name of the header to retrieve.</param>
- /// <returns>List of values of the header.</returns>
- public IList<T> GetList<T>(string name)
+ /// <summary>
+ /// Gets or sets the <c>If-Unmodified-Since</c> header for an HTTP request.
+ /// </summary>
+ public DateTimeOffset? IfUnmodifiedSince
+ {
+ get
+ {
+ return Headers.GetDate(HeaderNames.IfUnmodifiedSince);
+ }
+ set
{
- return Headers.GetList<T>(name);
+ Headers.SetDate(HeaderNames.IfUnmodifiedSince, value);
}
+ }
- /// <summary>
- /// Sets the header value.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="value">The header value.</param>
- public void Set(string name, object? value)
+ /// <summary>
+ /// Gets or sets the <c>Last-Modified</c> header for an HTTP request.
+ /// </summary>
+ public DateTimeOffset? LastModified
+ {
+ get
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ return Headers.GetDate(HeaderNames.LastModified);
+ }
+ set
+ {
+ Headers.SetDate(HeaderNames.LastModified, value);
+ }
+ }
- Headers.Set(name, value);
+ /// <summary>
+ /// Gets or sets the <c>Range</c> header for an HTTP request.
+ /// </summary>
+ public RangeHeaderValue? Range
+ {
+ get
+ {
+ return Headers.Get<RangeHeaderValue>(HeaderNames.Range);
+ }
+ set
+ {
+ Headers.Set(HeaderNames.Range, value);
}
+ }
- /// <summary>
- /// Sets the specified header and it's values.
- /// </summary>
- /// <typeparam name="T">The type of the value.</typeparam>
- /// <param name="name">The header name.</param>
- /// <param name="values">The sequence of header values.</param>
- public void SetList<T>(string name, IList<T>? values)
+ /// <summary>
+ /// Gets or sets the <c>Referer</c> header for an HTTP request.
+ /// </summary>
+ public Uri? Referer
+ {
+ get
{
- if (name == null)
+ if (Uri.TryCreate(Headers.Referer, UriKind.RelativeOrAbsolute, out var uri))
{
- throw new ArgumentNullException(nameof(name));
+ return uri;
}
+ return null;
+ }
+ set
+ {
+ Headers.Set(HeaderNames.Referer, value == null ? null : UriHelper.Encode(value));
+ }
+ }
- Headers.SetList<T>(name, values);
+ /// <summary>
+ /// Gets the value of header with <paramref name="name"/>.
+ /// </summary>
+ /// <remarks><typeparamref name="T"/> must contain a TryParse method with the signature <c>public static bool TryParse(string, out T)</c>.</remarks>
+ /// <typeparam name="T">The type of the header.
+ /// The given type must have a static TryParse method.</typeparam>
+ /// <param name="name">The name of the header to retrieve.</param>
+ /// <returns>The value of the header.</returns>
+ public T? Get<T>(string name)
+ {
+ return Headers.Get<T>(name);
+ }
+
+ /// <summary>
+ /// Gets the values of header with <paramref name="name"/>.
+ /// </summary>
+ /// <remarks><typeparamref name="T"/> must contain a TryParseList method with the signature <c>public static bool TryParseList(IList&lt;string&gt;, out IList&lt;T&gt;)</c>.</remarks>
+ /// <typeparam name="T">The type of the header.
+ /// The given type must have a static TryParseList method.</typeparam>
+ /// <param name="name">The name of the header to retrieve.</param>
+ /// <returns>List of values of the header.</returns>
+ public IList<T> GetList<T>(string name)
+ {
+ return Headers.GetList<T>(name);
+ }
+
+ /// <summary>
+ /// Sets the header value.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="value">The header value.</param>
+ public void Set(string name, object? value)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Appends the header name and value.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="value">The header value.</param>
- public void Append(string name, object value)
+ Headers.Set(name, value);
+ }
+
+ /// <summary>
+ /// Sets the specified header and it's values.
+ /// </summary>
+ /// <typeparam name="T">The type of the value.</typeparam>
+ /// <param name="name">The header name.</param>
+ /// <param name="values">The sequence of header values.</param>
+ public void SetList<T>(string name, IList<T>? values)
+ {
+ if (name == null)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ throw new ArgumentNullException(nameof(name));
+ }
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
+ Headers.SetList<T>(name, values);
+ }
- Headers.Append(name, value.ToString());
+ /// <summary>
+ /// Appends the header name and value.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="value">The header value.</param>
+ public void Append(string name, object value)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Appends the header name and it's values.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="values">The header values.</param>
- public void AppendList<T>(string name, IList<T> values)
+ if (value == null)
{
- Headers.AppendList<T>(name, values);
+ throw new ArgumentNullException(nameof(value));
}
+
+ Headers.Append(name, value.ToString());
+ }
+
+ /// <summary>
+ /// Appends the header name and it's values.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="values">The header values.</param>
+ 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
index 4a3607625c..992fa29214 100644
--- a/src/Http/Http.Extensions/src/ResponseExtensions.cs
+++ b/src/Http/Http.Extensions/src/ResponseExtensions.cs
@@ -5,54 +5,53 @@ using System;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods for <see cref="HttpResponse"/>.
+/// </summary>
+public static class ResponseExtensions
{
/// <summary>
- /// Extension methods for <see cref="HttpResponse"/>.
+ /// Clears the HTTP response.
+ /// <para>
+ /// This invocation resets the response headers, response status code, and response body.
+ /// </para>
/// </summary>
- public static class ResponseExtensions
+ /// <param name="response">The <see cref="HttpResponse"/> to clear.</param>
+ public static void Clear(this HttpResponse response)
{
- /// <summary>
- /// Clears the HTTP response.
- /// <para>
- /// This invocation resets the response headers, response status code, and response body.
- /// </para>
- /// </summary>
- /// <param name="response">The <see cref="HttpResponse"/> to clear.</param>
- public static void Clear(this HttpResponse response)
+ if (response.HasStarted)
{
- 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);
- }
+ throw new InvalidOperationException("The response cannot be cleared, it has already started sending.");
}
-
- /// <summary>
- /// Returns a redirect response (HTTP 301, HTTP 302, HTTP 307 or HTTP 308) to the client.
- /// </summary>
- /// <param name="response">The <see cref="HttpResponse"/> to redirect.</param>
- /// <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 or 308), otherwise <c>false</c> (302 or 307).</param>
- /// <param name="preserveMethod"><c>True</c> if the redirect needs to reuse the method and body (308 or 307), otherwise <c>false</c> (301 or 302).</param>
- public static void Redirect(this HttpResponse response, string location, bool permanent, bool preserveMethod)
+ response.StatusCode = 200;
+ response.HttpContext.Features.Get<IHttpResponseFeature>()!.ReasonPhrase = null;
+ response.Headers.Clear();
+ if (response.Body.CanSeek)
{
- if (preserveMethod)
- {
- response.StatusCode = permanent ? StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect;
- }
- else
- {
- response.StatusCode = permanent ? StatusCodes.Status301MovedPermanently : StatusCodes.Status302Found;
- }
+ response.Body.SetLength(0);
+ }
+ }
- response.Headers.Location = location;
+ /// <summary>
+ /// Returns a redirect response (HTTP 301, HTTP 302, HTTP 307 or HTTP 308) to the client.
+ /// </summary>
+ /// <param name="response">The <see cref="HttpResponse"/> to redirect.</param>
+ /// <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 or 308), otherwise <c>false</c> (302 or 307).</param>
+ /// <param name="preserveMethod"><c>True</c> if the redirect needs to reuse the method and body (308 or 307), otherwise <c>false</c> (301 or 302).</param>
+ public static void Redirect(this HttpResponse response, string location, bool permanent, bool preserveMethod)
+ {
+ if (preserveMethod)
+ {
+ response.StatusCode = permanent ? StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect;
}
+ else
+ {
+ response.StatusCode = permanent ? StatusCodes.Status301MovedPermanently : StatusCodes.Status302Found;
+ }
+
+ response.Headers.Location = location;
}
}
diff --git a/src/Http/Http.Extensions/src/ResponseHeaders.cs b/src/Http/Http.Extensions/src/ResponseHeaders.cs
index d5f72dcf6a..f4212e3232 100644
--- a/src/Http/Http.Extensions/src/ResponseHeaders.cs
+++ b/src/Http/Http.Extensions/src/ResponseHeaders.cs
@@ -6,286 +6,285 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Headers
+namespace Microsoft.AspNetCore.Http.Headers;
+
+/// <summary>
+/// Strongly typed HTTP response headers.
+/// </summary>
+public class ResponseHeaders
{
/// <summary>
- /// Strongly typed HTTP response headers.
+ /// Initializes a new instance of <see cref="ResponseHeaders"/>.
/// </summary>
- public class ResponseHeaders
+ /// <param name="headers">The request headers.</param>
+ public ResponseHeaders(IHeaderDictionary headers)
{
- /// <summary>
- /// Initializes a new instance of <see cref="ResponseHeaders"/>.
- /// </summary>
- /// <param name="headers">The request headers.</param>
- public ResponseHeaders(IHeaderDictionary headers)
+ if (headers == null)
{
- if (headers == null)
- {
- throw new ArgumentNullException(nameof(headers));
- }
-
- Headers = headers;
+ throw new ArgumentNullException(nameof(headers));
}
- /// <summary>
- /// Gets the backing response header dictionary.
- /// </summary>
- public IHeaderDictionary Headers { get; }
+ Headers = headers;
+ }
+
+ /// <summary>
+ /// Gets the backing response header dictionary.
+ /// </summary>
+ public IHeaderDictionary Headers { get; }
- /// <summary>
- /// Gets or sets the <c>Cache-Control</c> header for an HTTP response.
- /// </summary>
- public CacheControlHeaderValue? CacheControl
+ /// <summary>
+ /// Gets or sets the <c>Cache-Control</c> header for an HTTP response.
+ /// </summary>
+ public CacheControlHeaderValue? CacheControl
+ {
+ get
{
- get
- {
- return Headers.Get<CacheControlHeaderValue>(HeaderNames.CacheControl);
- }
- set
- {
- Headers.Set(HeaderNames.CacheControl, value);
- }
+ return Headers.Get<CacheControlHeaderValue>(HeaderNames.CacheControl);
}
-
- /// <summary>
- /// Gets or sets the <c>Content-Disposition</c> header for an HTTP response.
- /// </summary>
- public ContentDispositionHeaderValue? ContentDisposition
+ set
{
- get
- {
- return Headers.Get<ContentDispositionHeaderValue>(HeaderNames.ContentDisposition);
- }
- set
- {
- Headers.Set(HeaderNames.ContentDisposition, value);
- }
+ Headers.Set(HeaderNames.CacheControl, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Content-Length</c> header for an HTTP response.
- /// </summary>
- public long? ContentLength
+ /// <summary>
+ /// Gets or sets the <c>Content-Disposition</c> header for an HTTP response.
+ /// </summary>
+ public ContentDispositionHeaderValue? ContentDisposition
+ {
+ get
{
- get
- {
- return Headers.ContentLength;
- }
- set
- {
- Headers.ContentLength = value;
- }
+ return Headers.Get<ContentDispositionHeaderValue>(HeaderNames.ContentDisposition);
}
-
- /// <summary>
- /// Gets or sets the <c>Content-Range</c> header for an HTTP response.
- /// </summary>
- public ContentRangeHeaderValue? ContentRange
+ set
{
- get
- {
- return Headers.Get<ContentRangeHeaderValue>(HeaderNames.ContentRange);
- }
- set
- {
- Headers.Set(HeaderNames.ContentRange, value);
- }
+ Headers.Set(HeaderNames.ContentDisposition, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Content-Type</c> header for an HTTP response.
- /// </summary>
- public MediaTypeHeaderValue? ContentType
+ /// <summary>
+ /// Gets or sets the <c>Content-Length</c> header for an HTTP response.
+ /// </summary>
+ public long? ContentLength
+ {
+ get
{
- get
- {
- return Headers.Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
- }
- set
- {
- Headers.Set(HeaderNames.ContentType, value);
- }
+ return Headers.ContentLength;
}
-
- /// <summary>
- /// Gets or sets the <c>Date</c> header for an HTTP response.
- /// </summary>
- public DateTimeOffset? Date
+ set
{
- get
- {
- return Headers.GetDate(HeaderNames.Date);
- }
- set
- {
- Headers.SetDate(HeaderNames.Date, value);
- }
+ Headers.ContentLength = value;
}
+ }
- /// <summary>
- /// Gets or sets the <c>ETag</c> header for an HTTP response.
- /// </summary>
- public EntityTagHeaderValue? ETag
+ /// <summary>
+ /// Gets or sets the <c>Content-Range</c> header for an HTTP response.
+ /// </summary>
+ public ContentRangeHeaderValue? ContentRange
+ {
+ get
{
- get
- {
- return Headers.Get<EntityTagHeaderValue>(HeaderNames.ETag);
- }
- set
- {
- Headers.Set(HeaderNames.ETag, value);
- }
+ return Headers.Get<ContentRangeHeaderValue>(HeaderNames.ContentRange);
}
-
- /// <summary>
- /// Gets or sets the <c>Expires</c> header for an HTTP response.
- /// </summary>
- public DateTimeOffset? Expires
+ set
{
- get
- {
- return Headers.GetDate(HeaderNames.Expires);
- }
- set
- {
- Headers.SetDate(HeaderNames.Expires, value);
- }
+ Headers.Set(HeaderNames.ContentRange, value);
}
+ }
- /// <summary>
- /// Gets or sets the <c>Last-Modified</c> header for an HTTP response.
- /// </summary>
- public DateTimeOffset? LastModified
+ /// <summary>
+ /// Gets or sets the <c>Content-Type</c> header for an HTTP response.
+ /// </summary>
+ public MediaTypeHeaderValue? ContentType
+ {
+ get
{
- get
- {
- return Headers.GetDate(HeaderNames.LastModified);
- }
- set
- {
- Headers.SetDate(HeaderNames.LastModified, value);
- }
+ return Headers.Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
}
+ set
+ {
+ Headers.Set(HeaderNames.ContentType, value);
+ }
+ }
- /// <summary>
- /// Gets or sets the <c>Location</c> header for an HTTP response.
- /// </summary>
- public Uri? Location
+ /// <summary>
+ /// Gets or sets the <c>Date</c> header for an HTTP response.
+ /// </summary>
+ public DateTimeOffset? Date
+ {
+ get
{
- get
- {
- if (Uri.TryCreate(Headers.Location, UriKind.RelativeOrAbsolute, out var uri))
- {
- return uri;
- }
- return null;
- }
- set
- {
- Headers.Set(HeaderNames.Location, value == null ? null : UriHelper.Encode(value));
- }
+ return Headers.GetDate(HeaderNames.Date);
}
+ set
+ {
+ Headers.SetDate(HeaderNames.Date, value);
+ }
+ }
- /// <summary>
- /// Gets or sets the <c>Set-Cookie</c> header for an HTTP response.
- /// </summary>
- public IList<SetCookieHeaderValue> SetCookie
+ /// <summary>
+ /// Gets or sets the <c>ETag</c> header for an HTTP response.
+ /// </summary>
+ public EntityTagHeaderValue? ETag
+ {
+ get
{
- get
- {
- return Headers.SetCookie.GetList<SetCookieHeaderValue>();
- }
- set
- {
- Headers.SetList(HeaderNames.SetCookie, value);
- }
+ return Headers.Get<EntityTagHeaderValue>(HeaderNames.ETag);
}
+ set
+ {
+ Headers.Set(HeaderNames.ETag, value);
+ }
+ }
- /// <summary>
- /// Gets the value of header with <paramref name="name"/>.
- /// </summary>
- /// <remarks><typeparamref name="T"/> must contain a TryParse method with the signature <c>public static bool TryParse(string, out T)</c>.</remarks>
- /// <typeparam name="T">The type of the header.
- /// The given type must have a static TryParse method.</typeparam>
- /// <param name="name">The name of the header to retrieve.</param>
- /// <returns>The value of the header.</returns>
- public T? Get<T>(string name)
+ /// <summary>
+ /// Gets or sets the <c>Expires</c> header for an HTTP response.
+ /// </summary>
+ public DateTimeOffset? Expires
+ {
+ get
+ {
+ return Headers.GetDate(HeaderNames.Expires);
+ }
+ set
{
- return Headers.Get<T>(name);
+ Headers.SetDate(HeaderNames.Expires, value);
}
+ }
- /// <summary>
- /// Gets the values of header with <paramref name="name"/>.
- /// </summary>
- /// <remarks><typeparamref name="T"/> must contain a TryParseList method with the signature <c>public static bool TryParseList(IList&lt;string&gt;, out IList&lt;T&gt;)</c>.</remarks>
- /// <typeparam name="T">The type of the header.
- /// The given type must have a static TryParseList method.</typeparam>
- /// <param name="name">The name of the header to retrieve.</param>
- /// <returns>List of values of the header.</returns>
- public IList<T> GetList<T>(string name)
+ /// <summary>
+ /// Gets or sets the <c>Last-Modified</c> header for an HTTP response.
+ /// </summary>
+ public DateTimeOffset? LastModified
+ {
+ get
+ {
+ return Headers.GetDate(HeaderNames.LastModified);
+ }
+ set
{
- return Headers.GetList<T>(name);
+ Headers.SetDate(HeaderNames.LastModified, value);
}
+ }
- /// <summary>
- /// Sets the header value.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="value">The header value.</param>
- public void Set(string name, object? value)
+ /// <summary>
+ /// Gets or sets the <c>Location</c> header for an HTTP response.
+ /// </summary>
+ public Uri? Location
+ {
+ get
{
- if (name == null)
+ if (Uri.TryCreate(Headers.Location, UriKind.RelativeOrAbsolute, out var uri))
{
- throw new ArgumentNullException(nameof(name));
+ return uri;
}
-
- Headers.Set(name, value);
+ return null;
}
+ set
+ {
+ Headers.Set(HeaderNames.Location, value == null ? null : UriHelper.Encode(value));
+ }
+ }
- /// <summary>
- /// Sets the specified header and it's values.
- /// </summary>
- /// <typeparam name="T">The type of the value.</typeparam>
- /// <param name="name">The header name.</param>
- /// <param name="values">The sequence of header values.</param>
- public void SetList<T>(string name, IList<T>? values)
+ /// <summary>
+ /// Gets or sets the <c>Set-Cookie</c> header for an HTTP response.
+ /// </summary>
+ public IList<SetCookieHeaderValue> SetCookie
+ {
+ get
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ return Headers.SetCookie.GetList<SetCookieHeaderValue>();
+ }
+ set
+ {
+ Headers.SetList(HeaderNames.SetCookie, value);
+ }
+ }
+
+ /// <summary>
+ /// Gets the value of header with <paramref name="name"/>.
+ /// </summary>
+ /// <remarks><typeparamref name="T"/> must contain a TryParse method with the signature <c>public static bool TryParse(string, out T)</c>.</remarks>
+ /// <typeparam name="T">The type of the header.
+ /// The given type must have a static TryParse method.</typeparam>
+ /// <param name="name">The name of the header to retrieve.</param>
+ /// <returns>The value of the header.</returns>
+ public T? Get<T>(string name)
+ {
+ return Headers.Get<T>(name);
+ }
- Headers.SetList<T>(name, values);
+ /// <summary>
+ /// Gets the values of header with <paramref name="name"/>.
+ /// </summary>
+ /// <remarks><typeparamref name="T"/> must contain a TryParseList method with the signature <c>public static bool TryParseList(IList&lt;string&gt;, out IList&lt;T&gt;)</c>.</remarks>
+ /// <typeparam name="T">The type of the header.
+ /// The given type must have a static TryParseList method.</typeparam>
+ /// <param name="name">The name of the header to retrieve.</param>
+ /// <returns>List of values of the header.</returns>
+ public IList<T> GetList<T>(string name)
+ {
+ return Headers.GetList<T>(name);
+ }
+
+ /// <summary>
+ /// Sets the header value.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="value">The header value.</param>
+ public void Set(string name, object? value)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Appends the header name and value.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="value">The header value.</param>
- public void Append(string name, object value)
+ Headers.Set(name, value);
+ }
+
+ /// <summary>
+ /// Sets the specified header and it's values.
+ /// </summary>
+ /// <typeparam name="T">The type of the value.</typeparam>
+ /// <param name="name">The header name.</param>
+ /// <param name="values">The sequence of header values.</param>
+ public void SetList<T>(string name, IList<T>? values)
+ {
+ if (name == null)
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ throw new ArgumentNullException(nameof(name));
+ }
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
+ Headers.SetList<T>(name, values);
+ }
- Headers.Append(name, value.ToString());
+ /// <summary>
+ /// Appends the header name and value.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="value">The header value.</param>
+ public void Append(string name, object value)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// Appends the header name and it's values.
- /// </summary>
- /// <param name="name">The header name.</param>
- /// <param name="values">The header values.</param>
- public void AppendList<T>(string name, IList<T> values)
+ if (value == null)
{
- Headers.AppendList<T>(name, values);
+ throw new ArgumentNullException(nameof(value));
}
+
+ Headers.Append(name, value.ToString());
+ }
+
+ /// <summary>
+ /// Appends the header name and it's values.
+ /// </summary>
+ /// <param name="name">The header name.</param>
+ /// <param name="values">The header values.</param>
+ public void AppendList<T>(string name, IList<T> values)
+ {
+ Headers.AppendList<T>(name, values);
}
}
diff --git a/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs b/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs
index 65e16a7feb..fcdb50aa40 100644
--- a/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs
+++ b/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs
@@ -10,159 +10,158 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.FileProviders;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides extensions for HttpResponse exposing the SendFile extension.
+/// </summary>
+public static class SendFileResponseExtensions
{
+ private const int StreamCopyBufferSize = 64 * 1024;
+
/// <summary>
- /// Provides extensions for HttpResponse exposing the SendFile extension.
+ /// Sends the given file using the SendFile extension.
/// </summary>
- public static class SendFileResponseExtensions
+ /// <param name="response"></param>
+ /// <param name="file">The file.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task SendFileAsync(this HttpResponse response, IFileInfo file, CancellationToken cancellationToken = default)
{
- private const int StreamCopyBufferSize = 64 * 1024;
-
- /// <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>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task SendFileAsync(this HttpResponse response, IFileInfo file, CancellationToken cancellationToken = default)
+ if (response == null)
{
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
- if (file == null)
- {
- throw new ArgumentNullException(nameof(file));
- }
-
- return SendFileAsyncCore(response, file, 0, null, cancellationToken);
+ throw new ArgumentNullException(nameof(response));
}
-
- /// <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>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task SendFileAsync(this HttpResponse response, IFileInfo file, long offset, long? count, CancellationToken cancellationToken = default)
+ if (file == null)
{
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
- if (file == null)
- {
- throw new ArgumentNullException(nameof(file));
- }
-
- return SendFileAsyncCore(response, file, offset, count, cancellationToken);
+ throw new ArgumentNullException(nameof(file));
}
- /// <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>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task SendFileAsync(this HttpResponse response, string fileName, CancellationToken cancellationToken = default)
+ 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>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task SendFileAsync(this HttpResponse response, IFileInfo file, long offset, long? count, CancellationToken cancellationToken = default)
+ {
+ if (response == null)
{
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
+ throw new ArgumentNullException(nameof(response));
+ }
+ if (file == null)
+ {
+ throw new ArgumentNullException(nameof(file));
+ }
- if (fileName == null)
- {
- throw new ArgumentNullException(nameof(fileName));
- }
+ return SendFileAsyncCore(response, file, offset, count, cancellationToken);
+ }
- 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="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <returns></returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task SendFileAsync(this HttpResponse response, string fileName, CancellationToken cancellationToken = default)
+ {
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
}
- /// <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>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static Task SendFileAsync(this HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken = default)
+ if (fileName == null)
{
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
+ throw new ArgumentNullException(nameof(fileName));
+ }
- if (fileName == null)
- {
- throw new ArgumentNullException(nameof(fileName));
- }
+ return SendFileAsyncCore(response, fileName, 0, null, cancellationToken);
+ }
- return SendFileAsyncCore(response, fileName, 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="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>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static Task SendFileAsync(this HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken = default)
+ {
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
}
- private static async Task SendFileAsyncCore(HttpResponse response, IFileInfo file, long offset, long? count, CancellationToken cancellationToken)
+ if (fileName == null)
{
- if (string.IsNullOrEmpty(file.PhysicalPath))
- {
- CheckRange(offset, count, file.Length);
- await using var fileContent = file.CreateReadStream();
-
- var useRequestAborted = !cancellationToken.CanBeCanceled;
- var localCancel = useRequestAborted ? response.HttpContext.RequestAborted : cancellationToken;
-
- try
- {
- localCancel.ThrowIfCancellationRequested();
- if (offset > 0)
- {
- fileContent.Seek(offset, SeekOrigin.Begin);
- }
- await StreamCopyOperation.CopyToAsync(fileContent, response.Body, count, StreamCopyBufferSize, localCancel);
- }
- catch (OperationCanceledException) when (useRequestAborted) { }
- }
- else
- {
- await response.SendFileAsync(file.PhysicalPath, offset, count, cancellationToken);
- }
+ throw new ArgumentNullException(nameof(fileName));
}
- private static async Task SendFileAsyncCore(HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken = default)
+ 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);
+ await using var fileContent = file.CreateReadStream();
+
var useRequestAborted = !cancellationToken.CanBeCanceled;
var localCancel = useRequestAborted ? response.HttpContext.RequestAborted : cancellationToken;
- var sendFile = response.HttpContext.Features.Get<IHttpResponseBodyFeature>()!;
try
{
- await sendFile.SendFileAsync(fileName, offset, count, localCancel);
+ localCancel.ThrowIfCancellationRequested();
+ if (offset > 0)
+ {
+ fileContent.Seek(offset, SeekOrigin.Begin);
+ }
+ await StreamCopyOperation.CopyToAsync(fileContent, response.Body, count, StreamCopyBufferSize, localCancel);
}
catch (OperationCanceledException) when (useRequestAborted) { }
}
+ else
+ {
+ await response.SendFileAsync(file.PhysicalPath, offset, count, cancellationToken);
+ }
+ }
- private static void CheckRange(long offset, long? count, long fileLength)
+ private static async Task SendFileAsyncCore(HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken = default)
+ {
+ var useRequestAborted = !cancellationToken.CanBeCanceled;
+ var localCancel = useRequestAborted ? response.HttpContext.RequestAborted : cancellationToken;
+ var sendFile = response.HttpContext.Features.Get<IHttpResponseBodyFeature>()!;
+
+ try
{
- if (offset < 0 || offset > fileLength)
- {
- throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
- }
- if (count.HasValue &&
- (count.GetValueOrDefault() < 0 || count.GetValueOrDefault() > fileLength - offset))
- {
- throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
- }
+ await sendFile.SendFileAsync(fileName, offset, count, localCancel);
+ }
+ catch (OperationCanceledException) when (useRequestAborted) { }
+ }
+
+ 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.GetValueOrDefault() < 0 || count.GetValueOrDefault() > fileLength - offset))
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
}
}
}
diff --git a/src/Http/Http.Extensions/src/SessionExtensions.cs b/src/Http/Http.Extensions/src/SessionExtensions.cs
index f9ecc61a97..81c4d07d69 100644
--- a/src/Http/Http.Extensions/src/SessionExtensions.cs
+++ b/src/Http/Http.Extensions/src/SessionExtensions.cs
@@ -3,81 +3,80 @@
using System.Text;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods for <see cref="ISession"/>.
+/// </summary>
+public static class SessionExtensions
{
/// <summary>
- /// Extension methods for <see cref="ISession"/>.
+ /// Sets an int value in the <see cref="ISession"/>.
/// </summary>
- public static class SessionExtensions
+ /// <param name="session">The <see cref="ISession"/>.</param>
+ /// <param name="key">The key to assign.</param>
+ /// <param name="value">The value to assign.</param>
+ public static void SetInt32(this ISession session, string key, int value)
{
- /// <summary>
- /// Sets an int value in the <see cref="ISession"/>.
- /// </summary>
- /// <param name="session">The <see cref="ISession"/>.</param>
- /// <param name="key">The key to assign.</param>
- /// <param name="value">The value to assign.</param>
- public static void SetInt32(this ISession session, string key, int value)
+ var bytes = new byte[]
{
- var bytes = new byte[]
- {
(byte)(value >> 24),
(byte)(0xFF & (value >> 16)),
(byte)(0xFF & (value >> 8)),
(byte)(0xFF & value)
- };
- session.Set(key, bytes);
- }
+ };
+ session.Set(key, bytes);
+ }
- /// <summary>
- /// Gets an int value from <see cref="ISession"/>.
- /// </summary>
- /// <param name="session">The <see cref="ISession"/>.</param>
- /// <param name="key">The key to read.</param>
- public static int? GetInt32(this ISession session, string key)
+ /// <summary>
+ /// Gets an int value from <see cref="ISession"/>.
+ /// </summary>
+ /// <param name="session">The <see cref="ISession"/>.</param>
+ /// <param name="key">The key to read.</param>
+ public static int? GetInt32(this ISession session, string key)
+ {
+ var data = session.Get(key);
+ if (data == null || data.Length < 4)
{
- 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];
+ return null;
}
+ return data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ }
- /// <summary>
- /// Sets a <see cref="string"/> value in the <see cref="ISession"/>.
- /// </summary>
- /// <param name="session">The <see cref="ISession"/>.</param>
- /// <param name="key">The key to assign.</param>
- /// <param name="value">The value to assign.</param>
- public static void SetString(this ISession session, string key, string value)
- {
- session.Set(key, Encoding.UTF8.GetBytes(value));
- }
+ /// <summary>
+ /// Sets a <see cref="string"/> value in the <see cref="ISession"/>.
+ /// </summary>
+ /// <param name="session">The <see cref="ISession"/>.</param>
+ /// <param name="key">The key to assign.</param>
+ /// <param name="value">The value to assign.</param>
+ public static void SetString(this ISession session, string key, string value)
+ {
+ session.Set(key, Encoding.UTF8.GetBytes(value));
+ }
- /// <summary>
- /// Gets a string value from <see cref="ISession"/>.
- /// </summary>
- /// <param name="session">The <see cref="ISession"/>.</param>
- /// <param name="key">The key to read.</param>
- public static string? GetString(this ISession session, string key)
+ /// <summary>
+ /// Gets a string value from <see cref="ISession"/>.
+ /// </summary>
+ /// <param name="session">The <see cref="ISession"/>.</param>
+ /// <param name="key">The key to read.</param>
+ public static string? GetString(this ISession session, string key)
+ {
+ var data = session.Get(key);
+ if (data == null)
{
- var data = session.Get(key);
- if (data == null)
- {
- return null;
- }
- return Encoding.UTF8.GetString(data);
+ return null;
}
+ return Encoding.UTF8.GetString(data);
+ }
- /// <summary>
- /// Gets a byte-array value from <see cref="ISession"/>.
- /// </summary>
- /// <param name="session">The <see cref="ISession"/>.</param>
- /// <param name="key">The key to read.</param>
- public static byte[]? Get(this ISession session, string key)
- {
- session.TryGetValue(key, out var value);
- return value;
- }
+ /// <summary>
+ /// Gets a byte-array value from <see cref="ISession"/>.
+ /// </summary>
+ /// <param name="session">The <see cref="ISession"/>.</param>
+ /// <param name="key">The key to read.</param>
+ public static byte[]? Get(this ISession session, string key)
+ {
+ session.TryGetValue(key, out var value);
+ return value;
}
}
diff --git a/src/Http/Http.Extensions/src/StreamCopyOperation.cs b/src/Http/Http.Extensions/src/StreamCopyOperation.cs
index b681489471..402c96f8de 100644
--- a/src/Http/Http.Extensions/src/StreamCopyOperation.cs
+++ b/src/Http/Http.Extensions/src/StreamCopyOperation.cs
@@ -5,31 +5,30 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+// FYI: In most cases the source will be a FileStream and the destination will be to the network.
+/// <summary>
+/// Provides APIs to copy a range of bytes from a source <see cref="Stream"/> to a destination <see cref="Stream"/>.
+/// </summary>
+public static class StreamCopyOperation
{
- // FYI: In most cases the source will be a FileStream and the destination will be to the network.
- /// <summary>
- /// Provides APIs to copy a range of bytes from a source <see cref="Stream"/> to a destination <see cref="Stream"/>.
- /// </summary>
- public static class StreamCopyOperation
- {
- /// <summary>Asynchronously reads the given number of 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)
- => StreamCopyOperationInternal.CopyToAsync(source, destination, count, cancel);
+ /// <summary>Asynchronously reads the given number of 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)
+ => StreamCopyOperationInternal.CopyToAsync(source, destination, count, cancel);
- /// <summary>Asynchronously reads the given number of 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 Task CopyToAsync(Stream source, Stream destination, long? count, int bufferSize, CancellationToken cancel)
- => StreamCopyOperationInternal.CopyToAsync(source, destination, count, bufferSize, cancel);
- }
+ /// <summary>Asynchronously reads the given number of 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 Task CopyToAsync(Stream source, Stream destination, long? count, int bufferSize, CancellationToken cancel)
+ => StreamCopyOperationInternal.CopyToAsync(source, destination, count, bufferSize, cancel);
}
diff --git a/src/Http/Http.Extensions/src/TagsAttribute.cs b/src/Http/Http.Extensions/src/TagsAttribute.cs
index 891bf67084..f4d823dd14 100644
--- a/src/Http/Http.Extensions/src/TagsAttribute.cs
+++ b/src/Http/Http.Extensions/src/TagsAttribute.cs
@@ -4,31 +4,30 @@
using System;
using Microsoft.AspNetCore.Http.Metadata;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Specifies a collection of tags in <see cref="Endpoint.Metadata"/>.
+/// </summary>
+/// <remarks>
+/// The OpenAPI specification supports a tags classification to categorize operations
+/// into related groups. These tags are typically included in the generated specification
+/// and are typically used to group operations by tags in the UI.
+/// </remarks>
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+public sealed class TagsAttribute : Attribute, ITagsMetadata
{
/// <summary>
- /// Specifies a collection of tags in <see cref="Endpoint.Metadata"/>.
+ /// Initializes an instance of the <see cref="TagsAttribute"/>.
/// </summary>
- /// <remarks>
- /// The OpenAPI specification supports a tags classification to categorize operations
- /// into related groups. These tags are typically included in the generated specification
- /// and are typically used to group operations by tags in the UI.
- /// </remarks>
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
- public sealed class TagsAttribute : Attribute, ITagsMetadata
+ /// <param name="tags">The tags associated with the endpoint.</param>
+ public TagsAttribute(params string[] tags)
{
- /// <summary>
- /// Initializes an instance of the <see cref="TagsAttribute"/>.
- /// </summary>
- /// <param name="tags">The tags associated with the endpoint.</param>
- public TagsAttribute(params string[] tags)
- {
- Tags = new List<string>(tags);
- }
-
- /// <summary>
- /// Gets the collection of tags associated with the endpoint.
- /// </summary>
- public IReadOnlyList<string> Tags { get; }
+ Tags = new List<string>(tags);
}
+
+ /// <summary>
+ /// Gets the collection of tags associated with the endpoint.
+ /// </summary>
+ public IReadOnlyList<string> Tags { get; }
}
diff --git a/src/Http/Http.Extensions/src/UriHelper.cs b/src/Http/Http.Extensions/src/UriHelper.cs
index 16a921dfec..5a9bf46631 100644
--- a/src/Http/Http.Extensions/src/UriHelper.cs
+++ b/src/Http/Http.Extensions/src/UriHelper.cs
@@ -6,273 +6,272 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using System.Text;
-namespace Microsoft.AspNetCore.Http.Extensions
+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 char ForwardSlash = '/';
+ private const char Hash = '#';
+ private const char QuestionMark = '?';
+ private static readonly string SchemeDelimiter = Uri.SchemeDelimiter;
+ private static readonly SpanAction<char, (string scheme, string host, string pathBase, string path, string query, string fragment)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);
+
/// <summary>
- /// A helper class for constructing encoded Uris for use in headers and other Uris.
+ /// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
/// </summary>
- public static class UriHelper
+ /// <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>The combined URI components, properly encoded for use in HTTP headers.</returns>
+ public static string BuildRelative(
+ PathString pathBase = new PathString(),
+ PathString path = new PathString(),
+ QueryString query = new QueryString(),
+ FragmentString fragment = new FragmentString())
{
- private const char ForwardSlash = '/';
- private const char Hash = '#';
- private const char QuestionMark = '?';
- private static readonly string SchemeDelimiter = Uri.SchemeDelimiter;
- private static readonly SpanAction<char, (string scheme, string host, string pathBase, string path, string query, string fragment)> InitializeAbsoluteUriStringSpanAction = new(InitializeAbsoluteUriString);
+ 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.
- /// </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>The combined URI components, properly encoded for use in HTTP headers.</returns>
- public static string BuildRelative(
- PathString pathBase = new PathString(),
- PathString path = new PathString(),
- QueryString query = new QueryString(),
- FragmentString fragment = new FragmentString())
+ /// <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>The combined URI components, properly encoded for use in HTTP headers.</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)
{
- string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
- return combinePath + query.ToString() + fragment.ToString();
+ throw new ArgumentNullException(nameof(scheme));
}
- /// <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>The combined URI components, properly encoded for use in HTTP headers.</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 hostText = host.ToUriComponent();
- var pathBaseText = pathBase.ToUriComponent();
- var pathText = path.ToUriComponent();
- var queryText = query.ToUriComponent();
- var fragmentText = fragment.ToUriComponent();
+ var hostText = host.ToUriComponent();
+ var pathBaseText = pathBase.ToUriComponent();
+ var pathText = path.ToUriComponent();
+ var queryText = query.ToUriComponent();
+ var fragmentText = fragment.ToUriComponent();
- // PERF: Calculate string length to allocate correct buffer size for string.Create.
- var length =
- scheme.Length +
- Uri.SchemeDelimiter.Length +
- hostText.Length +
- pathBaseText.Length +
- pathText.Length +
- queryText.Length +
- fragmentText.Length;
+ // PERF: Calculate string length to allocate correct buffer size for string.Create.
+ var length =
+ scheme.Length +
+ Uri.SchemeDelimiter.Length +
+ hostText.Length +
+ pathBaseText.Length +
+ pathText.Length +
+ queryText.Length +
+ fragmentText.Length;
- if (string.IsNullOrEmpty(pathText))
- {
- if (string.IsNullOrEmpty(pathBaseText))
- {
- pathText = "/";
- length++;
- }
- }
- else if (pathBaseText.EndsWith('/'))
+ if (string.IsNullOrEmpty(pathText))
+ {
+ if (string.IsNullOrEmpty(pathBaseText))
{
- // If the path string has a trailing slash and the other string has a leading slash, we need
- // to trim one of them.
- // Just decrement the total length, for now.
- length--;
+ pathText = "/";
+ length++;
}
-
- return string.Create(length, (scheme, hostText, pathBaseText, pathText, queryText, fragmentText), InitializeAbsoluteUriStringSpanAction);
}
-
- /// <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)
+ else if (pathBaseText.EndsWith('/'))
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ // If the path string has a trailing slash and the other string has a leading slash, we need
+ // to trim one of them.
+ // Just decrement the total length, for now.
+ length--;
+ }
- path = new PathString();
- query = new QueryString();
- fragment = new FragmentString();
- var startIndex = uri.IndexOf(SchemeDelimiter, StringComparison.Ordinal);
+ return string.Create(length, (scheme, hostText, pathBaseText, pathText, queryText, fragmentText), InitializeAbsoluteUriStringSpanAction);
+ }
- if (startIndex < 0)
- {
- throw new FormatException("No scheme delimiter in uri.");
- }
+ /// <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));
+ }
- scheme = uri.Substring(0, startIndex);
+ path = new PathString();
+ query = new QueryString();
+ fragment = new FragmentString();
+ var startIndex = uri.IndexOf(SchemeDelimiter, StringComparison.Ordinal);
- // PERF: Calculate the end of the scheme for next IndexOf
- startIndex += SchemeDelimiter.Length;
+ if (startIndex < 0)
+ {
+ throw new FormatException("No scheme delimiter in uri.");
+ }
- int searchIndex;
- var limit = uri.Length;
- if ((searchIndex = uri.IndexOf(Hash, startIndex)) >= 0 && searchIndex < limit)
- {
- fragment = FragmentString.FromUriComponent(uri.Substring(searchIndex));
- limit = searchIndex;
- }
+ scheme = uri.Substring(0, startIndex);
- if ((searchIndex = uri.IndexOf(QuestionMark, startIndex)) >= 0 && searchIndex < limit)
- {
- query = QueryString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex));
- limit = searchIndex;
- }
+ // PERF: Calculate the end of the scheme for next IndexOf
+ startIndex += SchemeDelimiter.Length;
- if ((searchIndex = uri.IndexOf(ForwardSlash, startIndex)) >= 0 && searchIndex < limit)
- {
- path = PathString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex));
- limit = searchIndex;
- }
+ int searchIndex;
+ var limit = uri.Length;
+ if ((searchIndex = uri.IndexOf(Hash, startIndex)) >= 0 && searchIndex < limit)
+ {
+ fragment = FragmentString.FromUriComponent(uri.Substring(searchIndex));
+ limit = searchIndex;
+ }
- host = HostString.FromUriComponent(uri.Substring(startIndex, limit - startIndex));
+ if ((searchIndex = uri.IndexOf(QuestionMark, startIndex)) >= 0 && searchIndex < limit)
+ {
+ query = QueryString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex));
+ limit = searchIndex;
}
- /// <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>The encoded string version of <paramref name="uri"/>.</returns>
- public static string Encode(Uri uri)
+ if ((searchIndex = uri.IndexOf(ForwardSlash, startIndex)) >= 0 && searchIndex < limit)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ path = PathString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex));
+ limit = searchIndex;
+ }
- 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);
- }
+ 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>The encoded string version of <paramref name="uri"/>.</returns>
+ public static string Encode(Uri uri)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
}
- /// <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>The encoded string version of the URL from <paramref name="request"/>.</returns>
- public static string GetEncodedUrl(this HttpRequest request)
+ if (uri.IsAbsoluteUri)
{
- return BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString);
+ return BuildAbsolute(
+ scheme: uri.Scheme,
+ host: HostString.FromUriComponent(uri),
+ pathBase: PathString.FromUriComponent(uri),
+ query: QueryString.FromUriComponent(uri),
+ fragment: FragmentString.FromUriComponent(uri));
}
- /// <summary>
- /// Returns the relative URI.
- /// </summary>
- /// <param name="request">The request to assemble the uri pieces from.</param>
- /// <returns>The path and query off of <paramref name="request"/>.</returns>
- public static string GetEncodedPathAndQuery(this HttpRequest request)
+ else
{
- return BuildRelative(request.PathBase, request.Path, request.QueryString);
+ return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
}
+ }
- /// <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>The combined components of the request URL in a fully un-escaped form (except for the QueryString)
- /// suitable only for display.</returns>
- public static string GetDisplayUrl(this HttpRequest request)
- {
- var scheme = request.Scheme ?? string.Empty;
- var host = request.Host.Value ?? string.Empty;
- var pathBase = request.PathBase.Value ?? string.Empty;
- var path = request.Path.Value ?? string.Empty;
- var queryString = request.QueryString.Value ?? string.Empty;
+ /// <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>The encoded string version of the URL from <paramref name="request"/>.</returns>
+ public static string GetEncodedUrl(this HttpRequest request)
+ {
+ return BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString);
+ }
+ /// <summary>
+ /// Returns the relative URI.
+ /// </summary>
+ /// <param name="request">The request to assemble the uri pieces from.</param>
+ /// <returns>The path and query off of <paramref name="request"/>.</returns>
+ public static string GetEncodedPathAndQuery(this HttpRequest request)
+ {
+ return BuildRelative(request.PathBase, request.Path, request.QueryString);
+ }
- // PERF: Calculate string length to allocate correct buffer size for StringBuilder.
- var length = scheme.Length + SchemeDelimiter.Length + host.Length
- + pathBase.Length + path.Length + queryString.Length;
+ /// <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>The combined components of the request URL in a fully un-escaped form (except for the QueryString)
+ /// suitable only for display.</returns>
+ public static string GetDisplayUrl(this HttpRequest request)
+ {
+ var scheme = request.Scheme ?? string.Empty;
+ var host = request.Host.Value ?? string.Empty;
+ var pathBase = request.PathBase.Value ?? string.Empty;
+ var path = request.Path.Value ?? string.Empty;
+ var queryString = request.QueryString.Value ?? string.Empty;
- return new StringBuilder(length)
- .Append(scheme)
- .Append(SchemeDelimiter)
- .Append(host)
- .Append(pathBase)
- .Append(path)
- .Append(queryString)
- .ToString();
- }
+ // PERF: Calculate string length to allocate correct buffer size for StringBuilder.
+ var length = scheme.Length + SchemeDelimiter.Length + host.Length
+ + pathBase.Length + path.Length + queryString.Length;
- /// <summary>
- /// Copies the specified <paramref name="text"/> to the specified <paramref name="buffer"/> starting at the specified <paramref name="index"/>.
- /// </summary>
- /// <param name="buffer">The buffer to copy text to.</param>
- /// <param name="index">The buffer start index.</param>
- /// <param name="text">The text to copy.</param>
- /// <returns></returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int CopyTextToBuffer(Span<char> buffer, int index, ReadOnlySpan<char> text)
- {
- text.CopyTo(buffer.Slice(index, text.Length));
- return index + text.Length;
- }
+ return new StringBuilder(length)
+ .Append(scheme)
+ .Append(SchemeDelimiter)
+ .Append(host)
+ .Append(pathBase)
+ .Append(path)
+ .Append(queryString)
+ .ToString();
+ }
- /// <summary>
- /// Initializes the URI <see cref="string"/> for <see cref="BuildAbsolute(string, HostString, PathString, PathString, QueryString, FragmentString)"/>.
- /// </summary>
- /// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
- /// <param name="uriParts">The URI parts.</param>
- private static void InitializeAbsoluteUriString(Span<char> buffer, (string scheme, string host, string pathBase, string path, string query, string fragment) uriParts)
- {
- var index = 0;
+ /// <summary>
+ /// Copies the specified <paramref name="text"/> to the specified <paramref name="buffer"/> starting at the specified <paramref name="index"/>.
+ /// </summary>
+ /// <param name="buffer">The buffer to copy text to.</param>
+ /// <param name="index">The buffer start index.</param>
+ /// <param name="text">The text to copy.</param>
+ /// <returns></returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int CopyTextToBuffer(Span<char> buffer, int index, ReadOnlySpan<char> text)
+ {
+ text.CopyTo(buffer.Slice(index, text.Length));
+ return index + text.Length;
+ }
- var pathBaseSpan = uriParts.pathBase.AsSpan();
+ /// <summary>
+ /// Initializes the URI <see cref="string"/> for <see cref="BuildAbsolute(string, HostString, PathString, PathString, QueryString, FragmentString)"/>.
+ /// </summary>
+ /// <param name="buffer">The URI <see cref="string"/>'s <see cref="char"/> buffer.</param>
+ /// <param name="uriParts">The URI parts.</param>
+ private static void InitializeAbsoluteUriString(Span<char> buffer, (string scheme, string host, string pathBase, string path, string query, string fragment) uriParts)
+ {
+ var index = 0;
- if (uriParts.path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/')
- {
- // If the path string has a trailing slash and the other string has a leading slash, we need
- // to trim one of them.
- // Trim the last slahs from pathBase. The total length was decremented before the call to string.Create.
- pathBaseSpan = pathBaseSpan[..^1];
- }
+ var pathBaseSpan = uriParts.pathBase.AsSpan();
- index = CopyTextToBuffer(buffer, index, uriParts.scheme.AsSpan());
- index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan());
- index = CopyTextToBuffer(buffer, index, uriParts.host.AsSpan());
- index = CopyTextToBuffer(buffer, index, pathBaseSpan);
- index = CopyTextToBuffer(buffer, index, uriParts.path.AsSpan());
- index = CopyTextToBuffer(buffer, index, uriParts.query.AsSpan());
- _ = CopyTextToBuffer(buffer, index, uriParts.fragment.AsSpan());
+ if (uriParts.path.Length > 0 && pathBaseSpan.Length > 0 && pathBaseSpan[^1] == '/')
+ {
+ // If the path string has a trailing slash and the other string has a leading slash, we need
+ // to trim one of them.
+ // Trim the last slahs from pathBase. The total length was decremented before the call to string.Create.
+ pathBaseSpan = pathBaseSpan[..^1];
}
+
+ index = CopyTextToBuffer(buffer, index, uriParts.scheme.AsSpan());
+ index = CopyTextToBuffer(buffer, index, Uri.SchemeDelimiter.AsSpan());
+ index = CopyTextToBuffer(buffer, index, uriParts.host.AsSpan());
+ index = CopyTextToBuffer(buffer, index, pathBaseSpan);
+ index = CopyTextToBuffer(buffer, index, uriParts.path.AsSpan());
+ index = CopyTextToBuffer(buffer, index, uriParts.query.AsSpan());
+ _ = CopyTextToBuffer(buffer, index, uriParts.fragment.AsSpan());
}
}
diff --git a/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs b/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
index e413a49d21..7019546926 100644
--- a/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
+++ b/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
@@ -7,199 +7,198 @@ using System.Linq;
using Microsoft.Net.Http.Headers;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Headers
+namespace Microsoft.AspNetCore.Http.Headers;
+
+public class HeaderDictionaryTypeExtensionsTest
{
- public class HeaderDictionaryTypeExtensionsTest
+ [Fact]
+ public void GetT_KnownTypeWithValidValue_Success()
{
- [Fact]
- public void GetT_KnownTypeWithValidValue_Success()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers.ContentType = "text/plain";
+ var context = new DefaultHttpContext();
+ context.Request.Headers.ContentType = "text/plain";
- var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+ var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
- var expected = new MediaTypeHeaderValue("text/plain");
- Assert.Equal(expected, result);
- }
+ var expected = new MediaTypeHeaderValue("text/plain");
+ Assert.Equal(expected, result);
+ }
- [Fact]
- public void GetT_KnownTypeWithMissingValue_Null()
- {
- var context = new DefaultHttpContext();
+ [Fact]
+ public void GetT_KnownTypeWithMissingValue_Null()
+ {
+ var context = new DefaultHttpContext();
- var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+ var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
- Assert.Null(result);
- }
+ Assert.Null(result);
+ }
- [Fact]
- public void GetT_KnownTypeWithInvalidValue_Null()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers.ContentType = "invalid";
+ [Fact]
+ public void GetT_KnownTypeWithInvalidValue_Null()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Headers.ContentType = "invalid";
- var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+ var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
- Assert.Null(result);
- }
+ Assert.Null(result);
+ }
- [Fact]
- public void GetT_UnknownTypeWithTryParseAndValidValue_Success()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers["custom"] = "valid";
+ [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);
- }
+ 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";
+ [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);
- }
+ var result = context.Request.GetTypedHeaders().Get<TestHeaderValue>("custom");
+ Assert.Null(result);
+ }
- [Fact]
- public void GetT_UnknownTypeWithTryParseAndMissingValue_Null()
- {
- var context = new DefaultHttpContext();
+ [Fact]
+ public void GetT_UnknownTypeWithTryParseAndMissingValue_Null()
+ {
+ var context = new DefaultHttpContext();
- var result = context.Request.GetTypedHeaders().Get<TestHeaderValue>("custom");
- Assert.Null(result);
- }
+ 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";
+ [Fact]
+ public void GetT_UnknownTypeWithoutTryParse_Throws()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Headers["custom"] = "valid";
- Assert.Throws<NotSupportedException>(() => context.Request.GetTypedHeaders().Get<object>("custom"));
- }
+ Assert.Throws<NotSupportedException>(() => context.Request.GetTypedHeaders().Get<object>("custom"));
+ }
- [Fact]
- public void GetListT_KnownTypeWithValidValue_Success()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers.Accept = "text/plain; q=0.9, text/other, */*";
+ [Fact]
+ public void GetListT_KnownTypeWithValidValue_Success()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Headers.Accept = "text/plain; q=0.9, text/other, */*";
- var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+ var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
- var expected = new[] {
+ var expected = new[] {
new MediaTypeHeaderValue("text/plain", 0.9),
new MediaTypeHeaderValue("text/other"),
new MediaTypeHeaderValue("*/*"),
}.ToList();
- Assert.Equal(expected, result);
- }
+ Assert.Equal(expected, result);
+ }
- [Fact]
- public void GetListT_KnownTypeWithMissingValue_EmptyList()
- {
- var context = new DefaultHttpContext();
+ [Fact]
+ public void GetListT_KnownTypeWithMissingValue_EmptyList()
+ {
+ var context = new DefaultHttpContext();
- var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+ var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
- Assert.Empty(result);
- }
+ Assert.Empty(result);
+ }
- [Fact]
- public void GetListT_KnownTypeWithInvalidValue_EmptyList()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers.Accept = "invalid";
+ [Fact]
+ public void GetListT_KnownTypeWithInvalidValue_EmptyList()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Headers.Accept = "invalid";
- var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+ var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
- Assert.Empty(result);
- }
+ Assert.Empty(result);
+ }
- [Fact]
- public void GetListT_UnknownTypeWithTryParseListAndValidValue_Success()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers["custom"] = "valid";
+ [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);
- }
+ var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
+ Assert.NotNull(results);
+ Assert.Equal(new[] { new TestHeaderValue() }.ToList(), results);
+ }
- [Fact]
- public void GetListT_UnknownTypeWithTryParseListAndInvalidValue_EmptyList()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers["custom"] = "invalid";
+ [Fact]
+ public void GetListT_UnknownTypeWithTryParseListAndInvalidValue_EmptyList()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Headers["custom"] = "invalid";
- var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
- Assert.Empty(results);
- }
+ var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
+ Assert.Empty(results);
+ }
- [Fact]
- public void GetListT_UnknownTypeWithTryParseListAndMissingValue_EmptyList()
- {
- var context = new DefaultHttpContext();
+ [Fact]
+ public void GetListT_UnknownTypeWithTryParseListAndMissingValue_EmptyList()
+ {
+ var context = new DefaultHttpContext();
- var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
- Assert.Empty(results);
- }
+ var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
+ Assert.Empty(results);
+ }
- [Fact]
- public void GetListT_UnknownTypeWithoutTryParseList_Throws()
- {
- var context = new DefaultHttpContext();
- context.Request.Headers["custom"] = "valid";
+ [Fact]
+ public void GetListT_UnknownTypeWithoutTryParseList_Throws()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Headers["custom"] = "valid";
- Assert.Throws<NotSupportedException>(() => context.Request.GetTypedHeaders().GetList<object>("custom"));
- }
+ Assert.Throws<NotSupportedException>(() => context.Request.GetTypedHeaders().GetList<object>("custom"));
+ }
- public class TestHeaderValue
+ public class TestHeaderValue
+ {
+ public static bool TryParse(string value, out TestHeaderValue result)
{
- public static bool TryParse(string value, out TestHeaderValue result)
+ if (string.Equals("valid", value, StringComparison.Ordinal))
{
- if (string.Equals("valid", value, StringComparison.Ordinal))
- {
- result = new TestHeaderValue();
- return true;
- }
- result = null;
- return false;
+ result = new TestHeaderValue();
+ return true;
}
+ result = null;
+ return false;
+ }
- public static bool TryParseList(IList<string> values, out IList<TestHeaderValue> result)
+ public static bool TryParseList(IList<string> values, out IList<TestHeaderValue> result)
+ {
+ var results = new List<TestHeaderValue>();
+ foreach (var value in values)
{
- var results = new List<TestHeaderValue>();
- foreach (var value in values)
- {
- if (string.Equals("valid", value, StringComparison.Ordinal))
- {
- results.Add(new TestHeaderValue());
- }
- }
- if (results.Count > 0)
+ if (string.Equals("valid", value, StringComparison.Ordinal))
{
- result = results;
- return true;
+ results.Add(new TestHeaderValue());
}
- result = null;
- return false;
}
-
- public override bool Equals(object obj)
+ if (results.Count > 0)
{
- var other = obj as TestHeaderValue;
- return other != null;
+ result = results;
+ return true;
}
+ result = null;
+ return false;
+ }
- public override int GetHashCode()
- {
- return 0;
- }
+ public override bool Equals(object obj)
+ {
+ var other = obj as TestHeaderValue;
+ return other != null;
+ }
+
+ public override int GetHashCode()
+ {
+ return 0;
}
}
}
diff --git a/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs
index deb8d80456..021d0b7277 100644
--- a/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/HttpRequestExtensionsTests.cs
@@ -5,28 +5,27 @@ using Xunit;
#nullable enable
-namespace Microsoft.AspNetCore.Http.Extensions.Tests
+namespace Microsoft.AspNetCore.Http.Extensions.Tests;
+
+public class HttpRequestExtensionsTests
{
- public class HttpRequestExtensionsTests
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData("", false)]
+ [InlineData("application/xml", false)]
+ [InlineData("text/json", false)]
+ [InlineData("text/json; charset=utf-8", false)]
+ [InlineData("application/json", true)]
+ [InlineData("application/json; charset=utf-8", true)]
+ [InlineData("application/ld+json", true)]
+ [InlineData("APPLICATION/JSON", true)]
+ [InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
+ [InlineData("APPLICATION/LD+JSON", true)]
+ public void HasJsonContentType(string contentType, bool hasJsonContentType)
{
- [Theory]
- [InlineData(null, false)]
- [InlineData("", false)]
- [InlineData("application/xml", false)]
- [InlineData("text/json", false)]
- [InlineData("text/json; charset=utf-8", false)]
- [InlineData("application/json", true)]
- [InlineData("application/json; charset=utf-8", true)]
- [InlineData("application/ld+json", true)]
- [InlineData("APPLICATION/JSON", true)]
- [InlineData("APPLICATION/JSON; CHARSET=UTF-8", true)]
- [InlineData("APPLICATION/LD+JSON", true)]
- public void HasJsonContentType(string contentType, bool hasJsonContentType)
- {
- var request = new DefaultHttpContext().Request;
- request.ContentType = contentType;
+ var request = new DefaultHttpContext().Request;
+ request.ContentType = contentType;
- Assert.Equal(hasJsonContentType, request.HasJsonContentType());
- }
+ Assert.Equal(hasJsonContentType, request.HasJsonContentType());
}
}
diff --git a/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs
index af97e40412..c5041928f9 100644
--- a/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/HttpRequestJsonExtensionsTests.cs
@@ -12,203 +12,202 @@ using Xunit;
#nullable enable
-namespace Microsoft.AspNetCore.Http.Extensions.Tests
+namespace Microsoft.AspNetCore.Http.Extensions.Tests;
+
+public class HttpRequestJsonExtensionsTests
{
- public class HttpRequestJsonExtensionsTests
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_NonJsonContentType_ThrowError()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "text/json";
+
+ // Act
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Request.ReadFromJsonAsync<int>());
+
+ // Assert
+ var exceptedMessage = $"Unable to read the request as JSON because the request content type 'text/json' is not a known JSON content type.";
+ Assert.Equal(exceptedMessage, ex.Message);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_NoBodyContent_ThrowError()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+
+ // Act
+ var ex = await Assert.ThrowsAsync<JsonException>(async () => await context.Request.ReadFromJsonAsync<int>());
+
+ // Assert
+ var exceptedMessage = $"The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.";
+ Assert.Equal(exceptedMessage, ex.Message);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_ValidBodyContent_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("1"));
+
+ // Act
+ var result = await context.Request.ReadFromJsonAsync<int>();
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_WithOptions_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
+
+ var options = new JsonSerializerOptions();
+ options.AllowTrailingCommas = true;
+
+ // Act
+ var result = await context.Request.ReadFromJsonAsync<List<int>>(options);
+
+ // Assert
+ Assert.Collection(result,
+ i => Assert.Equal(1, i),
+ i => Assert.Equal(2, i));
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_Utf8Encoding_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json; charset=utf-8";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2]"));
+
+ // Act
+ var result = await context.Request.ReadFromJsonAsync<List<int>>();
+
+ // Assert
+ Assert.Collection(result,
+ i => Assert.Equal(1, i),
+ i => Assert.Equal(2, i));
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_Utf16Encoding_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json; charset=utf-16";
+ context.Request.Body = new MemoryStream(Encoding.Unicode.GetBytes(@"{""name"": ""激光這兩個字是甚麼意思""}"));
+
+ // Act
+ var result = await context.Request.ReadFromJsonAsync<Dictionary<string, string>>();
+
+ // Assert
+ Assert.Equal("激光這兩個字是甚麼意思", result!["name"]);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_WithCancellationToken_CancellationRaised()
{
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_NonJsonContentType_ThrowError()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "text/json";
-
- // Act
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Request.ReadFromJsonAsync<int>());
-
- // Assert
- var exceptedMessage = $"Unable to read the request as JSON because the request content type 'text/json' is not a known JSON content type.";
- Assert.Equal(exceptedMessage, ex.Message);
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_NoBodyContent_ThrowError()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json";
-
- // Act
- var ex = await Assert.ThrowsAsync<JsonException>(async () => await context.Request.ReadFromJsonAsync<int>());
-
- // Assert
- var exceptedMessage = $"The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.";
- Assert.Equal(exceptedMessage, ex.Message);
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_ValidBodyContent_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json";
- context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("1"));
-
- // Act
- var result = await context.Request.ReadFromJsonAsync<int>();
-
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_WithOptions_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json";
- context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
-
- var options = new JsonSerializerOptions();
- options.AllowTrailingCommas = true;
-
- // Act
- var result = await context.Request.ReadFromJsonAsync<List<int>>(options);
-
- // Assert
- Assert.Collection(result,
- i => Assert.Equal(1, i),
- i => Assert.Equal(2, i));
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_Utf8Encoding_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json; charset=utf-8";
- context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2]"));
-
- // Act
- var result = await context.Request.ReadFromJsonAsync<List<int>>();
-
- // Assert
- Assert.Collection(result,
- i => Assert.Equal(1, i),
- i => Assert.Equal(2, i));
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_Utf16Encoding_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json; charset=utf-16";
- context.Request.Body = new MemoryStream(Encoding.Unicode.GetBytes(@"{""name"": ""激光這兩個字是甚麼意思""}"));
-
- // Act
- var result = await context.Request.ReadFromJsonAsync<Dictionary<string, string>>();
-
- // Assert
- Assert.Equal("激光這兩個字是甚麼意思", result!["name"]);
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_WithCancellationToken_CancellationRaised()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application /json";
- context.Request.Body = new TestStream();
-
- var cts = new CancellationTokenSource();
-
- // Act
- var readTask = context.Request.ReadFromJsonAsync<List<int>>(cts.Token);
- Assert.False(readTask.IsCompleted);
-
- cts.Cancel();
-
- // Assert
- await Assert.ThrowsAsync<TaskCanceledException>(async () => await readTask);
- }
-
- [Fact]
- public async Task ReadFromJsonAsyncGeneric_InvalidEncoding_ThrowError()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json; charset=invalid";
-
- // Act
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Request.ReadFromJsonAsync<object>());
-
- // Assert
- Assert.Equal("Unable to read the request as JSON because the request content type charset 'invalid' is not a known encoding.", ex.Message);
- }
-
- [Fact]
- public async Task ReadFromJsonAsync_ValidBodyContent_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json";
- context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("1"));
-
- // Act
- var result = (int?)await context.Request.ReadFromJsonAsync(typeof(int));
-
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public async Task ReadFromJsonAsync_Utf16Encoding_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json; charset=utf-16";
- context.Request.Body = new MemoryStream(Encoding.Unicode.GetBytes(@"{""name"": ""激光這兩個字是甚麼意思""}"));
-
- // Act
- var result = (Dictionary<string, string>?)await context.Request.ReadFromJsonAsync(typeof(Dictionary<string, string>));
-
- // Assert
- Assert.Equal("激光這兩個字是甚麼意思", result!["name"]);
- }
-
- [Fact]
- public async Task ReadFromJsonAsync_InvalidEncoding_ThrowError()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json; charset=invalid";
-
- // Act
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Request.ReadFromJsonAsync(typeof(object)));
-
- // Assert
- Assert.Equal("Unable to read the request as JSON because the request content type charset 'invalid' is not a known encoding.", ex.Message);
- }
-
- [Fact]
- public async Task ReadFromJsonAsync_WithOptions_ReturnValue()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/json";
- context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
-
- var options = new JsonSerializerOptions();
- options.AllowTrailingCommas = true;
-
- // Act
- var result = (List<int>?)await context.Request.ReadFromJsonAsync(typeof(List<int>), options);
-
- // Assert
- Assert.Collection(result,
- i => Assert.Equal(1, i),
- i => Assert.Equal(2, i));
- }
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application /json";
+ context.Request.Body = new TestStream();
+
+ var cts = new CancellationTokenSource();
+
+ // Act
+ var readTask = context.Request.ReadFromJsonAsync<List<int>>(cts.Token);
+ Assert.False(readTask.IsCompleted);
+
+ cts.Cancel();
+
+ // Assert
+ await Assert.ThrowsAsync<TaskCanceledException>(async () => await readTask);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsyncGeneric_InvalidEncoding_ThrowError()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json; charset=invalid";
+
+ // Act
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Request.ReadFromJsonAsync<object>());
+
+ // Assert
+ Assert.Equal("Unable to read the request as JSON because the request content type charset 'invalid' is not a known encoding.", ex.Message);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsync_ValidBodyContent_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("1"));
+
+ // Act
+ var result = (int?)await context.Request.ReadFromJsonAsync(typeof(int));
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsync_Utf16Encoding_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json; charset=utf-16";
+ context.Request.Body = new MemoryStream(Encoding.Unicode.GetBytes(@"{""name"": ""激光這兩個字是甚麼意思""}"));
+
+ // Act
+ var result = (Dictionary<string, string>?)await context.Request.ReadFromJsonAsync(typeof(Dictionary<string, string>));
+
+ // Assert
+ Assert.Equal("激光這兩個字是甚麼意思", result!["name"]);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsync_InvalidEncoding_ThrowError()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json; charset=invalid";
+
+ // Act
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Request.ReadFromJsonAsync(typeof(object)));
+
+ // Assert
+ Assert.Equal("Unable to read the request as JSON because the request content type charset 'invalid' is not a known encoding.", ex.Message);
+ }
+
+ [Fact]
+ public async Task ReadFromJsonAsync_WithOptions_ReturnValue()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/json";
+ context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("[1,2,]"));
+
+ var options = new JsonSerializerOptions();
+ options.AllowTrailingCommas = true;
+
+ // Act
+ var result = (List<int>?)await context.Request.ReadFromJsonAsync(typeof(List<int>), options);
+
+ // Assert
+ Assert.Collection(result,
+ i => Assert.Equal(1, i),
+ i => Assert.Equal(2, i));
}
}
diff --git a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
index 5addfa413b..148ef1f846 100644
--- a/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/HttpResponseJsonExtensionsTests.cs
@@ -13,486 +13,485 @@ using Xunit;
#nullable enable
-namespace Microsoft.AspNetCore.Http.Extensions.Tests
+namespace Microsoft.AspNetCore.Http.Extensions.Tests;
+
+public class HttpResponseJsonExtensionsTests
{
- public class HttpResponseJsonExtensionsTests
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_SimpleValue_JsonResponse()
{
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_SimpleValue_JsonResponse()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- await context.Response.WriteAsJsonAsync(1);
+ // Act
+ await context.Response.WriteAsJsonAsync(1);
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
- var data = body.ToArray();
- Assert.Collection(data, b => Assert.Equal((byte)'1', b));
- }
+ var data = body.ToArray();
+ Assert.Collection(data, b => Assert.Equal((byte)'1', b));
+ }
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_NullValue_JsonResponse()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_NullValue_JsonResponse()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- await context.Response.WriteAsJsonAsync<Uri?>(value: null);
+ // Act
+ await context.Response.WriteAsJsonAsync<Uri?>(value: null);
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- var data = Encoding.UTF8.GetString(body.ToArray());
- Assert.Equal("null", data);
- }
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal("null", data);
+ }
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_WithOptions_JsonResponse()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_WithOptions_JsonResponse()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- var options = new JsonSerializerOptions();
- options.Converters.Add(new IntegerConverter());
- await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, options);
+ // Act
+ var options = new JsonSerializerOptions();
+ options.Converters.Add(new IntegerConverter());
+ await context.Response.WriteAsJsonAsync(new int[] { 1, 2, 3 }, options);
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- var data = Encoding.UTF8.GetString(body.ToArray());
- Assert.Equal("[false,true,false]", data);
- }
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal("[false,true,false]", data);
+ }
- private class IntegerConverter : JsonConverter<int>
+ private class IntegerConverter : JsonConverter<int>
+ {
+ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
- {
- writer.WriteBooleanValue(value % 2 == 0);
- }
+ throw new NotImplementedException();
}
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_CustomStatusCode_StatusCodeUnchanged()
+ public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
-
- // Act
- context.Response.StatusCode = StatusCodes.Status418ImATeapot;
- await context.Response.WriteAsJsonAsync(1);
-
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode);
+ writer.WriteBooleanValue(value % 2 == 0);
}
+ }
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_WithContentType_JsonResponseWithCustomContentType()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_CustomStatusCode_StatusCodeUnchanged()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+
+ // Act
+ context.Response.StatusCode = StatusCodes.Status418ImATeapot;
+ await context.Response.WriteAsJsonAsync(1);
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode);
+ }
- // Act
- await context.Response.WriteAsJsonAsync(1, options: null, contentType: "application/custom-type");
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_WithContentType_JsonResponseWithCustomContentType()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Assert
- Assert.Equal("application/custom-type", context.Response.ContentType);
- }
+ // Act
+ await context.Response.WriteAsJsonAsync(1, options: null, contentType: "application/custom-type");
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_WithCancellationToken_CancellationRaised()
- {
- // Arrange
- var context = new DefaultHttpContext();
- context.Response.Body = new TestStream();
+ // Assert
+ Assert.Equal("application/custom-type", context.Response.ContentType);
+ }
- var cts = new CancellationTokenSource();
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_WithCancellationToken_CancellationRaised()
+ {
+ // Arrange
+ var context = new DefaultHttpContext();
+ context.Response.Body = new TestStream();
- // Act
- var writeTask = context.Response.WriteAsJsonAsync(1, cts.Token);
- Assert.False(writeTask.IsCompleted);
+ var cts = new CancellationTokenSource();
- cts.Cancel();
+ // Act
+ var writeTask = context.Response.WriteAsJsonAsync(1, cts.Token);
+ Assert.False(writeTask.IsCompleted);
- // Assert
- await Assert.ThrowsAsync<TaskCanceledException>(async () => await writeTask);
- }
+ cts.Cancel();
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_ObjectWithStrings_CamcelCaseAndNotEscaped()
+ // Assert
+ await Assert.ThrowsAsync<TaskCanceledException>(async () => await writeTask);
+ }
+
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_ObjectWithStrings_CamcelCaseAndNotEscaped()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+ var value = new TestObject
{
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
- var value = new TestObject
- {
- StringProperty = "激光這兩個字是甚麼意思"
- };
+ StringProperty = "激光這兩個字是甚麼意思"
+ };
- // Act
- await context.Response.WriteAsJsonAsync(value);
+ // Act
+ await context.Response.WriteAsJsonAsync(value);
- // Assert
- var data = Encoding.UTF8.GetString(body.ToArray());
- Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data);
- }
+ // Assert
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data);
+ }
- [Fact]
- public async Task WriteAsJsonAsync_SimpleValue_JsonResponse()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsync_SimpleValue_JsonResponse()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- await context.Response.WriteAsJsonAsync(1, typeof(int));
+ // Act
+ await context.Response.WriteAsJsonAsync(1, typeof(int));
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
- var data = body.ToArray();
- Assert.Collection(data, b => Assert.Equal((byte)'1', b));
- }
+ var data = body.ToArray();
+ Assert.Collection(data, b => Assert.Equal((byte)'1', b));
+ }
- [Fact]
- public async Task WriteAsJsonAsync_NullValue_JsonResponse()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsync_NullValue_JsonResponse()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- await context.Response.WriteAsJsonAsync(value: null, typeof(int?));
+ // Act
+ await context.Response.WriteAsJsonAsync(value: null, typeof(int?));
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- var data = Encoding.UTF8.GetString(body.ToArray());
- Assert.Equal("null", data);
- }
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal("null", data);
+ }
- [Fact]
- public async Task WriteAsJsonAsync_NullType_ThrowsArgumentNullException()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsync_NullType_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act & Assert
- await Assert.ThrowsAsync<ArgumentNullException>(async () => await context.Response.WriteAsJsonAsync(value: null, type: null!));
- }
+ // Act & Assert
+ await Assert.ThrowsAsync<ArgumentNullException>(async () => await context.Response.WriteAsJsonAsync(value: null, type: null!));
+ }
- [Fact]
- public async Task WriteAsJsonAsync_NullResponse_ThrowsArgumentNullException()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsync_NullResponse_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act & Assert
- await Assert.ThrowsAsync<ArgumentNullException>(async () => await HttpResponseJsonExtensions.WriteAsJsonAsync(response: null!, value: null, typeof(int?)));
- }
+ // Act & Assert
+ await Assert.ThrowsAsync<ArgumentNullException>(async () => await HttpResponseJsonExtensions.WriteAsJsonAsync(response: null!, value: null, typeof(int?)));
+ }
- [Fact]
- public async Task WriteAsJsonAsync_ObjectWithStrings_CamcelCaseAndNotEscaped()
+ [Fact]
+ public async Task WriteAsJsonAsync_ObjectWithStrings_CamcelCaseAndNotEscaped()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+ var value = new TestObject
{
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
- var value = new TestObject
- {
- StringProperty = "激光這兩個字是甚麼意思"
- };
+ StringProperty = "激光這兩個字是甚麼意思"
+ };
- // Act
- await context.Response.WriteAsJsonAsync(value, typeof(TestObject));
+ // Act
+ await context.Response.WriteAsJsonAsync(value, typeof(TestObject));
- // Assert
- var data = Encoding.UTF8.GetString(body.ToArray());
- Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data);
- }
+ // Assert
+ var data = Encoding.UTF8.GetString(body.ToArray());
+ Assert.Equal(@"{""stringProperty"":""激光這兩個字是甚麼意思""}", data);
+ }
- [Fact]
- public async Task WriteAsJsonAsync_CustomStatusCode_StatusCodeUnchanged()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
-
- // Act
- context.Response.StatusCode = StatusCodes.Status418ImATeapot;
- await context.Response.WriteAsJsonAsync(1, typeof(int));
-
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode);
- }
+ [Fact]
+ public async Task WriteAsJsonAsync_CustomStatusCode_StatusCodeUnchanged()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+
+ // Act
+ context.Response.StatusCode = StatusCodes.Status418ImATeapot;
+ await context.Response.WriteAsJsonAsync(1, typeof(int));
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status418ImATeapot, context.Response.StatusCode);
+ }
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- await context.Response.WriteAsJsonAsync(AsyncEnumerable());
+ // Act
+ await context.Response.WriteAsJsonAsync(AsyncEnumerable());
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
- Assert.Equal("[1,2]", Encoding.UTF8.GetString(body.ToArray()));
+ Assert.Equal("[1,2]", Encoding.UTF8.GetString(body.ToArray()));
- async IAsyncEnumerable<int> AsyncEnumerable()
- {
- await Task.Yield();
- yield return 1;
- yield return 2;
- }
+ async IAsyncEnumerable<int> AsyncEnumerable()
+ {
+ await Task.Yield();
+ yield return 1;
+ yield return 2;
}
+ }
- [Fact]
- public async Task WriteAsJsonAsync_AsyncEnumerable()
- {
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
+ [Fact]
+ public async Task WriteAsJsonAsync_AsyncEnumerable()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
- // Act
- await context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>));
+ // Act
+ await context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>));
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
- Assert.Equal("[1,2]", Encoding.UTF8.GetString(body.ToArray()));
+ Assert.Equal("[1,2]", Encoding.UTF8.GetString(body.ToArray()));
- async IAsyncEnumerable<int> AsyncEnumerable()
- {
- await Task.Yield();
- yield return 1;
- yield return 2;
- }
+ async IAsyncEnumerable<int> AsyncEnumerable()
+ {
+ await Task.Yield();
+ yield return 1;
+ yield return 2;
}
+ }
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable_ClosedConnecton()
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_AsyncEnumerable_ClosedConnecton()
+ {
+ // Arrange
+ var cts = new CancellationTokenSource();
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+ context.RequestAborted = cts.Token;
+ var iterated = false;
+
+ // Act
+ await context.Response.WriteAsJsonAsync(AsyncEnumerable());
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+
+ // System.Text.Json might write the '[' before cancellation is observed
+ Assert.InRange(body.ToArray().Length, 0, 1);
+ Assert.False(iterated);
+
+ async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- // Arrange
- var cts = new CancellationTokenSource();
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
- context.RequestAborted = cts.Token;
- var iterated = false;
-
- // Act
- await context.Response.WriteAsJsonAsync(AsyncEnumerable());
-
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
-
- // System.Text.Json might write the '[' before cancellation is observed
- Assert.InRange(body.ToArray().Length, 0, 1);
- Assert.False(iterated);
-
- async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ await Task.Yield();
+ cts.Cancel();
+ for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
{
- await Task.Yield();
- cts.Cancel();
- for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
- {
- iterated = true;
- yield return i;
- }
+ iterated = true;
+ yield return i;
}
}
+ }
- [Fact]
- public async Task WriteAsJsonAsync_AsyncEnumerable_ClosedConnecton()
+ [Fact]
+ public async Task WriteAsJsonAsync_AsyncEnumerable_ClosedConnecton()
+ {
+ // Arrange
+ var cts = new CancellationTokenSource();
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+ context.RequestAborted = cts.Token;
+ var iterated = false;
+
+ // Act
+ await context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>));
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+
+ // System.Text.Json might write the '[' before cancellation is observed
+ Assert.InRange(body.ToArray().Length, 0, 1);
+ Assert.False(iterated);
+
+ async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- // Arrange
- var cts = new CancellationTokenSource();
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
- context.RequestAborted = cts.Token;
- var iterated = false;
-
- // Act
- await context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>));
-
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
-
- // System.Text.Json might write the '[' before cancellation is observed
- Assert.InRange(body.ToArray().Length, 0, 1);
- Assert.False(iterated);
-
- async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ await Task.Yield();
+ cts.Cancel();
+ for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
{
- await Task.Yield();
- cts.Cancel();
- for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
- {
- iterated = true;
- yield return i;
- }
+ iterated = true;
+ yield return i;
}
}
+ }
- [Fact]
- public async Task WriteAsJsonAsync_AsyncEnumerable_UserPassedTokenThrows()
+ [Fact]
+ public async Task WriteAsJsonAsync_AsyncEnumerable_UserPassedTokenThrows()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+ context.RequestAborted = new CancellationToken(canceled: true);
+ var cts = new CancellationTokenSource();
+ var iterated = false;
+
+ // Act
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>), cts.Token));
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+
+ // System.Text.Json might write the '[' before cancellation is observed
+ Assert.InRange(body.ToArray().Length, 0, 1);
+ Assert.False(iterated);
+
+ async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
- context.RequestAborted = new CancellationToken(canceled: true);
- var cts = new CancellationTokenSource();
- var iterated = false;
-
- // Act
- await Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Response.WriteAsJsonAsync(AsyncEnumerable(), typeof(IAsyncEnumerable<int>), cts.Token));
-
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
-
- // System.Text.Json might write the '[' before cancellation is observed
- Assert.InRange(body.ToArray().Length, 0, 1);
- Assert.False(iterated);
-
- async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ await Task.Yield();
+ cts.Cancel();
+ for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
{
- await Task.Yield();
- cts.Cancel();
- for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
- {
- iterated = true;
- yield return i;
- }
+ iterated = true;
+ yield return i;
}
}
+ }
- [Fact]
- public async Task WriteAsJsonAsyncGeneric_AsyncEnumerableG_UserPassedTokenThrows()
+ [Fact]
+ public async Task WriteAsJsonAsyncGeneric_AsyncEnumerableG_UserPassedTokenThrows()
+ {
+ // Arrange
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.Response.Body = body;
+ context.RequestAborted = new CancellationToken(canceled: true);
+ var cts = new CancellationTokenSource();
+ var iterated = false;
+
+ // Act
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Response.WriteAsJsonAsync(AsyncEnumerable(), cts.Token));
+
+ // Assert
+ Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
+ Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
+
+ // System.Text.Json might write the '[' before cancellation is observed
+ Assert.InRange(body.ToArray().Length, 0, 1);
+ Assert.False(iterated);
+
+ async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
{
- // Arrange
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.Response.Body = body;
- context.RequestAborted = new CancellationToken(canceled: true);
- var cts = new CancellationTokenSource();
- var iterated = false;
-
- // Act
- await Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Response.WriteAsJsonAsync(AsyncEnumerable(), cts.Token));
-
- // Assert
- Assert.Equal(JsonConstants.JsonContentTypeWithCharset, context.Response.ContentType);
- Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
-
- // System.Text.Json might write the '[' before cancellation is observed
- Assert.InRange(body.ToArray().Length, 0, 1);
- Assert.False(iterated);
-
- async IAsyncEnumerable<int> AsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ await Task.Yield();
+ cts.Cancel();
+ for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
{
- await Task.Yield();
- cts.Cancel();
- for (var i = 0; i < 100 && !cancellationToken.IsCancellationRequested; i++)
- {
- iterated = true;
- yield return i;
- }
+ iterated = true;
+ yield return i;
}
}
+ }
+
+ public class TestObject
+ {
+ public string? StringProperty { get; set; }
+ }
- public class TestObject
+ private class TestStream : Stream
+ {
+ public override bool CanRead { get; }
+ public override bool CanSeek { get; }
+ public override bool CanWrite { get; }
+ public override long Length { get; }
+ public override long Position { get; set; }
+
+ public override void Flush()
{
- public string? StringProperty { get; set; }
+ throw new NotImplementedException();
}
- private class TestStream : Stream
+ public override int Read(byte[] buffer, int offset, int count)
{
- public override bool CanRead { get; }
- public override bool CanSeek { get; }
- public override bool CanWrite { get; }
- public override long Length { get; }
- public override long Position { get; set; }
-
- public override void Flush()
- {
- throw new NotImplementedException();
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
+ }
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotImplementedException();
- }
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
- public override void SetLength(long value)
- {
- throw new NotImplementedException();
- }
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
- public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
- {
- var tcs = new TaskCompletionSource<int>();
- cancellationToken.Register(s => ((TaskCompletionSource<int>)s!).SetCanceled(), tcs);
- return new ValueTask<int>(tcs.Task);
- }
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ var tcs = new TaskCompletionSource<int>();
+ cancellationToken.Register(s => ((TaskCompletionSource<int>)s!).SetCanceled(), tcs);
+ return new ValueTask<int>(tcs.Task);
+ }
- public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
- {
- var tcs = new TaskCompletionSource<int>();
- cancellationToken.Register(s => ((TaskCompletionSource<int>)s!).SetCanceled(), tcs);
- return new ValueTask(tcs.Task);
- }
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ var tcs = new TaskCompletionSource<int>();
+ cancellationToken.Register(s => ((TaskCompletionSource<int>)s!).SetCanceled(), tcs);
+ return new ValueTask(tcs.Task);
}
}
}
diff --git a/src/Http/Http.Extensions/test/HttpValidationProblemDetailsJsonConverterTest.cs b/src/Http/Http.Extensions/test/HttpValidationProblemDetailsJsonConverterTest.cs
index 3e987c3b2c..1c98c56fc5 100644
--- a/src/Http/Http.Extensions/test/HttpValidationProblemDetailsJsonConverterTest.cs
+++ b/src/Http/Http.Extensions/test/HttpValidationProblemDetailsJsonConverterTest.cs
@@ -7,134 +7,133 @@ using System.Text.Json;
using Microsoft.AspNetCore.Http.Json;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+public class HttpValidationProblemDetailsJsonConverterTest
{
- public class HttpValidationProblemDetailsJsonConverterTest
- {
- private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().SerializerOptions;
+ private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().SerializerOptions;
- [Fact]
- public void Read_Works()
- {
- // Arrange
- var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
- var title = "Not found";
- var status = 404;
- var detail = "Product not found";
- var instance = "http://example.com/products/14";
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"," +
- "\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
- var converter = new HttpValidationProblemDetailsJsonConverter();
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- reader.Read();
+ [Fact]
+ public void Read_Works()
+ {
+ // Arrange
+ var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
+ var title = "Not found";
+ var status = 404;
+ var detail = "Product not found";
+ var instance = "http://example.com/products/14";
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"," +
+ "\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
+ var converter = new HttpValidationProblemDetailsJsonConverter();
+ var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
+ reader.Read();
- // Act
- var problemDetails = converter.Read(ref reader, typeof(HttpValidationProblemDetails), JsonSerializerOptions);
+ // Act
+ var problemDetails = converter.Read(ref reader, typeof(HttpValidationProblemDetails), JsonSerializerOptions);
- Assert.Equal(type, problemDetails.Type);
- Assert.Equal(title, problemDetails.Title);
- Assert.Equal(status, problemDetails.Status);
- Assert.Equal(instance, problemDetails.Instance);
- Assert.Equal(detail, problemDetails.Detail);
- Assert.Collection(
- problemDetails.Extensions,
- kvp =>
- {
- Assert.Equal("traceId", kvp.Key);
- Assert.Equal(traceId, kvp.Value.ToString());
- });
- Assert.Collection(
- problemDetails.Errors.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("key0", kvp.Key);
- Assert.Equal(new[] { "error0" }, kvp.Value);
- },
- kvp =>
- {
- Assert.Equal("key1", kvp.Key);
- Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
- });
- }
+ Assert.Equal(type, problemDetails.Type);
+ Assert.Equal(title, problemDetails.Title);
+ Assert.Equal(status, problemDetails.Status);
+ Assert.Equal(instance, problemDetails.Instance);
+ Assert.Equal(detail, problemDetails.Detail);
+ Assert.Collection(
+ problemDetails.Extensions,
+ kvp =>
+ {
+ Assert.Equal("traceId", kvp.Key);
+ Assert.Equal(traceId, kvp.Value.ToString());
+ });
+ Assert.Collection(
+ problemDetails.Errors.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("key0", kvp.Key);
+ Assert.Equal(new[] { "error0" }, kvp.Value);
+ },
+ kvp =>
+ {
+ Assert.Equal("key1", kvp.Key);
+ Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
+ });
+ }
- [Fact]
- public void Read_WithSomeMissingValues_Works()
- {
- // Arrange
- var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
- var title = "Not found";
- var status = 404;
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"," +
- "\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
- var converter = new HttpValidationProblemDetailsJsonConverter();
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- reader.Read();
+ [Fact]
+ public void Read_WithSomeMissingValues_Works()
+ {
+ // Arrange
+ var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
+ var title = "Not found";
+ var status = 404;
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"," +
+ "\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
+ var converter = new HttpValidationProblemDetailsJsonConverter();
+ var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
+ reader.Read();
- // Act
- var problemDetails = converter.Read(ref reader, typeof(HttpValidationProblemDetails), JsonSerializerOptions);
+ // Act
+ var problemDetails = converter.Read(ref reader, typeof(HttpValidationProblemDetails), JsonSerializerOptions);
- Assert.Equal(type, problemDetails.Type);
- Assert.Equal(title, problemDetails.Title);
- Assert.Equal(status, problemDetails.Status);
- Assert.Collection(
- problemDetails.Extensions,
- kvp =>
- {
- Assert.Equal("traceId", kvp.Key);
- Assert.Equal(traceId, kvp.Value.ToString());
- });
- Assert.Collection(
- problemDetails.Errors.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("key0", kvp.Key);
- Assert.Equal(new[] { "error0" }, kvp.Value);
- },
- kvp =>
- {
- Assert.Equal("key1", kvp.Key);
- Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
- });
- }
+ Assert.Equal(type, problemDetails.Type);
+ Assert.Equal(title, problemDetails.Title);
+ Assert.Equal(status, problemDetails.Status);
+ Assert.Collection(
+ problemDetails.Extensions,
+ kvp =>
+ {
+ Assert.Equal("traceId", kvp.Key);
+ Assert.Equal(traceId, kvp.Value.ToString());
+ });
+ Assert.Collection(
+ problemDetails.Errors.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("key0", kvp.Key);
+ Assert.Equal(new[] { "error0" }, kvp.Value);
+ },
+ kvp =>
+ {
+ Assert.Equal("key1", kvp.Key);
+ Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
+ });
+ }
- [Fact]
- public void ReadUsingJsonSerializerWorks()
- {
- // Arrange
- var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
- var title = "Not found";
- var status = 404;
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"," +
- "\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
+ [Fact]
+ public void ReadUsingJsonSerializerWorks()
+ {
+ // Arrange
+ var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
+ var title = "Not found";
+ var status = 404;
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"," +
+ "\"errors\":{\"key0\":[\"error0\"],\"key1\":[\"error1\",\"error2\"]}}";
- // Act
- var problemDetails = JsonSerializer.Deserialize<HttpValidationProblemDetails>(json, JsonSerializerOptions);
+ // Act
+ var problemDetails = JsonSerializer.Deserialize<HttpValidationProblemDetails>(json, JsonSerializerOptions);
- Assert.Equal(type, problemDetails.Type);
- Assert.Equal(title, problemDetails.Title);
- Assert.Equal(status, problemDetails.Status);
- Assert.Collection(
- problemDetails.Extensions,
- kvp =>
- {
- Assert.Equal("traceId", kvp.Key);
- Assert.Equal(traceId, kvp.Value.ToString());
- });
- Assert.Collection(
- problemDetails.Errors.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("key0", kvp.Key);
- Assert.Equal(new[] { "error0" }, kvp.Value);
- },
- kvp =>
- {
- Assert.Equal("key1", kvp.Key);
- Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
- });
- }
+ Assert.Equal(type, problemDetails.Type);
+ Assert.Equal(title, problemDetails.Title);
+ Assert.Equal(status, problemDetails.Status);
+ Assert.Collection(
+ problemDetails.Extensions,
+ kvp =>
+ {
+ Assert.Equal("traceId", kvp.Key);
+ Assert.Equal(traceId, kvp.Value.ToString());
+ });
+ Assert.Collection(
+ problemDetails.Errors.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("key0", kvp.Key);
+ Assert.Equal(new[] { "error0" }, kvp.Value);
+ },
+ kvp =>
+ {
+ Assert.Equal("key1", kvp.Key);
+ Assert.Equal(new[] { "error1", "error2" }, kvp.Value);
+ });
}
}
diff --git a/src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs b/src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs
index e64496b207..dad9ad3375 100644
--- a/src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs
+++ b/src/Http/Http.Extensions/test/ParameterBindingMethodCacheTests.cs
@@ -8,122 +8,122 @@ using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Extensions.Internal;
-namespace Microsoft.AspNetCore.Http.Extensions.Tests
+namespace Microsoft.AspNetCore.Http.Extensions.Tests;
+
+public class ParameterBindingMethodCacheTests
{
- public class ParameterBindingMethodCacheTests
- {
- [Theory]
- [InlineData(typeof(int))]
- [InlineData(typeof(double))]
- [InlineData(typeof(float))]
- [InlineData(typeof(Half))]
- [InlineData(typeof(short))]
- [InlineData(typeof(long))]
- [InlineData(typeof(IntPtr))]
- [InlineData(typeof(sbyte))]
- [InlineData(typeof(ushort))]
- [InlineData(typeof(uint))]
- [InlineData(typeof(ulong))]
- public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCulture(Type type)
- {
- var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
+ [Theory]
+ [InlineData(typeof(int))]
+ [InlineData(typeof(double))]
+ [InlineData(typeof(float))]
+ [InlineData(typeof(Half))]
+ [InlineData(typeof(short))]
+ [InlineData(typeof(long))]
+ [InlineData(typeof(IntPtr))]
+ [InlineData(typeof(sbyte))]
+ [InlineData(typeof(ushort))]
+ [InlineData(typeof(uint))]
+ [InlineData(typeof(ulong))]
+ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCulture(Type type)
+ {
+ var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
- Assert.NotNull(methodFound);
+ Assert.NotNull(methodFound);
- var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
- Assert.NotNull(call);
- var parameters = call!.Method.GetParameters();
+ var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
+ Assert.NotNull(call);
+ var parameters = call!.Method.GetParameters();
- Assert.Equal(4, parameters.Length);
- Assert.Equal(typeof(string), parameters[0].ParameterType);
- Assert.Equal(typeof(NumberStyles), parameters[1].ParameterType);
- Assert.Equal(typeof(IFormatProvider), parameters[2].ParameterType);
- Assert.True(parameters[3].IsOut);
- }
+ Assert.Equal(4, parameters.Length);
+ Assert.Equal(typeof(string), parameters[0].ParameterType);
+ Assert.Equal(typeof(NumberStyles), parameters[1].ParameterType);
+ Assert.Equal(typeof(IFormatProvider), parameters[2].ParameterType);
+ Assert.True(parameters[3].IsOut);
+ }
- [Theory]
- [InlineData(typeof(DateTime))]
- [InlineData(typeof(DateOnly))]
- [InlineData(typeof(DateTimeOffset))]
- [InlineData(typeof(TimeOnly))]
- [InlineData(typeof(TimeSpan))]
- public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureDateType(Type type)
- {
- var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
+ [Theory]
+ [InlineData(typeof(DateTime))]
+ [InlineData(typeof(DateOnly))]
+ [InlineData(typeof(DateTimeOffset))]
+ [InlineData(typeof(TimeOnly))]
+ [InlineData(typeof(TimeSpan))]
+ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureDateType(Type type)
+ {
+ var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
- Assert.NotNull(methodFound);
+ Assert.NotNull(methodFound);
- var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
- Assert.NotNull(call);
- var parameters = call!.Method.GetParameters();
+ var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
+ Assert.NotNull(call);
+ var parameters = call!.Method.GetParameters();
- if (@type == typeof(TimeSpan))
- {
- Assert.Equal(3, parameters.Length);
- Assert.Equal(typeof(string), parameters[0].ParameterType);
- Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
- Assert.True(parameters[2].IsOut);
- }
- else
- {
- Assert.Equal(4, parameters.Length);
- Assert.Equal(typeof(string), parameters[0].ParameterType);
- Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
- Assert.Equal(typeof(DateTimeStyles), parameters[2].ParameterType);
- Assert.True(parameters[3].IsOut);
- }
- }
-
- [Theory]
- [InlineData(typeof(TryParseStringRecord))]
- [InlineData(typeof(TryParseStringStruct))]
- [InlineData(typeof(TryParseInheritClassWithFormatProvider))]
- [InlineData(typeof(TryParseFromInterfaceWithFormatProvider))]
- public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureCustomType(Type type)
+ if (@type == typeof(TimeSpan))
{
- var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
-
- Assert.NotNull(methodFound);
-
- var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
- Assert.NotNull(call);
- var parameters = call!.Method.GetParameters();
-
Assert.Equal(3, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
Assert.True(parameters[2].IsOut);
- Assert.True(((call.Arguments[1] as ConstantExpression)!.Value as CultureInfo)!.Equals(CultureInfo.InvariantCulture));
}
-
- [Theory]
- [InlineData(typeof(TryParseNoFormatProviderRecord))]
- [InlineData(typeof(TryParseNoFormatProviderStruct))]
- [InlineData(typeof(TryParseInheritClass))]
- [InlineData(typeof(TryParseFromInterface))]
- [InlineData(typeof(TryParseFromGrandparentInterface))]
- [InlineData(typeof(TryParseDirectlyAndFromInterface))]
- [InlineData(typeof(TryParseFromClassAndInterface))]
- public void FindTryParseMethod_WithNoFormatProvider(Type type)
+ else
{
- var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
- Assert.NotNull(methodFound);
-
- var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
- Assert.NotNull(call);
- var parameters = call!.Method.GetParameters();
-
- Assert.Equal(2, parameters.Length);
+ Assert.Equal(4, parameters.Length);
Assert.Equal(typeof(string), parameters[0].ParameterType);
- Assert.True(parameters[1].IsOut);
+ Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
+ Assert.Equal(typeof(DateTimeStyles), parameters[2].ParameterType);
+ Assert.True(parameters[3].IsOut);
}
+ }
- public static IEnumerable<object[]> TryParseStringParameterInfoData
+ [Theory]
+ [InlineData(typeof(TryParseStringRecord))]
+ [InlineData(typeof(TryParseStringStruct))]
+ [InlineData(typeof(TryParseInheritClassWithFormatProvider))]
+ [InlineData(typeof(TryParseFromInterfaceWithFormatProvider))]
+ public void FindTryParseStringMethod_ReturnsTheExpectedTryParseMethodWithInvariantCultureCustomType(Type type)
+ {
+ var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
+
+ Assert.NotNull(methodFound);
+
+ var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
+ Assert.NotNull(call);
+ var parameters = call!.Method.GetParameters();
+
+ Assert.Equal(3, parameters.Length);
+ Assert.Equal(typeof(string), parameters[0].ParameterType);
+ Assert.Equal(typeof(IFormatProvider), parameters[1].ParameterType);
+ Assert.True(parameters[2].IsOut);
+ Assert.True(((call.Arguments[1] as ConstantExpression)!.Value as CultureInfo)!.Equals(CultureInfo.InvariantCulture));
+ }
+
+ [Theory]
+ [InlineData(typeof(TryParseNoFormatProviderRecord))]
+ [InlineData(typeof(TryParseNoFormatProviderStruct))]
+ [InlineData(typeof(TryParseInheritClass))]
+ [InlineData(typeof(TryParseFromInterface))]
+ [InlineData(typeof(TryParseFromGrandparentInterface))]
+ [InlineData(typeof(TryParseDirectlyAndFromInterface))]
+ [InlineData(typeof(TryParseFromClassAndInterface))]
+ public void FindTryParseMethod_WithNoFormatProvider(Type type)
+ {
+ var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(@type);
+ Assert.NotNull(methodFound);
+
+ var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
+ Assert.NotNull(call);
+ var parameters = call!.Method.GetParameters();
+
+ Assert.Equal(2, parameters.Length);
+ Assert.Equal(typeof(string), parameters[0].ParameterType);
+ Assert.True(parameters[1].IsOut);
+ }
+
+ public static IEnumerable<object[]> TryParseStringParameterInfoData
+ {
+ get
{
- get
+ return new[]
{
- return new[]
- {
new[]
{
GetFirstParameter((TryParseStringRecord arg) => TryParseStringRecordMethod(arg)),
@@ -137,127 +137,127 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests
GetFirstParameter((TryParseStringStruct? arg) => TryParseStringNullableStructMethod(arg)),
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(TryParseStringParameterInfoData))]
- public void HasTryParseStringMethod_ReturnsTrueWhenMethodExists(ParameterInfo parameterInfo)
- {
- Assert.True(new ParameterBindingMethodCache().HasTryParseMethod(parameterInfo));
- }
+ [Theory]
+ [MemberData(nameof(TryParseStringParameterInfoData))]
+ public void HasTryParseStringMethod_ReturnsTrueWhenMethodExists(ParameterInfo parameterInfo)
+ {
+ Assert.True(new ParameterBindingMethodCache().HasTryParseMethod(parameterInfo));
+ }
- [Fact]
- public void FindTryParseStringMethod_WorksForEnums()
- {
- var type = typeof(Choice);
- var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(type);
+ [Fact]
+ public void FindTryParseStringMethod_WorksForEnums()
+ {
+ var type = typeof(Choice);
+ var methodFound = new ParameterBindingMethodCache().FindTryParseMethod(type);
- Assert.NotNull(methodFound);
+ Assert.NotNull(methodFound);
- var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
- Assert.NotNull(call);
- var method = call!.Method;
- var parameters = method.GetParameters();
+ var call = methodFound!(Expression.Variable(type, "parsedValue")) as MethodCallExpression;
+ Assert.NotNull(call);
+ var method = call!.Method;
+ var parameters = method.GetParameters();
- // By default, we use Enum.TryParse<T>
- Assert.True(method.IsGenericMethod);
- Assert.Equal(2, parameters.Length);
- Assert.Equal(typeof(string), parameters[0].ParameterType);
- Assert.True(parameters[1].IsOut);
- }
+ // By default, we use Enum.TryParse<T>
+ Assert.True(method.IsGenericMethod);
+ Assert.Equal(2, parameters.Length);
+ Assert.Equal(typeof(string), parameters[0].ParameterType);
+ Assert.True(parameters[1].IsOut);
+ }
- [Fact]
- public void FindTryParseStringMethod_WorksForEnumsWhenNonGenericEnumParseIsUsed()
- {
- var type = typeof(Choice);
- var cache = new ParameterBindingMethodCache(preferNonGenericEnumParseOverload: true);
- var methodFound = cache.FindTryParseMethod(type);
+ [Fact]
+ public void FindTryParseStringMethod_WorksForEnumsWhenNonGenericEnumParseIsUsed()
+ {
+ var type = typeof(Choice);
+ var cache = new ParameterBindingMethodCache(preferNonGenericEnumParseOverload: true);
+ var methodFound = cache.FindTryParseMethod(type);
- Assert.NotNull(methodFound);
+ Assert.NotNull(methodFound);
- var parsedValue = Expression.Variable(type, "parsedValue");
- var block = methodFound!(parsedValue) as BlockExpression;
- Assert.NotNull(block);
- Assert.Equal(typeof(bool), block!.Type);
+ var parsedValue = Expression.Variable(type, "parsedValue");
+ var block = methodFound!(parsedValue) as BlockExpression;
+ Assert.NotNull(block);
+ Assert.Equal(typeof(bool), block!.Type);
- var parseEnum = Expression.Lambda<Func<string, Choice>>(Expression.Block(new[] { parsedValue },
- block,
- parsedValue), ParameterBindingMethodCache.TempSourceStringExpr).Compile();
+ var parseEnum = Expression.Lambda<Func<string, Choice>>(Expression.Block(new[] { parsedValue },
+ block,
+ parsedValue), ParameterBindingMethodCache.TempSourceStringExpr).Compile();
- Assert.Equal(Choice.One, parseEnum("One"));
- Assert.Equal(Choice.Two, parseEnum("Two"));
- Assert.Equal(Choice.Three, parseEnum("Three"));
- }
+ Assert.Equal(Choice.One, parseEnum("One"));
+ Assert.Equal(Choice.Two, parseEnum("Two"));
+ Assert.Equal(Choice.Three, parseEnum("Three"));
+ }
- [Fact]
- public async Task FindBindAsyncMethod_FindsCorrectMethodOnClass()
- {
- var type = typeof(BindAsyncRecord);
- var cache = new ParameterBindingMethodCache();
- var parameter = new MockParameterInfo(type, "bindAsyncRecord");
- var methodFound = cache.FindBindAsyncMethod(parameter);
+ [Fact]
+ public async Task FindBindAsyncMethod_FindsCorrectMethodOnClass()
+ {
+ var type = typeof(BindAsyncRecord);
+ var cache = new ParameterBindingMethodCache();
+ var parameter = new MockParameterInfo(type, "bindAsyncRecord");
+ var methodFound = cache.FindBindAsyncMethod(parameter);
- Assert.NotNull(methodFound.Expression);
- Assert.Equal(2, methodFound.ParamCount);
+ Assert.NotNull(methodFound.Expression);
+ Assert.Equal(2, methodFound.ParamCount);
- var parsedValue = Expression.Variable(type, "parsedValue");
+ var parsedValue = Expression.Variable(type, "parsedValue");
- var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(
- Expression.Block(new[] { parsedValue }, methodFound.Expression!),
- ParameterBindingMethodCache.HttpContextExpr).Compile();
+ var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(
+ Expression.Block(new[] { parsedValue }, methodFound.Expression!),
+ ParameterBindingMethodCache.HttpContextExpr).Compile();
- var httpContext = new DefaultHttpContext
- {
- Request =
+ var httpContext = new DefaultHttpContext
+ {
+ Request =
{
Headers =
{
["ETag"] = "42",
},
},
- };
+ };
- Assert.Equal(new BindAsyncRecord(42), await parseHttpContext(httpContext));
- }
+ Assert.Equal(new BindAsyncRecord(42), await parseHttpContext(httpContext));
+ }
- [Fact]
- public async Task FindBindAsyncMethod_FindsSingleArgBindAsync()
- {
- var type = typeof(BindAsyncSingleArgStruct);
- var cache = new ParameterBindingMethodCache();
- var parameter = new MockParameterInfo(type, "bindAsyncSingleArgStruct");
- var methodFound = cache.FindBindAsyncMethod(parameter);
+ [Fact]
+ public async Task FindBindAsyncMethod_FindsSingleArgBindAsync()
+ {
+ var type = typeof(BindAsyncSingleArgStruct);
+ var cache = new ParameterBindingMethodCache();
+ var parameter = new MockParameterInfo(type, "bindAsyncSingleArgStruct");
+ var methodFound = cache.FindBindAsyncMethod(parameter);
- Assert.NotNull(methodFound.Expression);
- Assert.Equal(1, methodFound.ParamCount);
+ Assert.NotNull(methodFound.Expression);
+ Assert.Equal(1, methodFound.ParamCount);
- var parsedValue = Expression.Variable(type, "parsedValue");
+ var parsedValue = Expression.Variable(type, "parsedValue");
- var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(
- Expression.Block(new[] { parsedValue }, methodFound.Expression!),
- ParameterBindingMethodCache.HttpContextExpr).Compile();
+ var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(
+ Expression.Block(new[] { parsedValue }, methodFound.Expression!),
+ ParameterBindingMethodCache.HttpContextExpr).Compile();
- var httpContext = new DefaultHttpContext
- {
- Request =
+ var httpContext = new DefaultHttpContext
+ {
+ Request =
{
Headers =
{
["ETag"] = "42",
},
},
- };
+ };
- Assert.Equal(new BindAsyncSingleArgStruct(42), await parseHttpContext(httpContext));
- }
+ Assert.Equal(new BindAsyncSingleArgStruct(42), await parseHttpContext(httpContext));
+ }
- public static IEnumerable<object[]> BindAsyncParameterInfoData
+ public static IEnumerable<object[]> BindAsyncParameterInfoData
+ {
+ get
{
- get
+ return new[]
{
- return new[]
- {
new[]
{
GetFirstParameter((BindAsyncRecord arg) => BindAsyncRecordMethod(arg)),
@@ -303,725 +303,724 @@ namespace Microsoft.AspNetCore.Http.Extensions.Tests
GetFirstParameter((BindAsyncFromInterfaceWithParameterInfo arg) => BindAsyncFromInterfaceWithParameterInfoMethod(arg))
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(BindAsyncParameterInfoData))]
- public void HasBindAsyncMethod_ReturnsTrueWhenMethodExists(ParameterInfo parameterInfo)
- {
- Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
- }
+ [Theory]
+ [MemberData(nameof(BindAsyncParameterInfoData))]
+ public void HasBindAsyncMethod_ReturnsTrueWhenMethodExists(ParameterInfo parameterInfo)
+ {
+ Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
+ }
- [Fact]
- public void HasBindAsyncMethod_ReturnsTrueForNullableReturningBindAsyncStructMethod()
- {
- var parameterInfo = GetFirstParameter((NullableReturningBindAsyncStruct arg) => NullableReturningBindAsyncStructMethod(arg));
- Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
- }
+ [Fact]
+ public void HasBindAsyncMethod_ReturnsTrueForNullableReturningBindAsyncStructMethod()
+ {
+ var parameterInfo = GetFirstParameter((NullableReturningBindAsyncStruct arg) => NullableReturningBindAsyncStructMethod(arg));
+ Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
+ }
- [Fact]
- public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNullableType()
- {
- var parameterInfo = GetFirstParameter((BindAsyncStruct? arg) => BindAsyncNullableStructMethod(arg));
- Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
- }
+ [Fact]
+ public void FindBindAsyncMethod_FindsNonNullableReturningBindAsyncMethodGivenNullableType()
+ {
+ var parameterInfo = GetFirstParameter((BindAsyncStruct? arg) => BindAsyncNullableStructMethod(arg));
+ Assert.True(new ParameterBindingMethodCache().HasBindAsyncMethod(parameterInfo));
+ }
- [Fact]
- public async Task FindBindAsyncMethod_FindsFallbackMethodWhenPreferredMethodsReturnTypeIsWrong()
- {
- var parameterInfo = GetFirstParameter((BindAsyncFallsBack? arg) => BindAsyncFallbackMethod(arg));
- var cache = new ParameterBindingMethodCache();
- Assert.True(cache.HasBindAsyncMethod(parameterInfo));
- var methodFound = cache.FindBindAsyncMethod(parameterInfo);
+ [Fact]
+ public async Task FindBindAsyncMethod_FindsFallbackMethodWhenPreferredMethodsReturnTypeIsWrong()
+ {
+ var parameterInfo = GetFirstParameter((BindAsyncFallsBack? arg) => BindAsyncFallbackMethod(arg));
+ var cache = new ParameterBindingMethodCache();
+ Assert.True(cache.HasBindAsyncMethod(parameterInfo));
+ var methodFound = cache.FindBindAsyncMethod(parameterInfo);
- var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
- ParameterBindingMethodCache.HttpContextExpr).Compile();
+ var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
+ ParameterBindingMethodCache.HttpContextExpr).Compile();
- var httpContext = new DefaultHttpContext();
+ var httpContext = new DefaultHttpContext();
- Assert.Null(await parseHttpContext(httpContext));
- }
+ Assert.Null(await parseHttpContext(httpContext));
+ }
- [Fact]
- public async Task FindBindAsyncMethod_FindsFallbackMethodFromInheritedWhenPreferredMethodIsInvalid()
- {
- var parameterInfo = GetFirstParameter((BindAsyncBadMethod? arg) => BindAsyncBadMethodMethod(arg));
- var cache = new ParameterBindingMethodCache();
- Assert.True(cache.HasBindAsyncMethod(parameterInfo));
- var methodFound = cache.FindBindAsyncMethod(parameterInfo);
+ [Fact]
+ public async Task FindBindAsyncMethod_FindsFallbackMethodFromInheritedWhenPreferredMethodIsInvalid()
+ {
+ var parameterInfo = GetFirstParameter((BindAsyncBadMethod? arg) => BindAsyncBadMethodMethod(arg));
+ var cache = new ParameterBindingMethodCache();
+ Assert.True(cache.HasBindAsyncMethod(parameterInfo));
+ var methodFound = cache.FindBindAsyncMethod(parameterInfo);
- var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
- ParameterBindingMethodCache.HttpContextExpr).Compile();
+ var parseHttpContext = Expression.Lambda<Func<HttpContext, ValueTask<object>>>(methodFound.Expression!,
+ ParameterBindingMethodCache.HttpContextExpr).Compile();
- var httpContext = new DefaultHttpContext();
+ var httpContext = new DefaultHttpContext();
- Assert.Null(await parseHttpContext(httpContext));
- }
+ Assert.Null(await parseHttpContext(httpContext));
+ }
- [Theory]
- [InlineData(typeof(InvalidVoidReturnTryParseStruct))]
- [InlineData(typeof(InvalidVoidReturnTryParseClass))]
- [InlineData(typeof(InvalidWrongTypeTryParseStruct))]
- [InlineData(typeof(InvalidWrongTypeTryParseClass))]
- [InlineData(typeof(InvalidTryParseNullableStruct))]
- [InlineData(typeof(InvalidTooFewArgsTryParseStruct))]
- [InlineData(typeof(InvalidTooFewArgsTryParseClass))]
- [InlineData(typeof(InvalidNonStaticTryParseStruct))]
- [InlineData(typeof(InvalidNonStaticTryParseClass))]
- [InlineData(typeof(TryParseWrongTypeInheritClass))]
- [InlineData(typeof(TryParseWrongTypeFromInterface))]
- public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
- {
- var ex = Assert.Throws<InvalidOperationException>(
- () => new ParameterBindingMethodCache().FindTryParseMethod(type));
- Assert.StartsWith($"TryParse method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
- Assert.Contains($"bool TryParse(string, IFormatProvider, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
- Assert.Contains($"bool TryParse(string, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
- }
+ [Theory]
+ [InlineData(typeof(InvalidVoidReturnTryParseStruct))]
+ [InlineData(typeof(InvalidVoidReturnTryParseClass))]
+ [InlineData(typeof(InvalidWrongTypeTryParseStruct))]
+ [InlineData(typeof(InvalidWrongTypeTryParseClass))]
+ [InlineData(typeof(InvalidTryParseNullableStruct))]
+ [InlineData(typeof(InvalidTooFewArgsTryParseStruct))]
+ [InlineData(typeof(InvalidTooFewArgsTryParseClass))]
+ [InlineData(typeof(InvalidNonStaticTryParseStruct))]
+ [InlineData(typeof(InvalidNonStaticTryParseClass))]
+ [InlineData(typeof(TryParseWrongTypeInheritClass))]
+ [InlineData(typeof(TryParseWrongTypeFromInterface))]
+ public void FindTryParseMethod_ThrowsIfInvalidTryParseOnType(Type type)
+ {
+ var ex = Assert.Throws<InvalidOperationException>(
+ () => new ParameterBindingMethodCache().FindTryParseMethod(type));
+ Assert.StartsWith($"TryParse method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
+ Assert.Contains($"bool TryParse(string, IFormatProvider, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
+ Assert.Contains($"bool TryParse(string, out {TypeNameHelper.GetTypeDisplayName(type, fullName: false)})", ex.Message);
+ }
- [Fact]
- public void FindTryParseMethod_ThrowsIfMultipleInterfacesMatch()
- {
- var ex = Assert.Throws<InvalidOperationException>(
- () => new ParameterBindingMethodCache().FindTryParseMethod(typeof(TryParseFromMultipleInterfaces)));
- Assert.Equal("TryParseFromMultipleInterfaces implements multiple interfaces defining a static Boolean TryParse(System.String, TryParseFromMultipleInterfaces ByRef) method causing ambiguity.", ex.Message);
- }
+ [Fact]
+ public void FindTryParseMethod_ThrowsIfMultipleInterfacesMatch()
+ {
+ var ex = Assert.Throws<InvalidOperationException>(
+ () => new ParameterBindingMethodCache().FindTryParseMethod(typeof(TryParseFromMultipleInterfaces)));
+ Assert.Equal("TryParseFromMultipleInterfaces implements multiple interfaces defining a static Boolean TryParse(System.String, TryParseFromMultipleInterfaces ByRef) method causing ambiguity.", ex.Message);
+ }
- [Theory]
- [InlineData(typeof(TryParseClassWithGoodAndBad))]
- [InlineData(typeof(TryParseStructWithGoodAndBad))]
- public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
- {
- var method = new ParameterBindingMethodCache().FindTryParseMethod(type);
- Assert.NotNull(method);
- }
+ [Theory]
+ [InlineData(typeof(TryParseClassWithGoodAndBad))]
+ [InlineData(typeof(TryParseStructWithGoodAndBad))]
+ public void FindTryParseMethod_IgnoresInvalidTryParseIfGoodOneFound(Type type)
+ {
+ var method = new ParameterBindingMethodCache().FindTryParseMethod(type);
+ Assert.NotNull(method);
+ }
- [Theory]
- [InlineData(typeof(InvalidWrongReturnBindAsyncStruct))]
- [InlineData(typeof(InvalidWrongReturnBindAsyncClass))]
- [InlineData(typeof(InvalidWrongParamBindAsyncStruct))]
- [InlineData(typeof(InvalidWrongParamBindAsyncClass))]
- [InlineData(typeof(BindAsyncWrongTypeInherit))]
- [InlineData(typeof(BindAsyncWithParameterInfoWrongTypeInherit))]
- [InlineData(typeof(BindAsyncWrongTypeFromInterface))]
- [InlineData(typeof(BindAsyncBothBadMethods))]
- public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
- {
- var cache = new ParameterBindingMethodCache();
- var parameter = new MockParameterInfo(type, "anything");
- var ex = Assert.Throws<InvalidOperationException>(
- () => cache.FindBindAsyncMethod(parameter));
- Assert.StartsWith($"BindAsync method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
- Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
- Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context)", ex.Message);
- Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
- Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context)", ex.Message);
- }
+ [Theory]
+ [InlineData(typeof(InvalidWrongReturnBindAsyncStruct))]
+ [InlineData(typeof(InvalidWrongReturnBindAsyncClass))]
+ [InlineData(typeof(InvalidWrongParamBindAsyncStruct))]
+ [InlineData(typeof(InvalidWrongParamBindAsyncClass))]
+ [InlineData(typeof(BindAsyncWrongTypeInherit))]
+ [InlineData(typeof(BindAsyncWithParameterInfoWrongTypeInherit))]
+ [InlineData(typeof(BindAsyncWrongTypeFromInterface))]
+ [InlineData(typeof(BindAsyncBothBadMethods))]
+ public void FindBindAsyncMethod_ThrowsIfInvalidBindAsyncOnType(Type type)
+ {
+ var cache = new ParameterBindingMethodCache();
+ var parameter = new MockParameterInfo(type, "anything");
+ var ex = Assert.Throws<InvalidOperationException>(
+ () => cache.FindBindAsyncMethod(parameter));
+ Assert.StartsWith($"BindAsync method found on {TypeNameHelper.GetTypeDisplayName(type, fullName: false)} with incorrect format. Must be a static method with format", ex.Message);
+ Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
+ Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}> BindAsync(HttpContext context)", ex.Message);
+ Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context, ParameterInfo parameter)", ex.Message);
+ Assert.Contains($"ValueTask<{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}?> BindAsync(HttpContext context)", ex.Message);
+ }
- [Fact]
- public void FindBindAsyncMethod_ThrowsIfMultipleInterfacesMatch()
- {
- var cache = new ParameterBindingMethodCache();
- var parameter = new MockParameterInfo(typeof(BindAsyncFromMultipleInterfaces), "anything");
- var ex = Assert.Throws<InvalidOperationException>(() => cache.FindBindAsyncMethod(parameter));
- Assert.Equal("BindAsyncFromMultipleInterfaces implements multiple interfaces defining a static System.Threading.Tasks.ValueTask`1[Microsoft.AspNetCore.Http.Extensions.Tests.ParameterBindingMethodCacheTests+BindAsyncFromMultipleInterfaces] BindAsync(Microsoft.AspNetCore.Http.HttpContext) method causing ambiguity.", ex.Message);
- }
+ [Fact]
+ public void FindBindAsyncMethod_ThrowsIfMultipleInterfacesMatch()
+ {
+ var cache = new ParameterBindingMethodCache();
+ var parameter = new MockParameterInfo(typeof(BindAsyncFromMultipleInterfaces), "anything");
+ var ex = Assert.Throws<InvalidOperationException>(() => cache.FindBindAsyncMethod(parameter));
+ Assert.Equal("BindAsyncFromMultipleInterfaces implements multiple interfaces defining a static System.Threading.Tasks.ValueTask`1[Microsoft.AspNetCore.Http.Extensions.Tests.ParameterBindingMethodCacheTests+BindAsyncFromMultipleInterfaces] BindAsync(Microsoft.AspNetCore.Http.HttpContext) method causing ambiguity.", ex.Message);
+ }
- [Theory]
- [InlineData(typeof(BindAsyncStructWithGoodAndBad))]
- [InlineData(typeof(BindAsyncClassWithGoodAndBad))]
- public void FindBindAsyncMethod_IgnoresInvalidBindAsyncIfGoodOneFound(Type type)
- {
- var cache = new ParameterBindingMethodCache();
- var parameter = new MockParameterInfo(type, "anything");
- var (expression, _) = cache.FindBindAsyncMethod(parameter);
- Assert.NotNull(expression);
- }
+ [Theory]
+ [InlineData(typeof(BindAsyncStructWithGoodAndBad))]
+ [InlineData(typeof(BindAsyncClassWithGoodAndBad))]
+ public void FindBindAsyncMethod_IgnoresInvalidBindAsyncIfGoodOneFound(Type type)
+ {
+ var cache = new ParameterBindingMethodCache();
+ var parameter = new MockParameterInfo(type, "anything");
+ var (expression, _) = cache.FindBindAsyncMethod(parameter);
+ Assert.NotNull(expression);
+ }
- enum Choice
- {
- One,
- Two,
- Three
- }
+ enum Choice
+ {
+ One,
+ Two,
+ Three
+ }
- private static void TryParseStringRecordMethod(TryParseStringRecord arg) { }
- private static void TryParseStringStructMethod(TryParseStringStruct arg) { }
- private static void TryParseStringNullableStructMethod(TryParseStringStruct? arg) { }
-
- private static void BindAsyncRecordMethod(BindAsyncRecord arg) { }
- private static void BindAsyncStructMethod(BindAsyncStruct arg) { }
- private static void BindAsyncNullableStructMethod(BindAsyncStruct? arg) { }
- private static void NullableReturningBindAsyncStructMethod(NullableReturningBindAsyncStruct arg) { }
-
- private static void BindAsyncSingleArgRecordMethod(BindAsyncSingleArgRecord arg) { }
- private static void BindAsyncSingleArgStructMethod(BindAsyncSingleArgStruct arg) { }
- private static void InheritBindAsyncMethod(InheritBindAsync arg) { }
- private static void InheritBindAsyncWithParameterInfoMethod(InheritBindAsyncWithParameterInfo args) { }
- private static void BindAsyncFromInterfaceMethod(BindAsyncFromInterface arg) { }
- private static void BindAsyncFromGrandparentInterfaceMethod(BindAsyncFromGrandparentInterface arg) { }
- private static void BindAsyncDirectlyAndFromInterfaceMethod(BindAsyncDirectlyAndFromInterface arg) { }
- private static void BindAsyncFromClassAndInterfaceMethod(BindAsyncFromClassAndInterface arg) { }
- private static void BindAsyncFromInterfaceWithParameterInfoMethod(BindAsyncFromInterfaceWithParameterInfo args) { }
- private static void BindAsyncFallbackMethod(BindAsyncFallsBack? arg) { }
- private static void BindAsyncBadMethodMethod(BindAsyncBadMethod? arg) { }
-
- private static ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
- {
- var mc = (MethodCallExpression)expr.Body;
- return mc.Method.GetParameters()[0];
- }
+ private static void TryParseStringRecordMethod(TryParseStringRecord arg) { }
+ private static void TryParseStringStructMethod(TryParseStringStruct arg) { }
+ private static void TryParseStringNullableStructMethod(TryParseStringStruct? arg) { }
+
+ private static void BindAsyncRecordMethod(BindAsyncRecord arg) { }
+ private static void BindAsyncStructMethod(BindAsyncStruct arg) { }
+ private static void BindAsyncNullableStructMethod(BindAsyncStruct? arg) { }
+ private static void NullableReturningBindAsyncStructMethod(NullableReturningBindAsyncStruct arg) { }
+
+ private static void BindAsyncSingleArgRecordMethod(BindAsyncSingleArgRecord arg) { }
+ private static void BindAsyncSingleArgStructMethod(BindAsyncSingleArgStruct arg) { }
+ private static void InheritBindAsyncMethod(InheritBindAsync arg) { }
+ private static void InheritBindAsyncWithParameterInfoMethod(InheritBindAsyncWithParameterInfo args) { }
+ private static void BindAsyncFromInterfaceMethod(BindAsyncFromInterface arg) { }
+ private static void BindAsyncFromGrandparentInterfaceMethod(BindAsyncFromGrandparentInterface arg) { }
+ private static void BindAsyncDirectlyAndFromInterfaceMethod(BindAsyncDirectlyAndFromInterface arg) { }
+ private static void BindAsyncFromClassAndInterfaceMethod(BindAsyncFromClassAndInterface arg) { }
+ private static void BindAsyncFromInterfaceWithParameterInfoMethod(BindAsyncFromInterfaceWithParameterInfo args) { }
+ private static void BindAsyncFallbackMethod(BindAsyncFallsBack? arg) { }
+ private static void BindAsyncBadMethodMethod(BindAsyncBadMethod? arg) { }
+
+ private static ParameterInfo GetFirstParameter<T>(Expression<Action<T>> expr)
+ {
+ var mc = (MethodCallExpression)expr.Body;
+ return mc.Method.GetParameters()[0];
+ }
- private record TryParseStringRecord(int Value)
+ private record TryParseStringRecord(int Value)
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out TryParseStringRecord? result)
{
- public static bool TryParse(string? value, IFormatProvider formatProvider, out TryParseStringRecord? result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = null;
- return false;
- }
-
- result = new TryParseStringRecord(val);
- return true;
+ result = null;
+ return false;
}
- }
-
- private record struct TryParseStringStruct(int Value)
- {
- public static bool TryParse(string? value, IFormatProvider formatProvider, out TryParseStringStruct result)
- {
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = default;
- return false;
- }
- result = new TryParseStringStruct(val);
- return true;
- }
+ result = new TryParseStringRecord(val);
+ return true;
}
+ }
- private record struct InvalidVoidReturnTryParseStruct(int Value)
+ private record struct TryParseStringStruct(int Value)
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out TryParseStringStruct result)
{
- public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = default;
- return;
- }
-
- result = new InvalidVoidReturnTryParseStruct(val);
- return;
+ result = default;
+ return false;
}
- }
-
- private record struct InvalidWrongTypeTryParseStruct(int Value)
- {
- public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
- {
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = default;
- return false;
- }
- result = new InvalidVoidReturnTryParseStruct(val);
- return true;
- }
+ result = new TryParseStringStruct(val);
+ return true;
}
+ }
- private record struct InvalidTryParseNullableStruct(int Value)
+ private record struct InvalidVoidReturnTryParseStruct(int Value)
+ {
+ public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
- public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidTryParseNullableStruct? result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = default;
- return false;
- }
-
- result = new InvalidTryParseNullableStruct(val);
- return true;
+ result = default;
+ return;
}
+
+ result = new InvalidVoidReturnTryParseStruct(val);
+ return;
}
+ }
- private record struct InvalidTooFewArgsTryParseStruct(int Value)
+ private record struct InvalidWrongTypeTryParseStruct(int Value)
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
- public static bool TryParse(out InvalidTooFewArgsTryParseStruct result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = default;
return false;
}
+
+ result = new InvalidVoidReturnTryParseStruct(val);
+ return true;
}
+ }
- private struct TryParseStructWithGoodAndBad
+ private record struct InvalidTryParseNullableStruct(int Value)
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidTryParseNullableStruct? result)
{
- public static bool TryParse(string? value, out TryParseStructWithGoodAndBad result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- result = new();
+ result = default;
return false;
}
- public static void TryParse(out TryParseStructWithGoodAndBad result)
- {
- result = new();
- }
+ result = new InvalidTryParseNullableStruct(val);
+ return true;
}
+ }
- private record struct InvalidNonStaticTryParseStruct(int Value)
+ private record struct InvalidTooFewArgsTryParseStruct(int Value)
+ {
+ public static bool TryParse(out InvalidTooFewArgsTryParseStruct result)
{
- public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
- {
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = default;
- return false;
- }
-
- result = new InvalidVoidReturnTryParseStruct(val);
- return true;
- }
+ result = default;
+ return false;
}
+ }
- private class InvalidVoidReturnTryParseClass
+ private struct TryParseStructWithGoodAndBad
+ {
+ public static bool TryParse(string? value, out TryParseStructWithGoodAndBad result)
{
- public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
- {
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = new();
- return;
- }
-
- result = new();
- }
+ result = new();
+ return false;
}
- private class InvalidWrongTypeTryParseClass
+ public static void TryParse(out TryParseStructWithGoodAndBad result)
{
- public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
- {
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = new();
- return false;
- }
-
- result = new();
- return true;
- }
+ result = new();
}
+ }
- private class InvalidTooFewArgsTryParseClass
+ private record struct InvalidNonStaticTryParseStruct(int Value)
+ {
+ public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseStruct result)
{
- public static bool TryParse(out InvalidTooFewArgsTryParseClass result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- result = new();
+ result = default;
return false;
}
+
+ result = new InvalidVoidReturnTryParseStruct(val);
+ return true;
}
+ }
- private class TryParseClassWithGoodAndBad
+ private class InvalidVoidReturnTryParseClass
+ {
+ public static void TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
{
- public static bool TryParse(string? value, out TryParseClassWithGoodAndBad result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
result = new();
- return false;
+ return;
}
- public static bool TryParse(out TryParseClassWithGoodAndBad result)
- {
- result = new();
- return false;
- }
+ result = new();
}
+ }
- private class InvalidNonStaticTryParseClass
+ private class InvalidWrongTypeTryParseClass
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out InvalidVoidReturnTryParseClass result)
{
- public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidNonStaticTryParseClass result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
- {
- result = new();
- return false;
- }
-
result = new();
- return true;
- }
- }
-
- private record TryParseNoFormatProviderRecord(int Value)
- {
- public static bool TryParse(string? value, out TryParseNoFormatProviderRecord? result)
- {
- if (!int.TryParse(value, out var val))
- {
- result = null;
- return false;
- }
-
- result = new TryParseNoFormatProviderRecord(val);
- return true;
+ return false;
}
- }
-
- private record struct TryParseNoFormatProviderStruct(int Value)
- {
- public static bool TryParse(string? value, out TryParseNoFormatProviderStruct result)
- {
- if (!int.TryParse(value, out var val))
- {
- result = default;
- return false;
- }
- result = new TryParseNoFormatProviderStruct(val);
- return true;
- }
+ result = new();
+ return true;
}
+ }
- private class BaseTryParseClass<T>
+ private class InvalidTooFewArgsTryParseClass
+ {
+ public static bool TryParse(out InvalidTooFewArgsTryParseClass result)
{
- public static bool TryParse(string? value, out T? result)
- {
- result = default(T);
- return false;
- }
+ result = new();
+ return false;
}
+ }
- private class TryParseInheritClass : BaseTryParseClass<TryParseInheritClass>
+ private class TryParseClassWithGoodAndBad
+ {
+ public static bool TryParse(string? value, out TryParseClassWithGoodAndBad result)
{
+ result = new();
+ return false;
}
- // using wrong T on purpose
- private class TryParseWrongTypeInheritClass : BaseTryParseClass<TryParseInheritClass>
+ public static bool TryParse(out TryParseClassWithGoodAndBad result)
{
+ result = new();
+ return false;
}
+ }
- private class BaseTryParseClassWithFormatProvider<T>
+ private class InvalidNonStaticTryParseClass
+ {
+ public bool TryParse(string? value, IFormatProvider formatProvider, out InvalidNonStaticTryParseClass result)
{
- public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
+ if (!int.TryParse(value, NumberStyles.Integer, formatProvider, out var val))
{
- result = default(T);
+ result = new();
return false;
}
- }
- private class TryParseInheritClassWithFormatProvider : BaseTryParseClassWithFormatProvider<TryParseInheritClassWithFormatProvider>
- {
+ result = new();
+ return true;
}
+ }
- private interface ITryParse<T>
+ private record TryParseNoFormatProviderRecord(int Value)
+ {
+ public static bool TryParse(string? value, out TryParseNoFormatProviderRecord? result)
{
- static bool TryParse(string? value, out T? result)
+ if (!int.TryParse(value, out var val))
{
- result = default(T);
+ result = null;
return false;
}
+
+ result = new TryParseNoFormatProviderRecord(val);
+ return true;
}
+ }
- private interface ITryParse2<T>
+ private record struct TryParseNoFormatProviderStruct(int Value)
+ {
+ public static bool TryParse(string? value, out TryParseNoFormatProviderStruct result)
{
- static bool TryParse(string? value, out T? result)
+ if (!int.TryParse(value, out var val))
{
- result = default(T);
+ result = default;
return false;
}
- }
- private interface IImplementITryParse<T> : ITryParse<T>
- {
+ result = new TryParseNoFormatProviderStruct(val);
+ return true;
}
+ }
- private class TryParseFromInterface : ITryParse<TryParseFromInterface>
+ private class BaseTryParseClass<T>
+ {
+ public static bool TryParse(string? value, out T? result)
{
+ result = default(T);
+ return false;
}
+ }
- private class TryParseFromGrandparentInterface : IImplementITryParse<TryParseFromGrandparentInterface>
- {
- }
+ private class TryParseInheritClass : BaseTryParseClass<TryParseInheritClass>
+ {
+ }
- private class TryParseDirectlyAndFromInterface : ITryParse<TryParseDirectlyAndFromInterface>
- {
- static bool TryParse(string? value, out TryParseDirectlyAndFromInterface? result)
- {
- result = null;
- return false;
- }
- }
+ // using wrong T on purpose
+ private class TryParseWrongTypeInheritClass : BaseTryParseClass<TryParseInheritClass>
+ {
+ }
- private class TryParseFromClassAndInterface
- : BaseTryParseClass<TryParseFromClassAndInterface>,
- ITryParse<TryParseFromClassAndInterface>
+ private class BaseTryParseClassWithFormatProvider<T>
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
{
+ result = default(T);
+ return false;
}
+ }
+
+ private class TryParseInheritClassWithFormatProvider : BaseTryParseClassWithFormatProvider<TryParseInheritClassWithFormatProvider>
+ {
+ }
- private class TryParseFromMultipleInterfaces
- : ITryParse<TryParseFromMultipleInterfaces>,
- ITryParse2<TryParseFromMultipleInterfaces>
+ private interface ITryParse<T>
+ {
+ static bool TryParse(string? value, out T? result)
{
+ result = default(T);
+ return false;
}
+ }
- // using wrong T on purpose
- private class TryParseWrongTypeFromInterface : ITryParse<TryParseFromInterface>
+ private interface ITryParse2<T>
+ {
+ static bool TryParse(string? value, out T? result)
{
+ result = default(T);
+ return false;
}
+ }
+
+ private interface IImplementITryParse<T> : ITryParse<T>
+ {
+ }
+
+ private class TryParseFromInterface : ITryParse<TryParseFromInterface>
+ {
+ }
+
+ private class TryParseFromGrandparentInterface : IImplementITryParse<TryParseFromGrandparentInterface>
+ {
+ }
- private interface ITryParseWithFormatProvider<T>
+ private class TryParseDirectlyAndFromInterface : ITryParse<TryParseDirectlyAndFromInterface>
+ {
+ static bool TryParse(string? value, out TryParseDirectlyAndFromInterface? result)
{
- public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
- {
- result = default(T);
- return false;
- }
+ result = null;
+ return false;
}
+ }
+
+ private class TryParseFromClassAndInterface
+ : BaseTryParseClass<TryParseFromClassAndInterface>,
+ ITryParse<TryParseFromClassAndInterface>
+ {
+ }
- private class TryParseFromInterfaceWithFormatProvider : ITryParseWithFormatProvider<TryParseFromInterfaceWithFormatProvider>
+ private class TryParseFromMultipleInterfaces
+ : ITryParse<TryParseFromMultipleInterfaces>,
+ ITryParse2<TryParseFromMultipleInterfaces>
+ {
+ }
+
+ // using wrong T on purpose
+ private class TryParseWrongTypeFromInterface : ITryParse<TryParseFromInterface>
+ {
+ }
+
+ private interface ITryParseWithFormatProvider<T>
+ {
+ public static bool TryParse(string? value, IFormatProvider formatProvider, out T? result)
{
+ result = default(T);
+ return false;
}
+ }
- private record BindAsyncRecord(int Value)
- {
- public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.Equal(typeof(BindAsyncRecord), parameter.ParameterType);
- Assert.Equal("bindAsyncRecord", parameter.Name);
+ private class TryParseFromInterfaceWithFormatProvider : ITryParseWithFormatProvider<TryParseFromInterfaceWithFormatProvider>
+ {
+ }
- if (!int.TryParse(context.Request.Headers.ETag, out var val))
- {
- return new(result: null);
- }
+ private record BindAsyncRecord(int Value)
+ {
+ public static ValueTask<BindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
+ {
+ Assert.Equal(typeof(BindAsyncRecord), parameter.ParameterType);
+ Assert.Equal("bindAsyncRecord", parameter.Name);
- return new(result: new(val));
+ if (!int.TryParse(context.Request.Headers.ETag, out var val))
+ {
+ return new(result: null);
}
+
+ return new(result: new(val));
}
+ }
- private record struct BindAsyncStruct(int Value)
+ private record struct BindAsyncStruct(int Value)
+ {
+ public static ValueTask<BindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
{
- public static ValueTask<BindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.Equal(typeof(BindAsyncStruct), parameter.ParameterType);
- Assert.Equal("bindAsyncStruct", parameter.Name);
+ Assert.Equal(typeof(BindAsyncStruct), parameter.ParameterType);
+ Assert.Equal("bindAsyncStruct", parameter.Name);
- if (!int.TryParse(context.Request.Headers.ETag, out var val))
- {
- throw new BadHttpRequestException("The request is missing the required ETag header.");
- }
-
- return new(result: new(val));
+ if (!int.TryParse(context.Request.Headers.ETag, out var val))
+ {
+ throw new BadHttpRequestException("The request is missing the required ETag header.");
}
- }
- private record struct NullableReturningBindAsyncStruct(int Value)
- {
- public static ValueTask<NullableReturningBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
+ return new(result: new(val));
}
+ }
+
+ private record struct NullableReturningBindAsyncStruct(int Value)
+ {
+ public static ValueTask<NullableReturningBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- private record BindAsyncSingleArgRecord(int Value)
+ private record BindAsyncSingleArgRecord(int Value)
+ {
+ public static ValueTask<BindAsyncSingleArgRecord?> BindAsync(HttpContext context)
{
- public static ValueTask<BindAsyncSingleArgRecord?> BindAsync(HttpContext context)
+ if (!int.TryParse(context.Request.Headers.ETag, out var val))
{
- if (!int.TryParse(context.Request.Headers.ETag, out var val))
- {
- return new(result: null);
- }
-
- return new(result: new(val));
+ return new(result: null);
}
+
+ return new(result: new(val));
}
+ }
- private record struct BindAsyncSingleArgStruct(int Value)
+ private record struct BindAsyncSingleArgStruct(int Value)
+ {
+ public static ValueTask<BindAsyncSingleArgStruct> BindAsync(HttpContext context)
{
- public static ValueTask<BindAsyncSingleArgStruct> BindAsync(HttpContext context)
+ if (!int.TryParse(context.Request.Headers.ETag, out var val))
{
- if (!int.TryParse(context.Request.Headers.ETag, out var val))
- {
- throw new BadHttpRequestException("The request is missing the required ETag header.");
- }
-
- return new(result: new(val));
+ throw new BadHttpRequestException("The request is missing the required ETag header.");
}
- }
- private record struct InvalidWrongReturnBindAsyncStruct(int Value)
- {
- public static Task<InvalidWrongReturnBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
+ return new(result: new(val));
}
+ }
- private class InvalidWrongReturnBindAsyncClass
- {
- public static Task<InvalidWrongReturnBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private record struct InvalidWrongReturnBindAsyncStruct(int Value)
+ {
+ public static Task<InvalidWrongReturnBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- private record struct InvalidWrongParamBindAsyncStruct(int Value)
- {
- public static ValueTask<InvalidWrongParamBindAsyncStruct> BindAsync(ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private class InvalidWrongReturnBindAsyncClass
+ {
+ public static Task<InvalidWrongReturnBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- private class InvalidWrongParamBindAsyncClass
- {
- public static Task<InvalidWrongParamBindAsyncClass> BindAsync(ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private record struct InvalidWrongParamBindAsyncStruct(int Value)
+ {
+ public static ValueTask<InvalidWrongParamBindAsyncStruct> BindAsync(ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- private record struct BindAsyncStructWithGoodAndBad(int Value)
- {
- public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
+ private class InvalidWrongParamBindAsyncClass
+ {
+ public static Task<InvalidWrongParamBindAsyncClass> BindAsync(ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private record struct BindAsyncStructWithGoodAndBad(int Value)
+ {
+ public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
- private class BindAsyncClassWithGoodAndBad
- {
- public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
+ public static ValueTask<BindAsyncStructWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private class BindAsyncClassWithGoodAndBad
+ {
+ public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
- private class BaseBindAsync<T>
- {
- public static ValueTask<T?> BindAsync(HttpContext context)
- {
- return new(default(T));
- }
- }
+ public static ValueTask<BindAsyncClassWithGoodAndBad> BindAsync(ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- private class InheritBindAsync : BaseBindAsync<InheritBindAsync>
+ private class BaseBindAsync<T>
+ {
+ public static ValueTask<T?> BindAsync(HttpContext context)
{
+ return new(default(T));
}
+ }
- // Using wrong T on purpose
- private class BindAsyncWrongTypeInherit : BaseBindAsync<InheritBindAsync>
- {
- }
+ private class InheritBindAsync : BaseBindAsync<InheritBindAsync>
+ {
+ }
- private class BaseBindAsyncWithParameterInfo<T>
- {
- public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- return new(default(T));
- }
- }
+ // Using wrong T on purpose
+ private class BindAsyncWrongTypeInherit : BaseBindAsync<InheritBindAsync>
+ {
+ }
- private class InheritBindAsyncWithParameterInfo : BaseBindAsyncWithParameterInfo<InheritBindAsyncWithParameterInfo>
+ private class BaseBindAsyncWithParameterInfo<T>
+ {
+ public static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
{
+ return new(default(T));
}
+ }
- // Using wrong T on purpose
- private class BindAsyncWithParameterInfoWrongTypeInherit : BaseBindAsyncWithParameterInfo<InheritBindAsync>
- {
- }
+ private class InheritBindAsyncWithParameterInfo : BaseBindAsyncWithParameterInfo<InheritBindAsyncWithParameterInfo>
+ {
+ }
- private interface IBindAsync<T>
- {
- static ValueTask<T?> BindAsync(HttpContext context)
- {
- return new(default(T));
- }
- }
+ // Using wrong T on purpose
+ private class BindAsyncWithParameterInfoWrongTypeInherit : BaseBindAsyncWithParameterInfo<InheritBindAsync>
+ {
+ }
- private interface IBindAsync2<T>
+ private interface IBindAsync<T>
+ {
+ static ValueTask<T?> BindAsync(HttpContext context)
{
- static ValueTask<T?> BindAsync(HttpContext context)
- {
- return new(default(T));
- }
+ return new(default(T));
}
+ }
- private interface IImeplmentIBindAsync<T> : IBindAsync<T>
+ private interface IBindAsync2<T>
+ {
+ static ValueTask<T?> BindAsync(HttpContext context)
{
+ return new(default(T));
}
+ }
- private class BindAsyncFromInterface : IBindAsync<BindAsyncFromInterface>
- {
- }
+ private interface IImeplmentIBindAsync<T> : IBindAsync<T>
+ {
+ }
- private class BindAsyncFromGrandparentInterface : IImeplmentIBindAsync<BindAsyncFromGrandparentInterface>
- {
- }
+ private class BindAsyncFromInterface : IBindAsync<BindAsyncFromInterface>
+ {
+ }
- private class BindAsyncDirectlyAndFromInterface : IBindAsync<BindAsyncDirectlyAndFromInterface>
- {
- static ValueTask<BindAsyncFromInterface?> BindAsync(HttpContext context)
- {
- return new(result: null);
- }
- }
+ private class BindAsyncFromGrandparentInterface : IImeplmentIBindAsync<BindAsyncFromGrandparentInterface>
+ {
+ }
- private class BindAsyncFromClassAndInterface
- : BaseBindAsync<BindAsyncFromClassAndInterface>,
- IBindAsync<BindAsyncFromClassAndInterface>
+ private class BindAsyncDirectlyAndFromInterface : IBindAsync<BindAsyncDirectlyAndFromInterface>
+ {
+ static ValueTask<BindAsyncFromInterface?> BindAsync(HttpContext context)
{
+ return new(result: null);
}
+ }
- private class BindAsyncFromMultipleInterfaces
- : IBindAsync<BindAsyncFromMultipleInterfaces>,
- IBindAsync2<BindAsyncFromMultipleInterfaces>
- {
- }
+ private class BindAsyncFromClassAndInterface
+ : BaseBindAsync<BindAsyncFromClassAndInterface>,
+ IBindAsync<BindAsyncFromClassAndInterface>
+ {
+ }
- // using wrong T on purpose
- private class BindAsyncWrongTypeFromInterface : IBindAsync<BindAsyncFromInterface>
- {
- }
+ private class BindAsyncFromMultipleInterfaces
+ : IBindAsync<BindAsyncFromMultipleInterfaces>,
+ IBindAsync2<BindAsyncFromMultipleInterfaces>
+ {
+ }
- private interface IBindAsyncWithParameterInfo<T>
- {
- static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- return new(default(T));
- }
- }
+ // using wrong T on purpose
+ private class BindAsyncWrongTypeFromInterface : IBindAsync<BindAsyncFromInterface>
+ {
+ }
- private class BindAsyncFromInterfaceWithParameterInfo : IBindAsync<BindAsyncFromInterfaceWithParameterInfo>
+ private interface IBindAsyncWithParameterInfo<T>
+ {
+ static ValueTask<T?> BindAsync(HttpContext context, ParameterInfo parameter)
{
+ return new(default(T));
}
+ }
- private class BindAsyncFallsBack
- {
- public static void BindAsync(HttpContext context, ParameterInfo parameter)
- => throw new NotImplementedException();
+ private class BindAsyncFromInterfaceWithParameterInfo : IBindAsync<BindAsyncFromInterfaceWithParameterInfo>
+ {
+ }
- public static ValueTask<BindAsyncFallsBack?> BindAsync(HttpContext context)
- {
- return new(result: null);
- }
- }
+ private class BindAsyncFallsBack
+ {
+ public static void BindAsync(HttpContext context, ParameterInfo parameter)
+ => throw new NotImplementedException();
- private class BindAsyncBadMethod : IBindAsyncWithParameterInfo<BindAsyncBadMethod>
+ public static ValueTask<BindAsyncFallsBack?> BindAsync(HttpContext context)
{
- public static void BindAsync(HttpContext context, ParameterInfo parameter)
- => throw new NotImplementedException();
+ return new(result: null);
}
+ }
- private class BindAsyncBothBadMethods
- {
- public static void BindAsync(HttpContext context, ParameterInfo parameter)
- => throw new NotImplementedException();
+ private class BindAsyncBadMethod : IBindAsyncWithParameterInfo<BindAsyncBadMethod>
+ {
+ public static void BindAsync(HttpContext context, ParameterInfo parameter)
+ => throw new NotImplementedException();
+ }
- public static void BindAsync(HttpContext context)
- => throw new NotImplementedException();
- }
+ private class BindAsyncBothBadMethods
+ {
+ public static void BindAsync(HttpContext context, ParameterInfo parameter)
+ => throw new NotImplementedException();
+
+ public static void BindAsync(HttpContext context)
+ => throw new NotImplementedException();
+ }
- private class MockParameterInfo : ParameterInfo
+ private class MockParameterInfo : ParameterInfo
+ {
+ public MockParameterInfo(Type type, string name)
{
- public MockParameterInfo(Type type, string name)
- {
- ClassImpl = type;
- NameImpl = name;
- }
+ ClassImpl = type;
+ NameImpl = name;
}
}
}
diff --git a/src/Http/Http.Extensions/test/ProblemDetailsJsonConverterTest.cs b/src/Http/Http.Extensions/test/ProblemDetailsJsonConverterTest.cs
index 94feff570e..76b0c99b85 100644
--- a/src/Http/Http.Extensions/test/ProblemDetailsJsonConverterTest.cs
+++ b/src/Http/Http.Extensions/test/ProblemDetailsJsonConverterTest.cs
@@ -8,173 +8,172 @@ using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Mvc;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+public class ProblemDetailsJsonConverterTest
{
- public class ProblemDetailsJsonConverterTest
+ private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().SerializerOptions;
+
+ [Fact]
+ public void Read_ThrowsIfJsonIsIncomplete()
{
- private static JsonSerializerOptions JsonSerializerOptions => new JsonOptions().SerializerOptions;
+ // Arrange
+ var json = "{";
+ var converter = new ProblemDetailsJsonConverter();
- [Fact]
- public void Read_ThrowsIfJsonIsIncomplete()
+ // Act & Assert
+ var ex = Record.Exception(() =>
{
- // Arrange
- var json = "{";
- var converter = new ProblemDetailsJsonConverter();
+ var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
+ converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
+ });
+ Assert.IsAssignableFrom<JsonException>(ex);
+ }
- // Act & Assert
- var ex = Record.Exception(() =>
+ [Fact]
+ public void Read_Works()
+ {
+ // Arrange
+ var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
+ var title = "Not found";
+ var status = 404;
+ var detail = "Product not found";
+ var instance = "http://example.com/products/14";
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}";
+ var converter = new ProblemDetailsJsonConverter();
+ var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
+ reader.Read();
+
+ // Act
+ var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
+
+ Assert.Equal(type, problemDetails.Type);
+ Assert.Equal(title, problemDetails.Title);
+ Assert.Equal(status, problemDetails.Status);
+ Assert.Equal(instance, problemDetails.Instance);
+ Assert.Equal(detail, problemDetails.Detail);
+ Assert.Collection(
+ problemDetails.Extensions,
+ kvp =>
{
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
+ Assert.Equal("traceId", kvp.Key);
+ Assert.Equal(traceId, kvp.Value.ToString());
});
- Assert.IsAssignableFrom<JsonException>(ex);
- }
-
- [Fact]
- public void Read_Works()
- {
- // Arrange
- var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
- var title = "Not found";
- var status = 404;
- var detail = "Product not found";
- var instance = "http://example.com/products/14";
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}";
- var converter = new ProblemDetailsJsonConverter();
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- reader.Read();
-
- // Act
- var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
-
- Assert.Equal(type, problemDetails.Type);
- Assert.Equal(title, problemDetails.Title);
- Assert.Equal(status, problemDetails.Status);
- Assert.Equal(instance, problemDetails.Instance);
- Assert.Equal(detail, problemDetails.Detail);
- Assert.Collection(
- problemDetails.Extensions,
- kvp =>
- {
- Assert.Equal("traceId", kvp.Key);
- Assert.Equal(traceId, kvp.Value.ToString());
- });
- }
-
- [Fact]
- public void Read_UsingJsonSerializerWorks()
- {
- // Arrange
- var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
- var title = "Not found";
- var status = 404;
- var detail = "Product not found";
- var instance = "http://example.com/products/14";
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}";
-
- // Act
- var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(json, JsonSerializerOptions);
-
- Assert.Equal(type, problemDetails.Type);
- Assert.Equal(title, problemDetails.Title);
- Assert.Equal(status, problemDetails.Status);
- Assert.Equal(instance, problemDetails.Instance);
- Assert.Equal(detail, problemDetails.Detail);
- Assert.Collection(
- problemDetails.Extensions,
- kvp =>
- {
- Assert.Equal("traceId", kvp.Key);
- Assert.Equal(traceId, kvp.Value.ToString());
- });
- }
-
- [Fact]
- public void Read_WithSomeMissingValues_Works()
- {
- // Arrange
- var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
- var title = "Not found";
- var status = 404;
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"}}";
- var converter = new ProblemDetailsJsonConverter();
- var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
- reader.Read();
+ }
- // Act
- var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
+ [Fact]
+ public void Read_UsingJsonSerializerWorks()
+ {
+ // Arrange
+ var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
+ var title = "Not found";
+ var status = 404;
+ var detail = "Product not found";
+ var instance = "http://example.com/products/14";
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"detail\":\"{detail}\", \"instance\":\"{instance}\",\"traceId\":\"{traceId}\"}}";
+
+ // Act
+ var problemDetails = JsonSerializer.Deserialize<ProblemDetails>(json, JsonSerializerOptions);
+
+ Assert.Equal(type, problemDetails.Type);
+ Assert.Equal(title, problemDetails.Title);
+ Assert.Equal(status, problemDetails.Status);
+ Assert.Equal(instance, problemDetails.Instance);
+ Assert.Equal(detail, problemDetails.Detail);
+ Assert.Collection(
+ problemDetails.Extensions,
+ kvp =>
+ {
+ Assert.Equal("traceId", kvp.Key);
+ Assert.Equal(traceId, kvp.Value.ToString());
+ });
+ }
- Assert.Equal(type, problemDetails.Type);
- Assert.Equal(title, problemDetails.Title);
- Assert.Equal(status, problemDetails.Status);
- Assert.Collection(
- problemDetails.Extensions,
- kvp =>
- {
- Assert.Equal("traceId", kvp.Key);
- Assert.Equal(traceId, kvp.Value.ToString());
- });
- }
+ [Fact]
+ public void Read_WithSomeMissingValues_Works()
+ {
+ // Arrange
+ var type = "https://tools.ietf.org/html/rfc7231#section-6.5.4";
+ var title = "Not found";
+ var status = 404;
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var json = $"{{\"type\":\"{type}\",\"title\":\"{title}\",\"status\":{status},\"traceId\":\"{traceId}\"}}";
+ var converter = new ProblemDetailsJsonConverter();
+ var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json));
+ reader.Read();
+
+ // Act
+ var problemDetails = converter.Read(ref reader, typeof(ProblemDetails), JsonSerializerOptions);
+
+ Assert.Equal(type, problemDetails.Type);
+ Assert.Equal(title, problemDetails.Title);
+ Assert.Equal(status, problemDetails.Status);
+ Assert.Collection(
+ problemDetails.Extensions,
+ kvp =>
+ {
+ Assert.Equal("traceId", kvp.Key);
+ Assert.Equal(traceId, kvp.Value.ToString());
+ });
+ }
- [Fact]
- public void Write_Works()
+ [Fact]
+ public void Write_Works()
+ {
+ // Arrange
+ var traceId = "|37dd3dd5-4a9619f953c40a16.";
+ var value = new ProblemDetails
{
- // Arrange
- var traceId = "|37dd3dd5-4a9619f953c40a16.";
- var value = new ProblemDetails
- {
- Title = "Not found",
- Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
- Status = 404,
- Detail = "Product not found",
- Instance = "http://example.com/products/14",
- Extensions =
+ Title = "Not found",
+ Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
+ Status = 404,
+ Detail = "Product not found",
+ Instance = "http://example.com/products/14",
+ Extensions =
{
{ "traceId", traceId },
{ "some-data", new[] { "value1", "value2" } }
}
- };
- var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status},\"detail\":\"{value.Detail}\",\"instance\":\"{JsonEncodedText.Encode(value.Instance)}\",\"traceId\":\"{traceId}\",\"some-data\":[\"value1\",\"value2\"]}}";
- var converter = new ProblemDetailsJsonConverter();
- var stream = new MemoryStream();
+ };
+ var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status},\"detail\":\"{value.Detail}\",\"instance\":\"{JsonEncodedText.Encode(value.Instance)}\",\"traceId\":\"{traceId}\",\"some-data\":[\"value1\",\"value2\"]}}";
+ var converter = new ProblemDetailsJsonConverter();
+ var stream = new MemoryStream();
- // Act
- using (var writer = new Utf8JsonWriter(stream))
- {
- converter.Write(writer, value, JsonSerializerOptions);
- }
-
- // Assert
- var actual = Encoding.UTF8.GetString(stream.ToArray());
- Assert.Equal(expected, actual);
+ // Act
+ using (var writer = new Utf8JsonWriter(stream))
+ {
+ converter.Write(writer, value, JsonSerializerOptions);
}
- [Fact]
- public void Write_WithSomeMissingContent_Works()
- {
- // Arrange
- var value = new ProblemDetails
- {
- Title = "Not found",
- Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
- Status = 404,
- };
- var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status}}}";
- var converter = new ProblemDetailsJsonConverter();
- var stream = new MemoryStream();
-
- // Act
- using (var writer = new Utf8JsonWriter(stream))
- {
- converter.Write(writer, value, JsonSerializerOptions);
- }
+ // Assert
+ var actual = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal(expected, actual);
+ }
- // Assert
- var actual = Encoding.UTF8.GetString(stream.ToArray());
- Assert.Equal(expected, actual);
+ [Fact]
+ public void Write_WithSomeMissingContent_Works()
+ {
+ // Arrange
+ var value = new ProblemDetails
+ {
+ Title = "Not found",
+ Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
+ Status = 404,
+ };
+ var expected = $"{{\"type\":\"{JsonEncodedText.Encode(value.Type)}\",\"title\":\"{value.Title}\",\"status\":{value.Status}}}";
+ var converter = new ProblemDetailsJsonConverter();
+ var stream = new MemoryStream();
+
+ // Act
+ using (var writer = new Utf8JsonWriter(stream))
+ {
+ converter.Write(writer, value, JsonSerializerOptions);
}
+
+ // Assert
+ var actual = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Http.Extensions/test/QueryBuilderTests.cs b/src/Http/Http.Extensions/test/QueryBuilderTests.cs
index 3b00142adb..a12448283c 100644
--- a/src/Http/Http.Extensions/test/QueryBuilderTests.cs
+++ b/src/Http/Http.Extensions/test/QueryBuilderTests.cs
@@ -6,106 +6,105 @@ using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+public class QueryBuilderTests
{
- public class QueryBuilderTests
+ [Fact]
+ public void EmptyQuery_NoQuestionMark()
{
- [Fact]
- public void EmptyQuery_NoQuestionMark()
- {
- var builder = new QueryBuilder();
- Assert.Equal(string.Empty, builder.ToString());
- }
+ 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 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 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 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 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 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()
+ [Fact]
+ public void AddMultipleValuesViaConstructor_AddedInOrder()
+ {
+ var builder = new QueryBuilder(new[]
{
- 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());
- }
+ Assert.Equal("?key1=value1&key2=value2&key3=value3", builder.ToString());
+ }
- [Fact]
- public void AddMultipleValuesViaConstructor_WithStringValues()
+ [Fact]
+ public void AddMultipleValuesViaConstructor_WithStringValues()
+ {
+ var builder = new QueryBuilder(new[]
{
- var builder = new QueryBuilder(new[]
- {
new KeyValuePair<string, StringValues>("key1", new StringValues(new [] { "value1", string.Empty, "value3" })),
new KeyValuePair<string, StringValues>("key2", string.Empty),
new KeyValuePair<string, StringValues>("key3", StringValues.Empty)
});
- Assert.Equal("?key1=value1&key1=&key1=value3&key2=", builder.ToString());
- }
+ Assert.Equal("?key1=value1&key1=&key1=value3&key2=", builder.ToString());
+ }
- [Fact]
- public void AddMultipleValuesViaInitializer_AddedInOrder()
- {
- var builder = new QueryBuilder()
+ [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());
- }
+ Assert.Equal("?key1=value1&key2=value2&key3=value3", builder.ToString());
+ }
- [Fact]
- public void CopyViaConstructor_AddedInOrder()
- {
- var builder = new QueryBuilder()
+ [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());
- }
+ var builder1 = new QueryBuilder(builder);
+ Assert.Equal("?key1=value1&key2=value2&key3=value3", builder1.ToString());
}
}
diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
index c3c9ec733e..7640eb1c55 100644
--- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
+++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs
@@ -26,54 +26,54 @@ using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Moq;
-namespace Microsoft.AspNetCore.Routing.Internal
+namespace Microsoft.AspNetCore.Routing.Internal;
+
+public class RequestDelegateFactoryTests : LoggedTest
{
- public class RequestDelegateFactoryTests : LoggedTest
+ public static IEnumerable<object[]> NoResult
{
- public static IEnumerable<object[]> NoResult
+ get
{
- get
+ void TestAction(HttpContext httpContext)
{
- void TestAction(HttpContext httpContext)
- {
- MarkAsInvoked(httpContext);
- }
+ MarkAsInvoked(httpContext);
+ }
- Task TaskTestAction(HttpContext httpContext)
- {
- MarkAsInvoked(httpContext);
- return Task.CompletedTask;
- }
+ Task TaskTestAction(HttpContext httpContext)
+ {
+ MarkAsInvoked(httpContext);
+ return Task.CompletedTask;
+ }
- ValueTask ValueTaskTestAction(HttpContext httpContext)
- {
- MarkAsInvoked(httpContext);
- return ValueTask.CompletedTask;
- }
+ ValueTask ValueTaskTestAction(HttpContext httpContext)
+ {
+ MarkAsInvoked(httpContext);
+ return ValueTask.CompletedTask;
+ }
- void StaticTestAction(HttpContext httpContext)
- {
- MarkAsInvoked(httpContext);
- }
+ void StaticTestAction(HttpContext httpContext)
+ {
+ MarkAsInvoked(httpContext);
+ }
- Task StaticTaskTestAction(HttpContext httpContext)
- {
- MarkAsInvoked(httpContext);
- return Task.CompletedTask;
- }
+ Task StaticTaskTestAction(HttpContext httpContext)
+ {
+ MarkAsInvoked(httpContext);
+ return Task.CompletedTask;
+ }
- ValueTask StaticValueTaskTestAction(HttpContext httpContext)
- {
- MarkAsInvoked(httpContext);
- return ValueTask.CompletedTask;
- }
+ ValueTask StaticValueTaskTestAction(HttpContext httpContext)
+ {
+ MarkAsInvoked(httpContext);
+ return ValueTask.CompletedTask;
+ }
- void MarkAsInvoked(HttpContext httpContext)
- {
- httpContext.Items.Add("invoked", true);
- }
+ void MarkAsInvoked(HttpContext httpContext)
+ {
+ httpContext.Items.Add("invoked", true);
+ }
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Action<HttpContext>)TestAction },
new object[] { (Func<HttpContext, Task>)TaskTestAction },
@@ -82,385 +82,385 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<HttpContext, Task>)StaticTaskTestAction },
new object[] { (Func<HttpContext, ValueTask>)StaticValueTaskTestAction },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(NoResult))]
- public async Task RequestDelegateInvokesAction(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
+ [Theory]
+ [MemberData(nameof(NoResult))]
+ public async Task RequestDelegateInvokesAction(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.True(httpContext.Items["invoked"] as bool?);
- }
+ Assert.True(httpContext.Items["invoked"] as bool?);
+ }
- private static void StaticTestActionBasicReflection(HttpContext httpContext)
- {
- httpContext.Items.Add("invoked", true);
- }
+ private static void StaticTestActionBasicReflection(HttpContext httpContext)
+ {
+ httpContext.Items.Add("invoked", true);
+ }
- [Fact]
- public async Task StaticMethodInfoOverloadWorksWithBasicReflection()
- {
- var methodInfo = typeof(RequestDelegateFactoryTests).GetMethod(
- nameof(StaticTestActionBasicReflection),
- BindingFlags.NonPublic | BindingFlags.Static,
- new[] { typeof(HttpContext) });
+ [Fact]
+ public async Task StaticMethodInfoOverloadWorksWithBasicReflection()
+ {
+ var methodInfo = typeof(RequestDelegateFactoryTests).GetMethod(
+ nameof(StaticTestActionBasicReflection),
+ BindingFlags.NonPublic | BindingFlags.Static,
+ new[] { typeof(HttpContext) });
- var factoryResult = RequestDelegateFactory.Create(methodInfo!);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(methodInfo!);
+ var requestDelegate = factoryResult.RequestDelegate;
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
+
+ Assert.True(httpContext.Items["invoked"] as bool?);
+ }
+
+ private class TestNonStaticActionClass
+ {
+ private readonly object _invokedValue;
- Assert.True(httpContext.Items["invoked"] as bool?);
+ public TestNonStaticActionClass(object invokedValue)
+ {
+ _invokedValue = invokedValue;
}
- private class TestNonStaticActionClass
+ public void NonStaticTestAction(HttpContext httpContext)
{
- private readonly object _invokedValue;
+ httpContext.Items.Add("invoked", _invokedValue);
+ }
+ }
- public TestNonStaticActionClass(object invokedValue)
- {
- _invokedValue = invokedValue;
- }
+ [Fact]
+ public async Task NonStaticMethodInfoOverloadWorksWithBasicReflection()
+ {
+ var methodInfo = typeof(TestNonStaticActionClass).GetMethod(
+ nameof(TestNonStaticActionClass.NonStaticTestAction),
+ BindingFlags.Public | BindingFlags.Instance,
+ new[] { typeof(HttpContext) });
+
+ var invoked = false;
- public void NonStaticTestAction(HttpContext httpContext)
+ object GetTarget()
+ {
+ if (!invoked)
{
- httpContext.Items.Add("invoked", _invokedValue);
+ invoked = true;
+ return new TestNonStaticActionClass(1);
}
+
+ return new TestNonStaticActionClass(2);
}
- [Fact]
- public async Task NonStaticMethodInfoOverloadWorksWithBasicReflection()
- {
- var methodInfo = typeof(TestNonStaticActionClass).GetMethod(
- nameof(TestNonStaticActionClass.NonStaticTestAction),
- BindingFlags.Public | BindingFlags.Instance,
- new[] { typeof(HttpContext) });
+ var factoryResult = RequestDelegateFactory.Create(methodInfo!, _ => GetTarget());
+ var requestDelegate = factoryResult.RequestDelegate;
- var invoked = false;
+ var httpContext = CreateHttpContext();
- object GetTarget()
- {
- if (!invoked)
- {
- invoked = true;
- return new TestNonStaticActionClass(1);
- }
+ await requestDelegate(httpContext);
- return new TestNonStaticActionClass(2);
- }
+ Assert.Equal(1, httpContext.Items["invoked"]);
- var factoryResult = RequestDelegateFactory.Create(methodInfo!, _ => GetTarget());
- var requestDelegate = factoryResult.RequestDelegate;
+ httpContext = CreateHttpContext();
- var httpContext = CreateHttpContext();
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Equal(2, httpContext.Items["invoked"]);
+ }
- Assert.Equal(1, httpContext.Items["invoked"]);
+ [Fact]
+ public void BuildRequestDelegateThrowsArgumentNullExceptions()
+ {
+ var methodInfo = typeof(RequestDelegateFactoryTests).GetMethod(
+ nameof(StaticTestActionBasicReflection),
+ BindingFlags.NonPublic | BindingFlags.Static,
+ new[] { typeof(HttpContext) });
- httpContext = CreateHttpContext();
+ var serviceProvider = new EmptyServiceProvider();
- await requestDelegate(httpContext);
+ var exNullAction = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(handler: null!));
+ var exNullMethodInfo1 = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(methodInfo: null!));
- Assert.Equal(2, httpContext.Items["invoked"]);
- }
+ Assert.Equal("handler", exNullAction.ParamName);
+ Assert.Equal("methodInfo", exNullMethodInfo1.ParamName);
+ }
- [Fact]
- public void BuildRequestDelegateThrowsArgumentNullExceptions()
- {
- var methodInfo = typeof(RequestDelegateFactoryTests).GetMethod(
- nameof(StaticTestActionBasicReflection),
- BindingFlags.NonPublic | BindingFlags.Static,
- new[] { typeof(HttpContext) });
+ [Fact]
+ public async Task RequestDelegatePopulatesFromRouteParameterBasedOnParameterName()
+ {
+ const string paramName = "value";
+ const int originalRouteParam = 42;
- var serviceProvider = new EmptyServiceProvider();
+ static void TestAction(HttpContext httpContext, [FromRoute] int value)
+ {
+ httpContext.Items.Add("input", value);
+ }
- var exNullAction = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(handler: null!));
- var exNullMethodInfo1 = Assert.Throws<ArgumentNullException>(() => RequestDelegateFactory.Create(methodInfo: null!));
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues[paramName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
- Assert.Equal("handler", exNullAction.ParamName);
- Assert.Equal("methodInfo", exNullMethodInfo1.ParamName);
- }
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- [Fact]
- public async Task RequestDelegatePopulatesFromRouteParameterBasedOnParameterName()
- {
- const string paramName = "value";
- const int originalRouteParam = 42;
+ await requestDelegate(httpContext);
- static void TestAction(HttpContext httpContext, [FromRoute] int value)
- {
- httpContext.Items.Add("input", value);
- }
+ Assert.Equal(originalRouteParam, httpContext.Items["input"]);
+ }
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues[paramName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
+ private static void TestOptional(HttpContext httpContext, [FromRoute] int value = 42)
+ {
+ httpContext.Items.Add("input", value);
+ }
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ private static void TestOptionalNullable(HttpContext httpContext, int? value = 42)
+ {
+ httpContext.Items.Add("input", value);
+ }
- await requestDelegate(httpContext);
+ private static void TestOptionalString(HttpContext httpContext, string value = "default")
+ {
+ httpContext.Items.Add("input", value);
+ }
- Assert.Equal(originalRouteParam, httpContext.Items["input"]);
- }
+ [Fact]
+ public async Task SpecifiedRouteParametersDoNotFallbackToQueryString()
+ {
+ var httpContext = CreateHttpContext();
- private static void TestOptional(HttpContext httpContext, [FromRoute] int value = 42)
+ var factoryResult = RequestDelegateFactory.Create((int? id, HttpContext httpContext) =>
{
- httpContext.Items.Add("input", value);
- }
+ if (id is not null)
+ {
+ httpContext.Items["input"] = id;
+ }
+ },
+ new() { RouteParameterNames = new string[] { "id" } });
- private static void TestOptionalNullable(HttpContext httpContext, int? value = 42)
- {
- httpContext.Items.Add("input", value);
- }
+ var requestDelegate = factoryResult.RequestDelegate;
- private static void TestOptionalString(HttpContext httpContext, string value = "default")
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- httpContext.Items.Add("input", value);
- }
+ ["id"] = "42"
+ });
- [Fact]
- public async Task SpecifiedRouteParametersDoNotFallbackToQueryString()
- {
- var httpContext = CreateHttpContext();
+ await requestDelegate(httpContext);
- var factoryResult = RequestDelegateFactory.Create((int? id, HttpContext httpContext) =>
- {
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- },
- new() { RouteParameterNames = new string[] { "id" } });
+ Assert.Null(httpContext.Items["input"]);
+ }
- var requestDelegate = factoryResult.RequestDelegate;
+ [Fact]
+ public async Task SpecifiedQueryParametersDoNotFallbackToRouteValues()
+ {
+ var httpContext = CreateHttpContext();
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ var factoryResult = RequestDelegateFactory.Create((int? id, HttpContext httpContext) =>
+ {
+ if (id is not null)
{
- ["id"] = "42"
- });
-
- await requestDelegate(httpContext);
-
- Assert.Null(httpContext.Items["input"]);
- }
+ httpContext.Items["input"] = id;
+ }
+ },
+ new() { RouteParameterNames = new string[] { } });
- [Fact]
- public async Task SpecifiedQueryParametersDoNotFallbackToRouteValues()
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- var httpContext = CreateHttpContext();
+ ["id"] = "41"
+ });
+ httpContext.Request.RouteValues = new()
+ {
+ ["id"] = "42"
+ };
- var factoryResult = RequestDelegateFactory.Create((int? id, HttpContext httpContext) =>
- {
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- },
- new() { RouteParameterNames = new string[] { } });
+ var requestDelegate = factoryResult.RequestDelegate;
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["id"] = "41"
- });
- httpContext.Request.RouteValues = new()
- {
- ["id"] = "42"
- };
+ await requestDelegate(httpContext);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal(41, httpContext.Items["input"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task NullRouteParametersPrefersRouteOverQueryString()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(41, httpContext.Items["input"]);
- }
+ var factoryResult = RequestDelegateFactory.Create((int? id, HttpContext httpContext) =>
+ {
+ if (id is not null)
+ {
+ httpContext.Items["input"] = id;
+ }
+ },
+ new() { RouteParameterNames = null });
- [Fact]
- public async Task NullRouteParametersPrefersRouteOverQueryString()
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["id"] = "41"
+ });
+ httpContext.Request.RouteValues = new()
{
- var httpContext = CreateHttpContext();
+ ["id"] = "42"
+ };
- var factoryResult = RequestDelegateFactory.Create((int? id, HttpContext httpContext) =>
- {
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- },
- new() { RouteParameterNames = null });
+ var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["id"] = "41"
- });
- httpContext.Request.RouteValues = new()
- {
- ["id"] = "42"
- };
+ Assert.Equal(42, httpContext.Items["input"]);
+ }
- var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task CreatingDelegateWithInstanceMethodInfoCreatesInstancePerCall()
+ {
+ var methodInfo = typeof(HttpHandler).GetMethod(nameof(HttpHandler.Handle));
- Assert.Equal(42, httpContext.Items["input"]);
- }
+ Assert.NotNull(methodInfo);
- [Fact]
- public async Task CreatingDelegateWithInstanceMethodInfoCreatesInstancePerCall()
- {
- var methodInfo = typeof(HttpHandler).GetMethod(nameof(HttpHandler.Handle));
+ var factoryResult = RequestDelegateFactory.Create(methodInfo!);
+ var requestDelegate = factoryResult.RequestDelegate;
- Assert.NotNull(methodInfo);
+ var context = CreateHttpContext();
- var factoryResult = RequestDelegateFactory.Create(methodInfo!);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(context);
- var context = CreateHttpContext();
+ Assert.Equal(1, context.Items["calls"]);
- await requestDelegate(context);
+ await requestDelegate(context);
- Assert.Equal(1, context.Items["calls"]);
+ Assert.Equal(1, context.Items["calls"]);
+ }
- await requestDelegate(context);
+ [Fact]
+ public void SpecifiedEmptyRouteParametersThrowIfRouteParameterDoesNotExist()
+ {
+ var ex = Assert.Throws<InvalidOperationException>(() =>
+ RequestDelegateFactory.Create(([FromRoute] int id) => { }, new() { RouteParameterNames = Array.Empty<string>() }));
- Assert.Equal(1, context.Items["calls"]);
- }
+ Assert.Equal("'id' is not a route parameter.", ex.Message);
+ }
- [Fact]
- public void SpecifiedEmptyRouteParametersThrowIfRouteParameterDoesNotExist()
- {
- var ex = Assert.Throws<InvalidOperationException>(() =>
- RequestDelegateFactory.Create(([FromRoute] int id) => { }, new() { RouteParameterNames = Array.Empty<string>() }));
+ [Fact]
+ public async Task RequestDelegatePopulatesFromRouteOptionalParameter()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal("'id' is not a route parameter.", ex.Message);
- }
+ var factoryResult = RequestDelegateFactory.Create(TestOptional);
+ var requestDelegate = factoryResult.RequestDelegate;
- [Fact]
- public async Task RequestDelegatePopulatesFromRouteOptionalParameter()
- {
- var httpContext = CreateHttpContext();
+ await requestDelegate(httpContext);
- var factoryResult = RequestDelegateFactory.Create(TestOptional);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal(42, httpContext.Items["input"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegatePopulatesFromNullableOptionalParameter()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(42, httpContext.Items["input"]);
- }
+ var factoryResult = RequestDelegateFactory.Create(TestOptional);
+ var requestDelegate = factoryResult.RequestDelegate;
- [Fact]
- public async Task RequestDelegatePopulatesFromNullableOptionalParameter()
- {
- var httpContext = CreateHttpContext();
+ await requestDelegate(httpContext);
- var factoryResult = RequestDelegateFactory.Create(TestOptional);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal(42, httpContext.Items["input"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegatePopulatesFromOptionalStringParameter()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(42, httpContext.Items["input"]);
- }
+ var factoryResult = RequestDelegateFactory.Create(TestOptionalString);
+ var requestDelegate = factoryResult.RequestDelegate;
- [Fact]
- public async Task RequestDelegatePopulatesFromOptionalStringParameter()
- {
- var httpContext = CreateHttpContext();
+ await requestDelegate(httpContext);
- var factoryResult = RequestDelegateFactory.Create(TestOptionalString);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal("default", httpContext.Items["input"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegatePopulatesFromRouteOptionalParameterBasedOnParameterName()
+ {
+ const string paramName = "value";
+ const int originalRouteParam = 47;
- Assert.Equal("default", httpContext.Items["input"]);
- }
+ var httpContext = CreateHttpContext();
- [Fact]
- public async Task RequestDelegatePopulatesFromRouteOptionalParameterBasedOnParameterName()
- {
- const string paramName = "value";
- const int originalRouteParam = 47;
+ httpContext.Request.RouteValues[paramName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
- var httpContext = CreateHttpContext();
+ var factoryResult = RequestDelegateFactory.Create(TestOptional);
+ var requestDelegate = factoryResult.RequestDelegate;
- httpContext.Request.RouteValues[paramName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
+ await requestDelegate(httpContext);
- var factoryResult = RequestDelegateFactory.Create(TestOptional);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal(47, httpContext.Items["input"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegatePopulatesFromRouteParameterBasedOnAttributeNameProperty()
+ {
+ const string specifiedName = "value";
+ const int originalRouteParam = 42;
- Assert.Equal(47, httpContext.Items["input"]);
- }
+ int? deserializedRouteParam = null;
- [Fact]
- public async Task RequestDelegatePopulatesFromRouteParameterBasedOnAttributeNameProperty()
+ void TestAction([FromRoute(Name = specifiedName)] int foo)
{
- const string specifiedName = "value";
- const int originalRouteParam = 42;
+ deserializedRouteParam = foo;
+ }
- int? deserializedRouteParam = null;
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues[specifiedName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
- void TestAction([FromRoute(Name = specifiedName)] int foo)
- {
- deserializedRouteParam = foo;
- }
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues[specifiedName] = originalRouteParam.ToString(NumberFormatInfo.InvariantInfo);
+ await requestDelegate(httpContext);
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal(originalRouteParam, deserializedRouteParam);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task Returns400IfNoMatchingRouteValueForRequiredParam()
+ {
+ const string unmatchedName = "value";
+ const int unmatchedRouteParam = 42;
- Assert.Equal(originalRouteParam, deserializedRouteParam);
- }
+ int? deserializedRouteParam = null;
- [Fact]
- public async Task Returns400IfNoMatchingRouteValueForRequiredParam()
+ void TestAction([FromRoute] int foo)
{
- const string unmatchedName = "value";
- const int unmatchedRouteParam = 42;
-
- int? deserializedRouteParam = null;
-
- void TestAction([FromRoute] int foo)
- {
- deserializedRouteParam = foo;
- }
+ deserializedRouteParam = foo;
+ }
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues[unmatchedName] = unmatchedRouteParam.ToString(NumberFormatInfo.InvariantInfo);
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues[unmatchedName] = unmatchedRouteParam.ToString(NumberFormatInfo.InvariantInfo);
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(400, httpContext.Response.StatusCode);
- }
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ }
- public static object?[][] TryParsableParameters
+ public static object?[][] TryParsableParameters
+ {
+ get
{
- get
+ static void Store<T>(HttpContext httpContext, T tryParsable)
{
- static void Store<T>(HttpContext httpContext, T tryParsable)
- {
- httpContext.Items["tryParsable"] = tryParsable;
- }
+ httpContext.Items["tryParsable"] = tryParsable;
+ }
- var now = DateTime.Now;
+ var now = DateTime.Now;
- return new[]
- {
+ return new[]
+ {
// string is not technically "TryParsable", but it's the special case.
new object[] { (Action<HttpContext, string>)Store, "plain string", "plain string" },
new object[] { (Action<HttpContext, int>)Store, "-42", -42 },
@@ -492,1453 +492,1453 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Action<HttpContext, MyTryParseRecord>)Store, "https://example.org", new MyTryParseRecord(new Uri("https://example.org")) },
new object?[] { (Action<HttpContext, int?>)Store, null, null },
};
- }
}
+ }
- private enum MyEnum { ValueA, ValueB, }
+ private enum MyEnum { ValueA, ValueB, }
- private record MyTryParseRecord(Uri Uri)
+ private record MyTryParseRecord(Uri Uri)
+ {
+ public static bool TryParse(string? value, out MyTryParseRecord? result)
{
- public static bool TryParse(string? value, out MyTryParseRecord? result)
+ if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
{
- if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
- {
- result = null;
- return false;
- }
-
- result = new MyTryParseRecord(uri);
- return true;
+ result = null;
+ return false;
}
- }
- private class MyBindAsyncTypeThatThrows
- {
- public static ValueTask<MyBindAsyncTypeThatThrows?> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new InvalidOperationException("BindAsync failed");
+ result = new MyTryParseRecord(uri);
+ return true;
}
+ }
- private record MyBindAsyncRecord(Uri Uri)
- {
- public static ValueTask<MyBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.Equal(typeof(MyBindAsyncRecord), parameter.ParameterType);
- Assert.StartsWith("myBindAsyncRecord", parameter.Name);
+ private class MyBindAsyncTypeThatThrows
+ {
+ public static ValueTask<MyBindAsyncTypeThatThrows?> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new InvalidOperationException("BindAsync failed");
+ }
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- return new(result: null);
- }
+ private record MyBindAsyncRecord(Uri Uri)
+ {
+ public static ValueTask<MyBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
+ {
+ Assert.Equal(typeof(MyBindAsyncRecord), parameter.ParameterType);
+ Assert.StartsWith("myBindAsyncRecord", parameter.Name);
- return new(result: new(uri));
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ return new(result: null);
}
- // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's
- // no [FromRoute] or [FromQuery] attributes.
- public static bool TryParse(string? value, out MyBindAsyncRecord? result) =>
- throw new NotImplementedException();
+ return new(result: new(uri));
}
- private record struct MyNullableBindAsyncStruct(Uri Uri)
- {
- public static ValueTask<MyNullableBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.True(parameter.ParameterType == typeof(MyNullableBindAsyncStruct) || parameter.ParameterType == typeof(MyNullableBindAsyncStruct?));
- Assert.Equal("myNullableBindAsyncStruct", parameter.Name);
+ // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's
+ // no [FromRoute] or [FromQuery] attributes.
+ public static bool TryParse(string? value, out MyBindAsyncRecord? result) =>
+ throw new NotImplementedException();
+ }
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- return new(result: null);
- }
+ private record struct MyNullableBindAsyncStruct(Uri Uri)
+ {
+ public static ValueTask<MyNullableBindAsyncStruct?> BindAsync(HttpContext context, ParameterInfo parameter)
+ {
+ Assert.True(parameter.ParameterType == typeof(MyNullableBindAsyncStruct) || parameter.ParameterType == typeof(MyNullableBindAsyncStruct?));
+ Assert.Equal("myNullableBindAsyncStruct", parameter.Name);
- return new(result: new(uri));
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ return new(result: null);
}
+
+ return new(result: new(uri));
}
+ }
- private record struct MyBindAsyncStruct(Uri Uri)
+ private record struct MyBindAsyncStruct(Uri Uri)
+ {
+ public static ValueTask<MyBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
{
- public static ValueTask<MyBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.True(parameter.ParameterType == typeof(MyBindAsyncStruct) || parameter.ParameterType == typeof(MyBindAsyncStruct?));
- Assert.Equal("myBindAsyncStruct", parameter.Name);
+ Assert.True(parameter.ParameterType == typeof(MyBindAsyncStruct) || parameter.ParameterType == typeof(MyBindAsyncStruct?));
+ Assert.Equal("myBindAsyncStruct", parameter.Name);
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- throw new BadHttpRequestException("The request is missing the required Referer header.");
- }
-
- return new(result: new(uri));
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ throw new BadHttpRequestException("The request is missing the required Referer header.");
}
- // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's
- // no [FromRoute] or [FromQuery] attributes.
- public static bool TryParse(string? value, out MyBindAsyncStruct result) =>
- throw new NotImplementedException();
+ return new(result: new(uri));
}
- private record MyAwaitedBindAsyncRecord(Uri Uri)
- {
- public static async ValueTask<MyAwaitedBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.Equal(typeof(MyAwaitedBindAsyncRecord), parameter.ParameterType);
- Assert.StartsWith("myAwaitedBindAsyncRecord", parameter.Name);
+ // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's
+ // no [FromRoute] or [FromQuery] attributes.
+ public static bool TryParse(string? value, out MyBindAsyncStruct result) =>
+ throw new NotImplementedException();
+ }
- await Task.Yield();
+ private record MyAwaitedBindAsyncRecord(Uri Uri)
+ {
+ public static async ValueTask<MyAwaitedBindAsyncRecord?> BindAsync(HttpContext context, ParameterInfo parameter)
+ {
+ Assert.Equal(typeof(MyAwaitedBindAsyncRecord), parameter.ParameterType);
+ Assert.StartsWith("myAwaitedBindAsyncRecord", parameter.Name);
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- return null;
- }
+ await Task.Yield();
- return new(uri);
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ return null;
}
+
+ return new(uri);
}
+ }
- private record struct MyAwaitedBindAsyncStruct(Uri Uri)
+ private record struct MyAwaitedBindAsyncStruct(Uri Uri)
+ {
+ public static async ValueTask<MyAwaitedBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
{
- public static async ValueTask<MyAwaitedBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.Equal(typeof(MyAwaitedBindAsyncStruct), parameter.ParameterType);
- Assert.Equal("myAwaitedBindAsyncStruct", parameter.Name);
+ Assert.Equal(typeof(MyAwaitedBindAsyncStruct), parameter.ParameterType);
+ Assert.Equal("myAwaitedBindAsyncStruct", parameter.Name);
- await Task.Yield();
+ await Task.Yield();
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- throw new BadHttpRequestException("The request is missing the required Referer header.");
- }
-
- return new(uri);
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ throw new BadHttpRequestException("The request is missing the required Referer header.");
}
+
+ return new(uri);
}
+ }
- private record struct MyBothBindAsyncStruct(Uri Uri)
+ private record struct MyBothBindAsyncStruct(Uri Uri)
+ {
+ public static ValueTask<MyBothBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
{
- public static ValueTask<MyBothBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.True(parameter.ParameterType == typeof(MyBothBindAsyncStruct) || parameter.ParameterType == typeof(MyBothBindAsyncStruct?));
- Assert.Equal("myBothBindAsyncStruct", parameter.Name);
-
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- throw new BadHttpRequestException("The request is missing the required Referer header.");
- }
+ Assert.True(parameter.ParameterType == typeof(MyBothBindAsyncStruct) || parameter.ParameterType == typeof(MyBothBindAsyncStruct?));
+ Assert.Equal("myBothBindAsyncStruct", parameter.Name);
- return new(result: new(uri));
- }
-
- // BindAsync with ParameterInfo is preferred
- public static ValueTask<MyBothBindAsyncStruct> BindAsync(HttpContext context)
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
{
- throw new NotImplementedException();
+ throw new BadHttpRequestException("The request is missing the required Referer header.");
}
+
+ return new(result: new(uri));
}
- private record struct MySimpleBindAsyncStruct(Uri Uri)
+ // BindAsync with ParameterInfo is preferred
+ public static ValueTask<MyBothBindAsyncStruct> BindAsync(HttpContext context)
{
- public static ValueTask<MySimpleBindAsyncStruct> BindAsync(HttpContext context)
- {
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- throw new BadHttpRequestException("The request is missing the required Referer header.");
- }
+ throw new NotImplementedException();
+ }
+ }
- return new(result: new(uri));
+ private record struct MySimpleBindAsyncStruct(Uri Uri)
+ {
+ public static ValueTask<MySimpleBindAsyncStruct> BindAsync(HttpContext context)
+ {
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ throw new BadHttpRequestException("The request is missing the required Referer header.");
}
+
+ return new(result: new(uri));
}
+ }
- private record MySimpleBindAsyncRecord(Uri Uri)
+ private record MySimpleBindAsyncRecord(Uri Uri)
+ {
+ public static ValueTask<MySimpleBindAsyncRecord?> BindAsync(HttpContext context)
{
- public static ValueTask<MySimpleBindAsyncRecord?> BindAsync(HttpContext context)
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
{
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- return new(result: null);
- }
-
- return new(result: new(uri));
+ return new(result: null);
}
+
+ return new(result: new(uri));
}
+ }
- private interface IBindAsync<T>
+ private interface IBindAsync<T>
+ {
+ static ValueTask<T?> BindAsync(HttpContext context)
{
- static ValueTask<T?> BindAsync(HttpContext context)
+ if (typeof(T) != typeof(MyBindAsyncFromInterfaceRecord))
{
- if (typeof(T) != typeof(MyBindAsyncFromInterfaceRecord))
- {
- throw new InvalidOperationException();
- }
-
- if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
- {
- return new(default(T));
- }
+ throw new InvalidOperationException();
+ }
- return new(result: (T)(object)new MyBindAsyncFromInterfaceRecord(uri));
+ if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri))
+ {
+ return new(default(T));
}
- }
- private record MyBindAsyncFromInterfaceRecord(Uri uri) : IBindAsync<MyBindAsyncFromInterfaceRecord>
- {
+ return new(result: (T)(object)new MyBindAsyncFromInterfaceRecord(uri));
}
+ }
- [Theory]
- [MemberData(nameof(TryParsableParameters))]
- public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValue(Delegate action, string? routeValue, object? expectedParameterValue)
- {
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues["tryParsable"] = routeValue;
+ private record MyBindAsyncFromInterfaceRecord(Uri uri) : IBindAsync<MyBindAsyncFromInterfaceRecord>
+ {
+ }
- var factoryResult = RequestDelegateFactory.Create(action);
- var requestDelegate = factoryResult.RequestDelegate;
+ [Theory]
+ [MemberData(nameof(TryParsableParameters))]
+ public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValue(Delegate action, string? routeValue, object? expectedParameterValue)
+ {
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues["tryParsable"] = routeValue;
- await requestDelegate(httpContext);
+ var factoryResult = RequestDelegateFactory.Create(action);
+ var requestDelegate = factoryResult.RequestDelegate;
- Assert.Equal(expectedParameterValue, httpContext.Items["tryParsable"]);
- }
+ await requestDelegate(httpContext);
+
+ Assert.Equal(expectedParameterValue, httpContext.Items["tryParsable"]);
+ }
- [Theory]
- [MemberData(nameof(TryParsableParameters))]
- public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromQueryString(Delegate action, string? routeValue, object? expectedParameterValue)
+ [Theory]
+ [MemberData(nameof(TryParsableParameters))]
+ public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromQueryString(Delegate action, string? routeValue, object? expectedParameterValue)
+ {
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- var httpContext = CreateHttpContext();
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["tryParsable"] = routeValue
- });
+ ["tryParsable"] = routeValue
+ });
- var factoryResult = RequestDelegateFactory.Create(action);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(action);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(expectedParameterValue, httpContext.Items["tryParsable"]);
- }
+ Assert.Equal(expectedParameterValue, httpContext.Items["tryParsable"]);
+ }
+
+ [Fact]
+ public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValueBeforeQueryString()
+ {
+ var httpContext = CreateHttpContext();
+
+ httpContext.Request.RouteValues["tryParsable"] = "42";
- [Fact]
- public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromRouteValueBeforeQueryString()
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- var httpContext = CreateHttpContext();
+ ["tryParsable"] = "invalid!"
+ });
- httpContext.Request.RouteValues["tryParsable"] = "42";
+ var factoryResult = RequestDelegateFactory.Create((HttpContext httpContext, int tryParsable) =>
+ {
+ httpContext.Items["tryParsable"] = tryParsable;
+ });
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["tryParsable"] = "invalid!"
- });
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create((HttpContext httpContext, int tryParsable) =>
- {
- httpContext.Items["tryParsable"] = tryParsable;
- });
+ await requestDelegate(httpContext);
- var requestDelegate = factoryResult.RequestDelegate;
+ Assert.Equal(42, httpContext.Items["tryParsable"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegatePrefersBindAsyncOverTryParse()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(42, httpContext.Items["tryParsable"]);
- }
+ httpContext.Request.Headers.Referer = "https://example.org";
- [Fact]
- public async Task RequestDelegatePrefersBindAsyncOverTryParse()
+ var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncRecord myBindAsyncRecord) =>
{
- var httpContext = CreateHttpContext();
+ httpContext.Items["myBindAsyncRecord"] = myBindAsyncRecord;
+ });
- httpContext.Request.Headers.Referer = "https://example.org";
+ var requestDelegate = resultFactory.RequestDelegate;
- var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncRecord myBindAsyncRecord) =>
- {
- httpContext.Items["myBindAsyncRecord"] = myBindAsyncRecord;
- });
+ await requestDelegate(httpContext);
- var requestDelegate = resultFactory.RequestDelegate;
+ Assert.Equal(new MyBindAsyncRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncRecord"]);
+ }
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegatePrefersBindAsyncOverTryParseForNonNullableStruct()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(new MyBindAsyncRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncRecord"]);
- }
+ httpContext.Request.Headers.Referer = "https://example.org";
- [Fact]
- public async Task RequestDelegatePrefersBindAsyncOverTryParseForNonNullableStruct()
+ var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct myBindAsyncStruct) =>
{
- var httpContext = CreateHttpContext();
+ httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct;
+ });
- httpContext.Request.Headers.Referer = "https://example.org";
+ var requestDelegate = resultFactory.RequestDelegate;
+ await requestDelegate(httpContext);
- var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct myBindAsyncStruct) =>
- {
- httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct;
- });
+ Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]);
+ }
- var requestDelegate = resultFactory.RequestDelegate;
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegateUsesBindAsyncOverTryParseGivenNullableStruct()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]);
- }
+ httpContext.Request.Headers.Referer = "https://example.org";
- [Fact]
- public async Task RequestDelegateUsesBindAsyncOverTryParseGivenNullableStruct()
+ var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct? myBindAsyncStruct) =>
{
- var httpContext = CreateHttpContext();
+ httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct;
+ });
- httpContext.Request.Headers.Referer = "https://example.org";
+ var requestDelegate = resultFactory.RequestDelegate;
+ await requestDelegate(httpContext);
- var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct? myBindAsyncStruct) =>
- {
- httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct;
- });
+ Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]);
+ }
- var requestDelegate = resultFactory.RequestDelegate;
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegateUsesParameterInfoBindAsyncOverOtherBindAsync()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]);
- }
+ httpContext.Request.Headers.Referer = "https://example.org";
- [Fact]
- public async Task RequestDelegateUsesParameterInfoBindAsyncOverOtherBindAsync()
+ var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBothBindAsyncStruct? myBothBindAsyncStruct) =>
{
- var httpContext = CreateHttpContext();
+ httpContext.Items["myBothBindAsyncStruct"] = myBothBindAsyncStruct;
+ });
- httpContext.Request.Headers.Referer = "https://example.org";
+ var requestDelegate = resultFactory.RequestDelegate;
+ await requestDelegate(httpContext);
- var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBothBindAsyncStruct? myBothBindAsyncStruct) =>
- {
- httpContext.Items["myBothBindAsyncStruct"] = myBothBindAsyncStruct;
- });
+ Assert.Equal(new MyBothBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBothBindAsyncStruct"]);
+ }
- var requestDelegate = resultFactory.RequestDelegate;
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegateUsesTryParseOverBindAsyncGivenExplicitAttribute()
+ {
+ var fromRouteFactoryResult = RequestDelegateFactory.Create((HttpContext httpContext, [FromRoute] MyBindAsyncRecord myBindAsyncRecord) => { });
+ var fromQueryFactoryResult = RequestDelegateFactory.Create((HttpContext httpContext, [FromQuery] MyBindAsyncRecord myBindAsyncRecord) => { });
- Assert.Equal(new MyBothBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBothBindAsyncStruct"]);
- }
- [Fact]
- public async Task RequestDelegateUsesTryParseOverBindAsyncGivenExplicitAttribute()
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues["myBindAsyncRecord"] = "foo";
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- var fromRouteFactoryResult = RequestDelegateFactory.Create((HttpContext httpContext, [FromRoute] MyBindAsyncRecord myBindAsyncRecord) => { });
- var fromQueryFactoryResult = RequestDelegateFactory.Create((HttpContext httpContext, [FromQuery] MyBindAsyncRecord myBindAsyncRecord) => { });
+ ["myBindAsyncRecord"] = "foo"
+ });
+ var fromRouteRequestDelegate = fromRouteFactoryResult.RequestDelegate;
+ var fromQueryRequestDelegate = fromQueryFactoryResult.RequestDelegate;
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues["myBindAsyncRecord"] = "foo";
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["myBindAsyncRecord"] = "foo"
- });
+ await Assert.ThrowsAsync<NotImplementedException>(() => fromRouteRequestDelegate(httpContext));
+ await Assert.ThrowsAsync<NotImplementedException>(() => fromQueryRequestDelegate(httpContext));
+ }
- var fromRouteRequestDelegate = fromRouteFactoryResult.RequestDelegate;
- var fromQueryRequestDelegate = fromQueryFactoryResult.RequestDelegate;
+ [Fact]
+ public async Task RequestDelegateCanAwaitValueTasksThatAreNotImmediatelyCompleted()
+ {
+ var httpContext = CreateHttpContext();
- await Assert.ThrowsAsync<NotImplementedException>(() => fromRouteRequestDelegate(httpContext));
- await Assert.ThrowsAsync<NotImplementedException>(() => fromQueryRequestDelegate(httpContext));
- }
+ httpContext.Request.Headers.Referer = "https://example.org";
- [Fact]
- public async Task RequestDelegateCanAwaitValueTasksThatAreNotImmediatelyCompleted()
- {
- var httpContext = CreateHttpContext();
+ var resultFactory = RequestDelegateFactory.Create(
+ (HttpContext httpContext, MyAwaitedBindAsyncRecord myAwaitedBindAsyncRecord, MyAwaitedBindAsyncStruct myAwaitedBindAsyncStruct) =>
+ {
+ httpContext.Items["myAwaitedBindAsyncRecord"] = myAwaitedBindAsyncRecord;
+ httpContext.Items["myAwaitedBindAsyncStruct"] = myAwaitedBindAsyncStruct;
+ });
- httpContext.Request.Headers.Referer = "https://example.org";
+ var requestDelegate = resultFactory.RequestDelegate;
+ await requestDelegate(httpContext);
- var resultFactory = RequestDelegateFactory.Create(
- (HttpContext httpContext, MyAwaitedBindAsyncRecord myAwaitedBindAsyncRecord, MyAwaitedBindAsyncStruct myAwaitedBindAsyncStruct) =>
- {
- httpContext.Items["myAwaitedBindAsyncRecord"] = myAwaitedBindAsyncRecord;
- httpContext.Items["myAwaitedBindAsyncStruct"] = myAwaitedBindAsyncStruct;
- });
+ Assert.Equal(new MyAwaitedBindAsyncRecord(new Uri("https://example.org")), httpContext.Items["myAwaitedBindAsyncRecord"]);
+ Assert.Equal(new MyAwaitedBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myAwaitedBindAsyncStruct"]);
+ }
- var requestDelegate = resultFactory.RequestDelegate;
- await requestDelegate(httpContext);
+ [Fact]
+ public async Task RequestDelegateUsesBindAsyncFromImplementedInterface()
+ {
+ var httpContext = CreateHttpContext();
- Assert.Equal(new MyAwaitedBindAsyncRecord(new Uri("https://example.org")), httpContext.Items["myAwaitedBindAsyncRecord"]);
- Assert.Equal(new MyAwaitedBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myAwaitedBindAsyncStruct"]);
- }
+ httpContext.Request.Headers.Referer = "https://example.org";
- [Fact]
- public async Task RequestDelegateUsesBindAsyncFromImplementedInterface()
+ var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncFromInterfaceRecord myBindAsyncRecord) =>
{
- var httpContext = CreateHttpContext();
+ httpContext.Items["myBindAsyncFromInterfaceRecord"] = myBindAsyncRecord;
+ });
- httpContext.Request.Headers.Referer = "https://example.org";
+ var requestDelegate = resultFactory.RequestDelegate;
- var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncFromInterfaceRecord myBindAsyncRecord) =>
- {
- httpContext.Items["myBindAsyncFromInterfaceRecord"] = myBindAsyncRecord;
- });
-
- var requestDelegate = resultFactory.RequestDelegate;
-
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(new MyBindAsyncFromInterfaceRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncFromInterfaceRecord"]);
- }
+ Assert.Equal(new MyBindAsyncFromInterfaceRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncFromInterfaceRecord"]);
+ }
- public static object[][] DelegatesWithAttributesOnNotTryParsableParameters
+ public static object[][] DelegatesWithAttributesOnNotTryParsableParameters
+ {
+ get
{
- get
- {
- void InvalidFromRoute([FromRoute] object notTryParsable) { }
- void InvalidFromQuery([FromQuery] object notTryParsable) { }
- void InvalidFromHeader([FromHeader] object notTryParsable) { }
+ void InvalidFromRoute([FromRoute] object notTryParsable) { }
+ void InvalidFromQuery([FromQuery] object notTryParsable) { }
+ void InvalidFromHeader([FromHeader] object notTryParsable) { }
- return new[]
- {
+ return new[]
+ {
new object[] { (Action<object>)InvalidFromRoute },
new object[] { (Action<object>)InvalidFromQuery },
new object[] { (Action<object>)InvalidFromHeader },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DelegatesWithAttributesOnNotTryParsableParameters))]
- public void CreateThrowsInvalidOperationExceptionWhenAttributeRequiresTryParseMethodThatDoesNotExist(Delegate action)
- {
- var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(action));
- Assert.Equal("No public static bool object.TryParse(string, out object) method found for notTryParsable.", ex.Message);
- }
+ [Theory]
+ [MemberData(nameof(DelegatesWithAttributesOnNotTryParsableParameters))]
+ public void CreateThrowsInvalidOperationExceptionWhenAttributeRequiresTryParseMethodThatDoesNotExist(Delegate action)
+ {
+ var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(action));
+ Assert.Equal("No public static bool object.TryParse(string, out object) method found for notTryParsable.", ex.Message);
+ }
- [Fact]
- public void CreateThrowsInvalidOperationExceptionGivenUnnamedArgument()
- {
- var unnamedParameter = Expression.Parameter(typeof(int));
- var lambda = Expression.Lambda(Expression.Block(), unnamedParameter);
- var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(lambda.Compile()));
- Assert.Equal("Encountered a parameter of type 'System.Runtime.CompilerServices.Closure' without a name. Parameters must have a name.", ex.Message);
- }
+ [Fact]
+ public void CreateThrowsInvalidOperationExceptionGivenUnnamedArgument()
+ {
+ var unnamedParameter = Expression.Parameter(typeof(int));
+ var lambda = Expression.Lambda(Expression.Block(), unnamedParameter);
+ var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(lambda.Compile()));
+ Assert.Equal("Encountered a parameter of type 'System.Runtime.CompilerServices.Closure' without a name. Parameters must have a name.", ex.Message);
+ }
- [Fact]
- public async Task RequestDelegateLogsTryParsableFailuresAsDebugAndSets400Response()
+ [Fact]
+ public async Task RequestDelegateLogsTryParsableFailuresAsDebugAndSets400Response()
+ {
+ var invoked = false;
+
+ void TestAction([FromRoute] int tryParsable, [FromRoute] int tryParsable2)
{
- var invoked = false;
+ invoked = true;
+ }
- void TestAction([FromRoute] int tryParsable, [FromRoute] int tryParsable2)
- {
- invoked = true;
- }
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues["tryParsable"] = "invalid!";
+ httpContext.Request.RouteValues["tryParsable2"] = "invalid again!";
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues["tryParsable"] = "invalid!";
- httpContext.Request.RouteValues["tryParsable2"] = "invalid again!";
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.False(invoked);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- Assert.False(invoked);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(400, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ var logs = TestSink.Writes.ToArray();
- var logs = TestSink.Writes.ToArray();
+ Assert.Equal(2, logs.Length);
- Assert.Equal(2, logs.Length);
+ Assert.Equal(new EventId(3, "ParameterBindingFailed"), logs[0].EventId);
+ Assert.Equal(LogLevel.Debug, logs[0].LogLevel);
+ Assert.Equal(@"Failed to bind parameter ""int tryParsable"" from ""invalid!"".", logs[0].Message);
- Assert.Equal(new EventId(3, "ParameterBindingFailed"), logs[0].EventId);
- Assert.Equal(LogLevel.Debug, logs[0].LogLevel);
- Assert.Equal(@"Failed to bind parameter ""int tryParsable"" from ""invalid!"".", logs[0].Message);
+ Assert.Equal(new EventId(3, "ParameterBindingFailed"), logs[1].EventId);
+ Assert.Equal(LogLevel.Debug, logs[1].LogLevel);
+ Assert.Equal(@"Failed to bind parameter ""int tryParsable2"" from ""invalid again!"".", logs[1].Message);
+ }
- Assert.Equal(new EventId(3, "ParameterBindingFailed"), logs[1].EventId);
- Assert.Equal(LogLevel.Debug, logs[1].LogLevel);
- Assert.Equal(@"Failed to bind parameter ""int tryParsable2"" from ""invalid again!"".", logs[1].Message);
- }
+ [Fact]
+ public async Task RequestDelegateThrowsForTryParsableFailuresIfThrowOnBadRequest()
+ {
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateThrowsForTryParsableFailuresIfThrowOnBadRequest()
+ void TestAction([FromRoute] int tryParsable, [FromRoute] int tryParsable2)
{
- var invoked = false;
+ invoked = true;
+ }
- void TestAction([FromRoute] int tryParsable, [FromRoute] int tryParsable2)
- {
- invoked = true;
- }
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues["tryParsable"] = "invalid!";
+ httpContext.Request.RouteValues["tryParsable2"] = "invalid again!";
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues["tryParsable"] = "invalid!";
- httpContext.Request.RouteValues["tryParsable2"] = "invalid again!";
+ var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = true });
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = true });
- var requestDelegate = factoryResult.RequestDelegate;
+ var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
- var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
+ Assert.False(invoked);
- Assert.False(invoked);
+ // The httpContext should be untouched.
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- // The httpContext should be untouched.
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ // We don't log bad requests when we throw.
+ Assert.Empty(TestSink.Writes);
- // We don't log bad requests when we throw.
- Assert.Empty(TestSink.Writes);
+ Assert.Equal(@"Failed to bind parameter ""int tryParsable"" from ""invalid!"".", badHttpRequestException.Message);
+ Assert.Equal(400, badHttpRequestException.StatusCode);
+ }
- Assert.Equal(@"Failed to bind parameter ""int tryParsable"" from ""invalid!"".", badHttpRequestException.Message);
- Assert.Equal(400, badHttpRequestException.StatusCode);
- }
+ [Fact]
+ public async Task RequestDelegateLogsBindAsyncFailuresAndSets400Response()
+ {
+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
+ var httpContext = CreateHttpContext();
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateLogsBindAsyncFailuresAndSets400Response()
+ var factoryResult = RequestDelegateFactory.Create((MyBindAsyncRecord myBindAsyncRecord1, MyBindAsyncRecord myBindAsyncRecord2) =>
{
- // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
- var httpContext = CreateHttpContext();
- var invoked = false;
+ invoked = true;
+ });
- var factoryResult = RequestDelegateFactory.Create((MyBindAsyncRecord myBindAsyncRecord1, MyBindAsyncRecord myBindAsyncRecord2) =>
- {
- invoked = true;
- });
+ var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ Assert.False(invoked);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(400, httpContext.Response.StatusCode);
- Assert.False(invoked);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(400, httpContext.Response.StatusCode);
+ var logs = TestSink.Writes.ToArray();
- var logs = TestSink.Writes.ToArray();
+ Assert.Equal(2, logs.Length);
- Assert.Equal(2, logs.Length);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[0].EventId);
+ Assert.Equal(LogLevel.Debug, logs[0].LogLevel);
+ Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord1"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", logs[0].Message);
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[0].EventId);
- Assert.Equal(LogLevel.Debug, logs[0].LogLevel);
- Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord1"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", logs[0].Message);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[1].EventId);
+ Assert.Equal(LogLevel.Debug, logs[1].LogLevel);
+ Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord2"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", logs[1].Message);
+ }
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[1].EventId);
- Assert.Equal(LogLevel.Debug, logs[1].LogLevel);
- Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord2"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", logs[1].Message);
- }
+ [Fact]
+ public async Task RequestDelegateThrowsForBindAsyncFailuresIfThrowOnBadRequest()
+ {
+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
+ var httpContext = CreateHttpContext();
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateThrowsForBindAsyncFailuresIfThrowOnBadRequest()
+ var factoryResult = RequestDelegateFactory.Create((MyBindAsyncRecord myBindAsyncRecord1, MyBindAsyncRecord myBindAsyncRecord2) =>
{
- // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
- var httpContext = CreateHttpContext();
- var invoked = false;
+ invoked = true;
+ }, new() { ThrowOnBadRequest = true });
- var factoryResult = RequestDelegateFactory.Create((MyBindAsyncRecord myBindAsyncRecord1, MyBindAsyncRecord myBindAsyncRecord2) =>
- {
- invoked = true;
- }, new() { ThrowOnBadRequest = true });
+ var requestDelegate = factoryResult.RequestDelegate;
+ var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
- var requestDelegate = factoryResult.RequestDelegate;
- var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
+ Assert.False(invoked);
- Assert.False(invoked);
+ // The httpContext should be untouched.
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- // The httpContext should be untouched.
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ // We don't log bad requests when we throw.
+ Assert.Empty(TestSink.Writes);
- // We don't log bad requests when we throw.
- Assert.Empty(TestSink.Writes);
+ Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord1"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", badHttpRequestException.Message);
+ Assert.Equal(400, badHttpRequestException.StatusCode);
+ }
- Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord1"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", badHttpRequestException.Message);
- Assert.Equal(400, badHttpRequestException.StatusCode);
- }
+ [Fact]
+ public async Task RequestDelegateLogsSingleArgBindAsyncFailuresAndSets400Response()
+ {
+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
+ var httpContext = CreateHttpContext();
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateLogsSingleArgBindAsyncFailuresAndSets400Response()
+ var factoryResult = RequestDelegateFactory.Create((MySimpleBindAsyncRecord mySimpleBindAsyncRecord1,
+ MySimpleBindAsyncRecord mySimpleBindAsyncRecord2) =>
{
- // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
- var httpContext = CreateHttpContext();
- var invoked = false;
+ invoked = true;
+ });
- var factoryResult = RequestDelegateFactory.Create((MySimpleBindAsyncRecord mySimpleBindAsyncRecord1,
- MySimpleBindAsyncRecord mySimpleBindAsyncRecord2) =>
- {
- invoked = true;
- });
+ var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ Assert.False(invoked);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(400, httpContext.Response.StatusCode);
- Assert.False(invoked);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(400, httpContext.Response.StatusCode);
+ var logs = TestSink.Writes.ToArray();
- var logs = TestSink.Writes.ToArray();
+ Assert.Equal(2, logs.Length);
- Assert.Equal(2, logs.Length);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[0].EventId);
+ Assert.Equal(LogLevel.Debug, logs[0].LogLevel);
+ Assert.Equal(@"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord1"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext).", logs[0].Message);
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[0].EventId);
- Assert.Equal(LogLevel.Debug, logs[0].LogLevel);
- Assert.Equal(@"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord1"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext).", logs[0].Message);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[1].EventId);
+ Assert.Equal(LogLevel.Debug, logs[1].LogLevel);
+ Assert.Equal(@"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord2"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext).", logs[1].Message);
+ }
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), logs[1].EventId);
- Assert.Equal(LogLevel.Debug, logs[1].LogLevel);
- Assert.Equal(@"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord2"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext).", logs[1].Message);
- }
+ [Fact]
+ public async Task RequestDelegateThrowsForSingleArgBindAsyncFailuresIfThrowOnBadRequest()
+ {
+ // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
+ var httpContext = CreateHttpContext();
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateThrowsForSingleArgBindAsyncFailuresIfThrowOnBadRequest()
+ var factoryResult = RequestDelegateFactory.Create((MySimpleBindAsyncRecord mySimpleBindAsyncRecord1,
+ MySimpleBindAsyncRecord mySimpleBindAsyncRecord2) =>
{
- // Not supplying any headers will cause the HttpContext BindAsync overload to return null.
- var httpContext = CreateHttpContext();
- var invoked = false;
+ invoked = true;
+ }, new() { ThrowOnBadRequest = true });
- var factoryResult = RequestDelegateFactory.Create((MySimpleBindAsyncRecord mySimpleBindAsyncRecord1,
- MySimpleBindAsyncRecord mySimpleBindAsyncRecord2) =>
- {
- invoked = true;
- }, new() { ThrowOnBadRequest = true });
-
- var requestDelegate = factoryResult.RequestDelegate;
- var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
+ var requestDelegate = factoryResult.RequestDelegate;
+ var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
- Assert.False(invoked);
+ Assert.False(invoked);
- // The httpContext should be untouched.
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ // The httpContext should be untouched.
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- // We don't log bad requests when we throw.
- Assert.Empty(TestSink.Writes);
+ // We don't log bad requests when we throw.
+ Assert.Empty(TestSink.Writes);
- Assert.Equal(@"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord1"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext).", badHttpRequestException.Message);
- Assert.Equal(400, badHttpRequestException.StatusCode);
- }
+ Assert.Equal(@"Required parameter ""MySimpleBindAsyncRecord mySimpleBindAsyncRecord1"" was not provided from MySimpleBindAsyncRecord.BindAsync(HttpContext).", badHttpRequestException.Message);
+ Assert.Equal(400, badHttpRequestException.StatusCode);
+ }
- [Fact]
- public async Task BindAsyncExceptionsAreUncaught()
- {
- var httpContext = CreateHttpContext();
+ [Fact]
+ public async Task BindAsyncExceptionsAreUncaught()
+ {
+ var httpContext = CreateHttpContext();
- var factoryResult = RequestDelegateFactory.Create((MyBindAsyncTypeThatThrows arg1) => { });
+ var factoryResult = RequestDelegateFactory.Create((MyBindAsyncTypeThatThrows arg1) => { });
- var requestDelegate = factoryResult.RequestDelegate;
+ var requestDelegate = factoryResult.RequestDelegate;
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegate(httpContext));
- Assert.Equal("BindAsync failed", ex.Message);
- }
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegate(httpContext));
+ Assert.Equal("BindAsync failed", ex.Message);
+ }
- [Fact]
- public async Task BindAsyncWithBodyArgument()
+ [Fact]
+ public async Task BindAsyncWithBodyArgument()
+ {
+ Todo originalTodo = new()
{
- Todo originalTodo = new()
- {
- Name = "Write more tests!"
- };
+ Name = "Write more tests!"
+ };
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
- var stream = new MemoryStream(requestBodyBytes);
- httpContext.Request.Body = stream;
+ var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
+ var stream = new MemoryStream(requestBodyBytes);
+ httpContext.Request.Body = stream;
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var jsonOptions = new JsonOptions();
- jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
+ var jsonOptions = new JsonOptions();
+ jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
- var mock = new Mock<IServiceProvider>();
- mock.Setup(m => m.GetService(It.IsAny<Type>())).Returns<Type>(t =>
+ var mock = new Mock<IServiceProvider>();
+ mock.Setup(m => m.GetService(It.IsAny<Type>())).Returns<Type>(t =>
+ {
+ if (t == typeof(IOptions<JsonOptions>))
{
- if (t == typeof(IOptions<JsonOptions>))
- {
- return Options.Create(jsonOptions);
- }
- return null;
- });
+ return Options.Create(jsonOptions);
+ }
+ return null;
+ });
- httpContext.RequestServices = mock.Object;
- httpContext.Request.Headers.Referer = "https://example.org";
+ httpContext.RequestServices = mock.Object;
+ httpContext.Request.Headers.Referer = "https://example.org";
- var invoked = false;
+ var invoked = false;
- var factoryResult = RequestDelegateFactory.Create((HttpContext context, MyBindAsyncRecord myBindAsyncRecord, Todo todo) =>
- {
- invoked = true;
- context.Items[nameof(myBindAsyncRecord)] = myBindAsyncRecord;
- context.Items[nameof(todo)] = todo;
- });
+ var factoryResult = RequestDelegateFactory.Create((HttpContext context, MyBindAsyncRecord myBindAsyncRecord, Todo todo) =>
+ {
+ invoked = true;
+ context.Items[nameof(myBindAsyncRecord)] = myBindAsyncRecord;
+ context.Items[nameof(todo)] = todo;
+ });
- var requestDelegate = factoryResult.RequestDelegate;
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.True(invoked);
- var arg = httpContext.Items["myBindAsyncRecord"] as MyBindAsyncRecord;
- Assert.NotNull(arg);
- Assert.Equal("https://example.org/", arg!.Uri.ToString());
- var todo = httpContext.Items["todo"] as Todo;
- Assert.NotNull(todo);
- Assert.Equal("Write more tests!", todo!.Name);
- }
+ Assert.True(invoked);
+ var arg = httpContext.Items["myBindAsyncRecord"] as MyBindAsyncRecord;
+ Assert.NotNull(arg);
+ Assert.Equal("https://example.org/", arg!.Uri.ToString());
+ var todo = httpContext.Items["todo"] as Todo;
+ Assert.NotNull(todo);
+ Assert.Equal("Write more tests!", todo!.Name);
+ }
- [Fact]
- public async Task BindAsyncRunsBeforeBodyBinding()
+ [Fact]
+ public async Task BindAsyncRunsBeforeBodyBinding()
+ {
+ Todo originalTodo = new()
{
- Todo originalTodo = new()
- {
- Name = "Write more tests!"
- };
+ Name = "Write more tests!"
+ };
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
- var stream = new MemoryStream(requestBodyBytes);
- httpContext.Request.Body = stream;
+ var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
+ var stream = new MemoryStream(requestBodyBytes);
+ httpContext.Request.Body = stream;
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var jsonOptions = new JsonOptions();
- jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
+ var jsonOptions = new JsonOptions();
+ jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
- var mock = new Mock<IServiceProvider>();
- mock.Setup(m => m.GetService(It.IsAny<Type>())).Returns<Type>(t =>
+ var mock = new Mock<IServiceProvider>();
+ mock.Setup(m => m.GetService(It.IsAny<Type>())).Returns<Type>(t =>
+ {
+ if (t == typeof(IOptions<JsonOptions>))
{
- if (t == typeof(IOptions<JsonOptions>))
- {
- return Options.Create(jsonOptions);
- }
- return null;
- });
+ return Options.Create(jsonOptions);
+ }
+ return null;
+ });
- httpContext.RequestServices = mock.Object;
- httpContext.Request.Headers.Referer = "https://example.org";
+ httpContext.RequestServices = mock.Object;
+ httpContext.Request.Headers.Referer = "https://example.org";
- var invoked = false;
+ var invoked = false;
- var factoryResult = RequestDelegateFactory.Create((HttpContext context, CustomTodo customTodo, Todo todo) =>
- {
- invoked = true;
- context.Items[nameof(customTodo)] = customTodo;
- context.Items[nameof(todo)] = todo;
- });
+ var factoryResult = RequestDelegateFactory.Create((HttpContext context, CustomTodo customTodo, Todo todo) =>
+ {
+ invoked = true;
+ context.Items[nameof(customTodo)] = customTodo;
+ context.Items[nameof(todo)] = todo;
+ });
- var requestDelegate = factoryResult.RequestDelegate;
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.True(invoked);
- var todo0 = httpContext.Items["customTodo"] as Todo;
- Assert.NotNull(todo0);
- Assert.Equal("Write more tests!", todo0!.Name);
- var todo1 = httpContext.Items["todo"] as Todo;
- Assert.NotNull(todo1);
- Assert.Equal("Write more tests!", todo1!.Name);
- }
+ Assert.True(invoked);
+ var todo0 = httpContext.Items["customTodo"] as Todo;
+ Assert.NotNull(todo0);
+ Assert.Equal("Write more tests!", todo0!.Name);
+ var todo1 = httpContext.Items["todo"] as Todo;
+ Assert.NotNull(todo1);
+ Assert.Equal("Write more tests!", todo1!.Name);
+ }
- [Fact]
- public async Task RequestDelegatePopulatesFromQueryParameterBasedOnParameterName()
- {
- const string paramName = "value";
- const int originalQueryParam = 42;
+ [Fact]
+ public async Task RequestDelegatePopulatesFromQueryParameterBasedOnParameterName()
+ {
+ const string paramName = "value";
+ const int originalQueryParam = 42;
- int? deserializedRouteParam = null;
+ int? deserializedRouteParam = null;
- void TestAction([FromQuery] int value)
- {
- deserializedRouteParam = value;
- }
+ void TestAction([FromQuery] int value)
+ {
+ deserializedRouteParam = value;
+ }
- var query = new QueryCollection(new Dictionary<string, StringValues>()
- {
- [paramName] = originalQueryParam.ToString(NumberFormatInfo.InvariantInfo)
- });
+ var query = new QueryCollection(new Dictionary<string, StringValues>()
+ {
+ [paramName] = originalQueryParam.ToString(NumberFormatInfo.InvariantInfo)
+ });
- var httpContext = CreateHttpContext();
- httpContext.Request.Query = query;
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Query = query;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(originalQueryParam, deserializedRouteParam);
- }
+ Assert.Equal(originalQueryParam, deserializedRouteParam);
+ }
- [Fact]
- public async Task RequestDelegatePopulatesFromHeaderParameterBasedOnParameterName()
- {
- const string customHeaderName = "X-Custom-Header";
- const int originalHeaderParam = 42;
+ [Fact]
+ public async Task RequestDelegatePopulatesFromHeaderParameterBasedOnParameterName()
+ {
+ const string customHeaderName = "X-Custom-Header";
+ const int originalHeaderParam = 42;
- int? deserializedRouteParam = null;
+ int? deserializedRouteParam = null;
- void TestAction([FromHeader(Name = customHeaderName)] int value)
- {
- deserializedRouteParam = value;
- }
+ void TestAction([FromHeader(Name = customHeaderName)] int value)
+ {
+ deserializedRouteParam = value;
+ }
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers[customHeaderName] = originalHeaderParam.ToString(NumberFormatInfo.InvariantInfo);
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers[customHeaderName] = originalHeaderParam.ToString(NumberFormatInfo.InvariantInfo);
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(originalHeaderParam, deserializedRouteParam);
- }
+ Assert.Equal(originalHeaderParam, deserializedRouteParam);
+ }
- public static object[][] ImplicitFromBodyActions
+ public static object[][] ImplicitFromBodyActions
+ {
+ get
{
- get
+ void TestImpliedFromBody(HttpContext httpContext, Todo todo)
{
- void TestImpliedFromBody(HttpContext httpContext, Todo todo)
- {
- httpContext.Items.Add("body", todo);
- }
+ httpContext.Items.Add("body", todo);
+ }
- void TestImpliedFromBodyInterface(HttpContext httpContext, ITodo todo)
- {
- httpContext.Items.Add("body", todo);
- }
+ void TestImpliedFromBodyInterface(HttpContext httpContext, ITodo todo)
+ {
+ httpContext.Items.Add("body", todo);
+ }
- void TestImpliedFromBodyStruct(HttpContext httpContext, TodoStruct todo)
- {
- httpContext.Items.Add("body", todo);
- }
+ void TestImpliedFromBodyStruct(HttpContext httpContext, TodoStruct todo)
+ {
+ httpContext.Items.Add("body", todo);
+ }
- return new[]
- {
+ return new[]
+ {
new[] { (Action<HttpContext, Todo>)TestImpliedFromBody },
new[] { (Action<HttpContext, ITodo>)TestImpliedFromBodyInterface },
new object[] { (Action<HttpContext, TodoStruct>)TestImpliedFromBodyStruct },
};
- }
}
+ }
- public static object[][] ExplicitFromBodyActions
+ public static object[][] ExplicitFromBodyActions
+ {
+ get
{
- get
+ void TestExplicitFromBody(HttpContext httpContext, [FromBody] Todo todo)
{
- void TestExplicitFromBody(HttpContext httpContext, [FromBody] Todo todo)
- {
- httpContext.Items.Add("body", todo);
- }
+ httpContext.Items.Add("body", todo);
+ }
- return new[]
- {
+ return new[]
+ {
new[] { (Action<HttpContext, Todo>)TestExplicitFromBody },
};
- }
}
+ }
- public static object[][] FromBodyActions
+ public static object[][] FromBodyActions
+ {
+ get
{
- get
- {
- return ExplicitFromBodyActions.Concat(ImplicitFromBodyActions).ToArray();
- }
+ return ExplicitFromBodyActions.Concat(ImplicitFromBodyActions).ToArray();
}
+ }
- [Theory]
- [MemberData(nameof(FromBodyActions))]
- public async Task RequestDelegatePopulatesFromBodyParameter(Delegate action)
+ [Theory]
+ [MemberData(nameof(FromBodyActions))]
+ public async Task RequestDelegatePopulatesFromBodyParameter(Delegate action)
+ {
+ Todo originalTodo = new()
{
- Todo originalTodo = new()
- {
- Name = "Write more tests!"
- };
+ Name = "Write more tests!"
+ };
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
- var stream = new MemoryStream(requestBodyBytes);
- httpContext.Request.Body = stream;
+ var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
+ var stream = new MemoryStream(requestBodyBytes);
+ httpContext.Request.Body = stream;
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var jsonOptions = new JsonOptions();
- jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
+ var jsonOptions = new JsonOptions();
+ jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
- var mock = new Mock<IServiceProvider>();
- mock.Setup(m => m.GetService(It.IsAny<Type>())).Returns<Type>(t =>
+ var mock = new Mock<IServiceProvider>();
+ mock.Setup(m => m.GetService(It.IsAny<Type>())).Returns<Type>(t =>
+ {
+ if (t == typeof(IOptions<JsonOptions>))
{
- if (t == typeof(IOptions<JsonOptions>))
- {
- return Options.Create(jsonOptions);
- }
- return null;
- });
- httpContext.RequestServices = mock.Object;
+ return Options.Create(jsonOptions);
+ }
+ return null;
+ });
+ httpContext.RequestServices = mock.Object;
- var factoryResult = RequestDelegateFactory.Create(action);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(action);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var deserializedRequestBody = httpContext.Items["body"];
- Assert.NotNull(deserializedRequestBody);
- Assert.Equal(originalTodo.Name, ((ITodo)deserializedRequestBody!).Name);
- }
+ var deserializedRequestBody = httpContext.Items["body"];
+ Assert.NotNull(deserializedRequestBody);
+ Assert.Equal(originalTodo.Name, ((ITodo)deserializedRequestBody!).Name);
+ }
- [Theory]
- [MemberData(nameof(ExplicitFromBodyActions))]
- public async Task RequestDelegateRejectsEmptyBodyGivenExplicitFromBodyParameter(Delegate action)
- {
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "0";
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(false));
+ [Theory]
+ [MemberData(nameof(ExplicitFromBodyActions))]
+ public async Task RequestDelegateRejectsEmptyBodyGivenExplicitFromBodyParameter(Delegate action)
+ {
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "0";
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(false));
- var factoryResult = RequestDelegateFactory.Create(action);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(action);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(400, httpContext.Response.StatusCode);
- }
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ }
- [Theory]
- [MemberData(nameof(ImplicitFromBodyActions))]
- public async Task RequestDelegateRejectsEmptyBodyGivenImplicitFromBodyParameter(Delegate action)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "0";
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(false));
+ [Theory]
+ [MemberData(nameof(ImplicitFromBodyActions))]
+ public async Task RequestDelegateRejectsEmptyBodyGivenImplicitFromBodyParameter(Delegate action)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "0";
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(false));
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(LoggerFactory);
- httpContext.RequestServices = serviceCollection.BuildServiceProvider();
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(LoggerFactory);
+ httpContext.RequestServices = serviceCollection.BuildServiceProvider();
- var factoryResult = RequestDelegateFactory.Create(action, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = true });
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(action, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = true });
+ var requestDelegate = factoryResult.RequestDelegate;
- var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
- Assert.StartsWith("Implicit body inferred for parameter", ex.Message);
- Assert.EndsWith("but no body was provided. Did you mean to use a Service instead?", ex.Message);
- }
+ var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
+ Assert.StartsWith("Implicit body inferred for parameter", ex.Message);
+ Assert.EndsWith("but no body was provided. Did you mean to use a Service instead?", ex.Message);
+ }
- [Fact]
- public async Task RequestDelegateAllowsEmptyBodyGivenCorrectyConfiguredFromBodyParameter()
+ [Fact]
+ public async Task RequestDelegateAllowsEmptyBodyGivenCorrectyConfiguredFromBodyParameter()
+ {
+ var todoToBecomeNull = new Todo();
+
+ void TestAction([FromBody(AllowEmpty = true)] Todo todo)
{
- var todoToBecomeNull = new Todo();
+ todoToBecomeNull = todo;
+ }
- void TestAction([FromBody(AllowEmpty = true)] Todo todo)
- {
- todoToBecomeNull = todo;
- }
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "0";
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "0";
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Null(todoToBecomeNull);
+ }
- Assert.Null(todoToBecomeNull);
- }
+ [Fact]
+ public async Task RequestDelegateAllowsEmptyBodyStructGivenCorrectyConfiguredFromBodyParameter()
+ {
+ var structToBeZeroed = new BodyStruct
+ {
+ Id = 42
+ };
- [Fact]
- public async Task RequestDelegateAllowsEmptyBodyStructGivenCorrectyConfiguredFromBodyParameter()
+ void TestAction([FromBody(AllowEmpty = true)] BodyStruct bodyStruct)
{
- var structToBeZeroed = new BodyStruct
- {
- Id = 42
- };
+ structToBeZeroed = bodyStruct;
+ }
- void TestAction([FromBody(AllowEmpty = true)] BodyStruct bodyStruct)
- {
- structToBeZeroed = bodyStruct;
- }
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "0";
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "0";
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Equal(default, structToBeZeroed);
+ }
- Assert.Equal(default, structToBeZeroed);
- }
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task RequestDelegateLogsIOExceptionsAsDebugDoesNotAbortAndNeverThrows(bool throwOnBadRequests)
+ {
+ var invoked = false;
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task RequestDelegateLogsIOExceptionsAsDebugDoesNotAbortAndNeverThrows(bool throwOnBadRequests)
+ void TestAction([FromBody] Todo todo)
{
- var invoked = false;
+ invoked = true;
+ }
- void TestAction([FromBody] Todo todo)
- {
- invoked = true;
- }
+ var ioException = new IOException();
- var ioException = new IOException();
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "1";
+ httpContext.Request.Body = new ExceptionThrowingRequestBodyStream(ioException);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "1";
- httpContext.Request.Body = new ExceptionThrowingRequestBodyStream(ioException);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = throwOnBadRequests });
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = throwOnBadRequests });
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.False(invoked);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.False(invoked);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var logMessage = Assert.Single(TestSink.Writes);
+ Assert.Equal(new EventId(1, "RequestBodyIOException"), logMessage.EventId);
+ Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+ Assert.Equal("Reading the request body failed with an IOException.", logMessage.Message);
+ Assert.Same(ioException, logMessage.Exception);
+ }
- var logMessage = Assert.Single(TestSink.Writes);
- Assert.Equal(new EventId(1, "RequestBodyIOException"), logMessage.EventId);
- Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
- Assert.Equal("Reading the request body failed with an IOException.", logMessage.Message);
- Assert.Same(ioException, logMessage.Exception);
- }
+ [Fact]
+ public async Task RequestDelegateLogsJsonExceptionsAsDebugAndSets400Response()
+ {
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateLogsJsonExceptionsAsDebugAndSets400Response()
+ void TestAction([FromBody] Todo todo)
{
- var invoked = false;
+ invoked = true;
+ }
- void TestAction([FromBody] Todo todo)
- {
- invoked = true;
- }
+ var jsonException = new JsonException();
- var jsonException = new JsonException();
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "1";
+ httpContext.Request.Body = new ExceptionThrowingRequestBodyStream(jsonException);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "1";
- httpContext.Request.Body = new ExceptionThrowingRequestBodyStream(jsonException);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.False(invoked);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- Assert.False(invoked);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(400, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ var logMessage = Assert.Single(TestSink.Writes);
+ Assert.Equal(new EventId(2, "InvalidJsonRequestBody"), logMessage.EventId);
+ Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+ Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", logMessage.Message);
+ Assert.Same(jsonException, logMessage.Exception);
+ }
- var logMessage = Assert.Single(TestSink.Writes);
- Assert.Equal(new EventId(2, "InvalidJsonRequestBody"), logMessage.EventId);
- Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
- Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", logMessage.Message);
- Assert.Same(jsonException, logMessage.Exception);
- }
+ [Fact]
+ public async Task RequestDelegateThrowsForJsonExceptionsIfThrowOnBadRequest()
+ {
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateThrowsForJsonExceptionsIfThrowOnBadRequest()
+ void TestAction([FromBody] Todo todo)
{
- var invoked = false;
+ invoked = true;
+ }
- void TestAction([FromBody] Todo todo)
- {
- invoked = true;
- }
+ var jsonException = new JsonException();
- var jsonException = new JsonException();
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "1";
+ httpContext.Request.Body = new ExceptionThrowingRequestBodyStream(jsonException);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "1";
- httpContext.Request.Body = new ExceptionThrowingRequestBodyStream(jsonException);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = true });
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = true });
- var requestDelegate = factoryResult.RequestDelegate;
+ var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
- var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
+ Assert.False(invoked);
- Assert.False(invoked);
+ // The httpContext should be untouched.
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- // The httpContext should be untouched.
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ // We don't log bad requests when we throw.
+ Assert.Empty(TestSink.Writes);
- // We don't log bad requests when we throw.
- Assert.Empty(TestSink.Writes);
+ Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", badHttpRequestException.Message);
+ Assert.Equal(400, badHttpRequestException.StatusCode);
+ Assert.Same(jsonException, badHttpRequestException.InnerException);
+ }
- Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", badHttpRequestException.Message);
- Assert.Equal(400, badHttpRequestException.StatusCode);
- Assert.Same(jsonException, badHttpRequestException.InnerException);
- }
+ [Fact]
+ public async Task RequestDelegateLogsMalformedJsonAsDebugAndSets400Response()
+ {
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateLogsMalformedJsonAsDebugAndSets400Response()
+ void TestAction([FromBody] Todo todo)
{
- var invoked = false;
+ invoked = true;
+ }
- void TestAction([FromBody] Todo todo)
- {
- invoked = true;
- }
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "1";
+ httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{"));
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "1";
- httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{"));
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.False(invoked);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- Assert.False(invoked);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(400, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ var logMessage = Assert.Single(TestSink.Writes);
+ Assert.Equal(new EventId(2, "InvalidJsonRequestBody"), logMessage.EventId);
+ Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+ Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", logMessage.Message);
+ Assert.IsType<JsonException>(logMessage.Exception);
+ }
- var logMessage = Assert.Single(TestSink.Writes);
- Assert.Equal(new EventId(2, "InvalidJsonRequestBody"), logMessage.EventId);
- Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
- Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", logMessage.Message);
- Assert.IsType<JsonException>(logMessage.Exception);
- }
+ [Fact]
+ public async Task RequestDelegateThrowsForMalformedJsonIfThrowOnBadRequest()
+ {
+ var invoked = false;
- [Fact]
- public async Task RequestDelegateThrowsForMalformedJsonIfThrowOnBadRequest()
+ void TestAction([FromBody] Todo todo)
{
- var invoked = false;
-
- void TestAction([FromBody] Todo todo)
- {
- invoked = true;
- }
+ invoked = true;
+ }
- var httpContext = CreateHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.Headers["Content-Length"] = "1";
- httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{"));
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.Headers["Content-Length"] = "1";
+ httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("{"));
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = true });
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(TestAction, new() { ThrowOnBadRequest = true });
+ var requestDelegate = factoryResult.RequestDelegate;
- var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
+ var badHttpRequestException = await Assert.ThrowsAsync<BadHttpRequestException>(() => requestDelegate(httpContext));
- Assert.False(invoked);
+ Assert.False(invoked);
- // The httpContext should be untouched.
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.Response.HasStarted);
+ // The httpContext should be untouched.
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.Response.HasStarted);
- // We don't log bad requests when we throw.
- Assert.Empty(TestSink.Writes);
+ // We don't log bad requests when we throw.
+ Assert.Empty(TestSink.Writes);
- Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", badHttpRequestException.Message);
- Assert.Equal(400, badHttpRequestException.StatusCode);
- Assert.IsType<JsonException>(badHttpRequestException.InnerException);
- }
+ Assert.Equal(@"Failed to read parameter ""Todo todo"" from the request body as JSON.", badHttpRequestException.Message);
+ Assert.Equal(400, badHttpRequestException.StatusCode);
+ Assert.IsType<JsonException>(badHttpRequestException.InnerException);
+ }
- [Fact]
- public void BuildRequestDelegateThrowsInvalidOperationExceptionGivenFromBodyOnMultipleParameters()
- {
- void TestAttributedInvalidAction([FromBody] int value1, [FromBody] int value2) { }
- void TestInferredInvalidAction(Todo value1, Todo value2) { }
- void TestBothInvalidAction(Todo value1, [FromBody] int value2) { }
+ [Fact]
+ public void BuildRequestDelegateThrowsInvalidOperationExceptionGivenFromBodyOnMultipleParameters()
+ {
+ void TestAttributedInvalidAction([FromBody] int value1, [FromBody] int value2) { }
+ void TestInferredInvalidAction(Todo value1, Todo value2) { }
+ void TestBothInvalidAction(Todo value1, [FromBody] int value2) { }
- Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestAttributedInvalidAction));
- Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestInferredInvalidAction));
- Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBothInvalidAction));
- }
+ Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestAttributedInvalidAction));
+ Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestInferredInvalidAction));
+ Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBothInvalidAction));
+ }
- [Fact]
- public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidTryParse()
- {
- void TestTryParseStruct(BadTryParseStruct value1) { }
- void TestTryParseClass(BadTryParseClass value1) { }
+ [Fact]
+ public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidTryParse()
+ {
+ void TestTryParseStruct(BadTryParseStruct value1) { }
+ void TestTryParseClass(BadTryParseClass value1) { }
- Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseStruct));
- Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseClass));
- }
+ Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseStruct));
+ Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestTryParseClass));
+ }
- private struct BadTryParseStruct
- {
- public static void TryParse(string? value, out BadTryParseStruct result) { }
- }
+ private struct BadTryParseStruct
+ {
+ public static void TryParse(string? value, out BadTryParseStruct result) { }
+ }
- private class BadTryParseClass
+ private class BadTryParseClass
+ {
+ public static void TryParse(string? value, out BadTryParseClass result)
{
- public static void TryParse(string? value, out BadTryParseClass result)
- {
- result = new();
- }
+ result = new();
}
+ }
- [Fact]
- public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidBindAsync()
- {
- void TestBindAsyncStruct(BadBindAsyncStruct value1) { }
- void TestBindAsyncClass(BadBindAsyncClass value1) { }
+ [Fact]
+ public void BuildRequestDelegateThrowsInvalidOperationExceptionForInvalidBindAsync()
+ {
+ void TestBindAsyncStruct(BadBindAsyncStruct value1) { }
+ void TestBindAsyncClass(BadBindAsyncClass value1) { }
- var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncStruct));
- Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncClass));
- }
+ var ex = Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncStruct));
+ Assert.Throws<InvalidOperationException>(() => RequestDelegateFactory.Create(TestBindAsyncClass));
+ }
- private struct BadBindAsyncStruct
- {
- public static Task<BadBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private struct BadBindAsyncStruct
+ {
+ public static Task<BadBindAsyncStruct> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- private class BadBindAsyncClass
- {
- public static Task<BadBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
- throw new NotImplementedException();
- }
+ private class BadBindAsyncClass
+ {
+ public static Task<BadBindAsyncClass> BindAsync(HttpContext context, ParameterInfo parameter) =>
+ throw new NotImplementedException();
+ }
- public static object[][] ExplicitFromServiceActions
+ public static object[][] ExplicitFromServiceActions
+ {
+ get
{
- get
+ void TestExplicitFromService(HttpContext httpContext, [FromService] MyService myService)
{
- void TestExplicitFromService(HttpContext httpContext, [FromService] MyService myService)
- {
- httpContext.Items.Add("service", myService);
- }
+ httpContext.Items.Add("service", myService);
+ }
- void TestExplicitFromIEnumerableService(HttpContext httpContext, [FromService] IEnumerable<MyService> myServices)
- {
- httpContext.Items.Add("service", myServices.Single());
- }
+ void TestExplicitFromIEnumerableService(HttpContext httpContext, [FromService] IEnumerable<MyService> myServices)
+ {
+ httpContext.Items.Add("service", myServices.Single());
+ }
- void TestExplicitMultipleFromService(HttpContext httpContext, [FromService] MyService myService, [FromService] IEnumerable<MyService> myServices)
- {
- httpContext.Items.Add("service", myService);
- }
+ void TestExplicitMultipleFromService(HttpContext httpContext, [FromService] MyService myService, [FromService] IEnumerable<MyService> myServices)
+ {
+ httpContext.Items.Add("service", myService);
+ }
- return new object[][]
- {
+ return new object[][]
+ {
new[] { (Action<HttpContext, MyService>)TestExplicitFromService },
new[] { (Action<HttpContext, IEnumerable<MyService>>)TestExplicitFromIEnumerableService },
new[] { (Action<HttpContext, MyService, IEnumerable<MyService>>)TestExplicitMultipleFromService },
- };
- }
+ };
}
+ }
- public static object[][] ImplicitFromServiceActions
+ public static object[][] ImplicitFromServiceActions
+ {
+ get
{
- get
+ void TestImpliedFromService(HttpContext httpContext, IMyService myService)
{
- void TestImpliedFromService(HttpContext httpContext, IMyService myService)
- {
- httpContext.Items.Add("service", myService);
- }
+ httpContext.Items.Add("service", myService);
+ }
- void TestImpliedIEnumerableFromService(HttpContext httpContext, IEnumerable<MyService> myServices)
- {
- httpContext.Items.Add("service", myServices.Single());
- }
+ void TestImpliedIEnumerableFromService(HttpContext httpContext, IEnumerable<MyService> myServices)
+ {
+ httpContext.Items.Add("service", myServices.Single());
+ }
- void TestImpliedFromServiceBasedOnContainer(HttpContext httpContext, MyService myService)
- {
- httpContext.Items.Add("service", myService);
- }
+ void TestImpliedFromServiceBasedOnContainer(HttpContext httpContext, MyService myService)
+ {
+ httpContext.Items.Add("service", myService);
+ }
- return new object[][]
- {
+ return new object[][]
+ {
new[] { (Action<HttpContext, IMyService>)TestImpliedFromService },
new[] { (Action<HttpContext, IEnumerable<MyService>>)TestImpliedIEnumerableFromService },
new[] { (Action<HttpContext, MyService>)TestImpliedFromServiceBasedOnContainer },
- };
- }
+ };
}
+ }
- public static object[][] FromServiceActions
+ public static object[][] FromServiceActions
+ {
+ get
{
- get
- {
- return ImplicitFromServiceActions.Concat(ExplicitFromServiceActions).ToArray();
- }
+ return ImplicitFromServiceActions.Concat(ExplicitFromServiceActions).ToArray();
}
+ }
- [Theory]
- [MemberData(nameof(ImplicitFromServiceActions))]
- public async Task RequestDelegateRequiresServiceForAllImplicitFromServiceParameters(Delegate action)
- {
- var httpContext = CreateHttpContext();
+ [Theory]
+ [MemberData(nameof(ImplicitFromServiceActions))]
+ public async Task RequestDelegateRequiresServiceForAllImplicitFromServiceParameters(Delegate action)
+ {
+ var httpContext = CreateHttpContext();
- var factoryResult = RequestDelegateFactory.Create(action);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(action);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var message = Assert.Single(TestSink.Writes).Message;
- Assert.StartsWith("Implicit body inferred for parameter", message);
- Assert.EndsWith("but no body was provided. Did you mean to use a Service instead?", message);
- }
+ var message = Assert.Single(TestSink.Writes).Message;
+ Assert.StartsWith("Implicit body inferred for parameter", message);
+ Assert.EndsWith("but no body was provided. Did you mean to use a Service instead?", message);
+ }
- [Theory]
- [MemberData(nameof(ExplicitFromServiceActions))]
- public async Task RequestDelegateWithExplicitFromServiceParameters(Delegate action)
+ [Theory]
+ [MemberData(nameof(ExplicitFromServiceActions))]
+ public async Task RequestDelegateWithExplicitFromServiceParameters(Delegate action)
+ {
+ // IEnumerable<T> always resolves from DI but is empty and throws from test method
+ if (action.Method.Name.Contains("TestExplicitFromIEnumerableService", StringComparison.Ordinal))
{
- // IEnumerable<T> always resolves from DI but is empty and throws from test method
- if (action.Method.Name.Contains("TestExplicitFromIEnumerableService", StringComparison.Ordinal))
- {
- return;
- }
+ return;
+ }
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- var requestDelegateResult = RequestDelegateFactory.Create(action);
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegateResult.RequestDelegate(httpContext));
- Assert.Equal("No service for type 'Microsoft.AspNetCore.Routing.Internal.RequestDelegateFactoryTests+MyService' has been registered.", ex.Message);
- }
+ var requestDelegateResult = RequestDelegateFactory.Create(action);
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegateResult.RequestDelegate(httpContext));
+ Assert.Equal("No service for type 'Microsoft.AspNetCore.Routing.Internal.RequestDelegateFactoryTests+MyService' has been registered.", ex.Message);
+ }
- [Theory]
- [MemberData(nameof(FromServiceActions))]
- public async Task RequestDelegatePopulatesParametersFromServiceWithAndWithoutAttribute(Delegate action)
- {
- var myOriginalService = new MyService();
+ [Theory]
+ [MemberData(nameof(FromServiceActions))]
+ public async Task RequestDelegatePopulatesParametersFromServiceWithAndWithoutAttribute(Delegate action)
+ {
+ var myOriginalService = new MyService();
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(LoggerFactory);
- serviceCollection.AddSingleton(myOriginalService);
- serviceCollection.AddSingleton<IMyService>(myOriginalService);
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(LoggerFactory);
+ serviceCollection.AddSingleton(myOriginalService);
+ serviceCollection.AddSingleton<IMyService>(myOriginalService);
- var services = serviceCollection.BuildServiceProvider();
+ var services = serviceCollection.BuildServiceProvider();
- using var requestScoped = services.CreateScope();
+ using var requestScoped = services.CreateScope();
- var httpContext = CreateHttpContext();
- httpContext.RequestServices = requestScoped.ServiceProvider;
+ var httpContext = CreateHttpContext();
+ httpContext.RequestServices = requestScoped.ServiceProvider;
- var factoryResult = RequestDelegateFactory.Create(action, options: new() { ServiceProvider = services });
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(action, options: new() { ServiceProvider = services });
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Same(myOriginalService, httpContext.Items["service"]);
- }
+ Assert.Same(myOriginalService, httpContext.Items["service"]);
+ }
+
+ [Fact]
+ public async Task RequestDelegatePopulatesHttpContextParameterWithoutAttribute()
+ {
+ HttpContext? httpContextArgument = null;
- [Fact]
- public async Task RequestDelegatePopulatesHttpContextParameterWithoutAttribute()
+ void TestAction(HttpContext httpContext)
{
- HttpContext? httpContextArgument = null;
+ httpContextArgument = httpContext;
+ }
- void TestAction(HttpContext httpContext)
- {
- httpContextArgument = httpContext;
- }
+ var httpContext = CreateHttpContext();
- var httpContext = CreateHttpContext();
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Same(httpContext, httpContextArgument);
+ }
- Assert.Same(httpContext, httpContextArgument);
- }
+ [Fact]
+ public async Task RequestDelegatePassHttpContextRequestAbortedAsCancellationToken()
+ {
+ CancellationToken? cancellationTokenArgument = null;
- [Fact]
- public async Task RequestDelegatePassHttpContextRequestAbortedAsCancellationToken()
+ void TestAction(CancellationToken cancellationToken)
{
- CancellationToken? cancellationTokenArgument = null;
+ cancellationTokenArgument = cancellationToken;
+ }
- void TestAction(CancellationToken cancellationToken)
- {
- cancellationTokenArgument = cancellationToken;
- }
+ using var cts = new CancellationTokenSource();
+ var httpContext = CreateHttpContext();
+ // Reset back to default HttpRequestLifetimeFeature that implements a setter for RequestAborted.
+ httpContext.Features.Set<IHttpRequestLifetimeFeature>(new HttpRequestLifetimeFeature());
+ httpContext.RequestAborted = cts.Token;
- using var cts = new CancellationTokenSource();
- var httpContext = CreateHttpContext();
- // Reset back to default HttpRequestLifetimeFeature that implements a setter for RequestAborted.
- httpContext.Features.Set<IHttpRequestLifetimeFeature>(new HttpRequestLifetimeFeature());
- httpContext.RequestAborted = cts.Token;
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Equal(httpContext.RequestAborted, cancellationTokenArgument);
+ }
- Assert.Equal(httpContext.RequestAborted, cancellationTokenArgument);
- }
+ [Fact]
+ public async Task RequestDelegatePassHttpContextUserAsClaimsPrincipal()
+ {
+ ClaimsPrincipal? userArgument = null;
- [Fact]
- public async Task RequestDelegatePassHttpContextUserAsClaimsPrincipal()
+ void TestAction(ClaimsPrincipal user)
{
- ClaimsPrincipal? userArgument = null;
+ userArgument = user;
+ }
- void TestAction(ClaimsPrincipal user)
- {
- userArgument = user;
- }
+ var httpContext = CreateHttpContext();
+ httpContext.User = new ClaimsPrincipal();
- var httpContext = CreateHttpContext();
- httpContext.User = new ClaimsPrincipal();
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Equal(httpContext.User, userArgument);
+ }
- Assert.Equal(httpContext.User, userArgument);
- }
+ [Fact]
+ public async Task RequestDelegatePassHttpContextRequestAsHttpRequest()
+ {
+ HttpRequest? httpRequestArgument = null;
- [Fact]
- public async Task RequestDelegatePassHttpContextRequestAsHttpRequest()
+ void TestAction(HttpRequest httpRequest)
{
- HttpRequest? httpRequestArgument = null;
+ httpRequestArgument = httpRequest;
+ }
- void TestAction(HttpRequest httpRequest)
- {
- httpRequestArgument = httpRequest;
- }
+ var httpContext = CreateHttpContext();
- var httpContext = CreateHttpContext();
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Equal(httpContext.Request, httpRequestArgument);
+ }
- Assert.Equal(httpContext.Request, httpRequestArgument);
- }
+ [Fact]
+ public async Task RequestDelegatePassesHttpContextRresponseAsHttpResponse()
+ {
+ HttpResponse? httpResponseArgument = null;
- [Fact]
- public async Task RequestDelegatePassesHttpContextRresponseAsHttpResponse()
+ void TestAction(HttpResponse httpResponse)
{
- HttpResponse? httpResponseArgument = null;
-
- void TestAction(HttpResponse httpResponse)
- {
- httpResponseArgument = httpResponse;
- }
+ httpResponseArgument = httpResponse;
+ }
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- var factoryResult = RequestDelegateFactory.Create(TestAction);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(TestAction);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(httpContext.Response, httpResponseArgument);
- }
+ Assert.Equal(httpContext.Response, httpResponseArgument);
+ }
- public static IEnumerable<object[]> ComplexResult
+ public static IEnumerable<object[]> ComplexResult
+ {
+ get
{
- get
+ Todo originalTodo = new()
{
- Todo originalTodo = new()
- {
- Name = "Write even more tests!"
- };
+ Name = "Write even more tests!"
+ };
- Todo TestAction() => originalTodo;
- Task<Todo> TaskTestAction() => Task.FromResult(originalTodo);
- ValueTask<Todo> ValueTaskTestAction() => ValueTask.FromResult(originalTodo);
+ Todo TestAction() => originalTodo;
+ Task<Todo> TaskTestAction() => Task.FromResult(originalTodo);
+ ValueTask<Todo> ValueTaskTestAction() => ValueTask.FromResult(originalTodo);
- static Todo StaticTestAction() => new Todo { Name = "Write even more tests!" };
- static Task<Todo> StaticTaskTestAction() => Task.FromResult(new Todo { Name = "Write even more tests!" });
- static ValueTask<Todo> StaticValueTaskTestAction() => ValueTask.FromResult(new Todo { Name = "Write even more tests!" });
+ static Todo StaticTestAction() => new Todo { Name = "Write even more tests!" };
+ static Task<Todo> StaticTaskTestAction() => Task.FromResult(new Todo { Name = "Write even more tests!" });
+ static ValueTask<Todo> StaticValueTaskTestAction() => ValueTask.FromResult(new Todo { Name = "Write even more tests!" });
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<Todo>)TestAction },
new object[] { (Func<Task<Todo>>)TaskTestAction},
@@ -1947,67 +1947,67 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<Task<Todo>>)StaticTaskTestAction},
new object[] { (Func<ValueTask<Todo>>)StaticValueTaskTestAction},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(ComplexResult))]
- public async Task RequestDelegateWritesComplexReturnValueAsJsonResponseBody(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(ComplexResult))]
+ public async Task RequestDelegateWritesComplexReturnValueAsJsonResponseBody(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var deserializedResponseBody = JsonSerializer.Deserialize<Todo>(responseBodyStream.ToArray(), new JsonSerializerOptions
- {
- // TODO: the output is "{\"id\":0,\"name\":\"Write even more tests!\",\"isComplete\":false}"
- // Verify that the camelCased property names are consistent with MVC and if so whether we should keep the behavior.
- PropertyNameCaseInsensitive = true
- });
+ var deserializedResponseBody = JsonSerializer.Deserialize<Todo>(responseBodyStream.ToArray(), new JsonSerializerOptions
+ {
+ // TODO: the output is "{\"id\":0,\"name\":\"Write even more tests!\",\"isComplete\":false}"
+ // Verify that the camelCased property names are consistent with MVC and if so whether we should keep the behavior.
+ PropertyNameCaseInsensitive = true
+ });
- Assert.NotNull(deserializedResponseBody);
- Assert.Equal("Write even more tests!", deserializedResponseBody!.Name);
- }
+ Assert.NotNull(deserializedResponseBody);
+ Assert.Equal("Write even more tests!", deserializedResponseBody!.Name);
+ }
- public static IEnumerable<object[]> CustomResults
+ public static IEnumerable<object[]> CustomResults
+ {
+ get
{
- get
- {
- var resultString = "Still not enough tests!";
+ var resultString = "Still not enough tests!";
- CustomResult TestAction() => new CustomResult(resultString);
- Task<CustomResult> TaskTestAction() => Task.FromResult(new CustomResult(resultString));
- ValueTask<CustomResult> ValueTaskTestAction() => ValueTask.FromResult(new CustomResult(resultString));
+ CustomResult TestAction() => new CustomResult(resultString);
+ Task<CustomResult> TaskTestAction() => Task.FromResult(new CustomResult(resultString));
+ ValueTask<CustomResult> ValueTaskTestAction() => ValueTask.FromResult(new CustomResult(resultString));
- static CustomResult StaticTestAction() => new CustomResult("Still not enough tests!");
- static Task<CustomResult> StaticTaskTestAction() => Task.FromResult(new CustomResult("Still not enough tests!"));
- static ValueTask<CustomResult> StaticValueTaskTestAction() => ValueTask.FromResult(new CustomResult("Still not enough tests!"));
+ static CustomResult StaticTestAction() => new CustomResult("Still not enough tests!");
+ static Task<CustomResult> StaticTaskTestAction() => Task.FromResult(new CustomResult("Still not enough tests!"));
+ static ValueTask<CustomResult> StaticValueTaskTestAction() => ValueTask.FromResult(new CustomResult("Still not enough tests!"));
- // Object return type where the object is IResult
- static object StaticResultAsObject() => new CustomResult("Still not enough tests!");
- static object StaticResultAsTaskObject() => Task.FromResult<object>(new CustomResult("Still not enough tests!"));
- static object StaticResultAsValueTaskObject() => ValueTask.FromResult<object>(new CustomResult("Still not enough tests!"));
+ // Object return type where the object is IResult
+ static object StaticResultAsObject() => new CustomResult("Still not enough tests!");
+ static object StaticResultAsTaskObject() => Task.FromResult<object>(new CustomResult("Still not enough tests!"));
+ static object StaticResultAsValueTaskObject() => ValueTask.FromResult<object>(new CustomResult("Still not enough tests!"));
- // Object return type where the object is Task<IResult>
- static object StaticResultAsTaskIResult() => Task.FromResult<IResult>(new CustomResult("Still not enough tests!"));
+ // Object return type where the object is Task<IResult>
+ static object StaticResultAsTaskIResult() => Task.FromResult<IResult>(new CustomResult("Still not enough tests!"));
- // Object return type where the object is ValueTask<IResult>
- static object StaticResultAsValueTaskIResult() => ValueTask.FromResult<IResult>(new CustomResult("Still not enough tests!"));
+ // Object return type where the object is ValueTask<IResult>
+ static object StaticResultAsValueTaskIResult() => ValueTask.FromResult<IResult>(new CustomResult("Still not enough tests!"));
- // Task<object> return type
- static Task<object> StaticTaskOfIResultAsObject() => Task.FromResult<object>(new CustomResult("Still not enough tests!"));
- static ValueTask<object> StaticValueTaskOfIResultAsObject() => ValueTask.FromResult<object>(new CustomResult("Still not enough tests!"));
+ // Task<object> return type
+ static Task<object> StaticTaskOfIResultAsObject() => Task.FromResult<object>(new CustomResult("Still not enough tests!"));
+ static ValueTask<object> StaticValueTaskOfIResultAsObject() => ValueTask.FromResult<object>(new CustomResult("Still not enough tests!"));
- StructResult TestStructAction() => new StructResult(resultString);
- Task<StructResult> TaskTestStructAction() => Task.FromResult(new StructResult(resultString));
- ValueTask<StructResult> ValueTaskTestStructAction() => ValueTask.FromResult(new StructResult(resultString));
+ StructResult TestStructAction() => new StructResult(resultString);
+ Task<StructResult> TaskTestStructAction() => Task.FromResult(new StructResult(resultString));
+ ValueTask<StructResult> ValueTaskTestStructAction() => ValueTask.FromResult(new StructResult(resultString));
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<CustomResult>)TestAction },
new object[] { (Func<Task<CustomResult>>)TaskTestAction},
@@ -2030,53 +2030,53 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<Task<StructResult>>)TaskTestStructAction },
new object[] { (Func<ValueTask<StructResult>>)ValueTaskTestStructAction },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(CustomResults))]
- public async Task RequestDelegateUsesCustomIResult(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(CustomResults))]
+ public async Task RequestDelegateUsesCustomIResult(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal("Still not enough tests!", decodedResponseBody);
- }
+ Assert.Equal("Still not enough tests!", decodedResponseBody);
+ }
- public static IEnumerable<object[]> StringResult
+ public static IEnumerable<object[]> StringResult
+ {
+ get
{
- get
- {
- var test = "String Test";
+ var test = "String Test";
- string TestAction() => test;
- Task<string> TaskTestAction() => Task.FromResult(test);
- ValueTask<string> ValueTaskTestAction() => ValueTask.FromResult(test);
+ string TestAction() => test;
+ Task<string> TaskTestAction() => Task.FromResult(test);
+ ValueTask<string> ValueTaskTestAction() => ValueTask.FromResult(test);
- static string StaticTestAction() => "String Test";
- static Task<string> StaticTaskTestAction() => Task.FromResult("String Test");
- static ValueTask<string> StaticValueTaskTestAction() => ValueTask.FromResult("String Test");
+ static string StaticTestAction() => "String Test";
+ static Task<string> StaticTaskTestAction() => Task.FromResult("String Test");
+ static ValueTask<string> StaticValueTaskTestAction() => ValueTask.FromResult("String Test");
- // Dynamic via object
- static object StaticStringAsObjectTestAction() => "String Test";
- static object StaticTaskStringAsObjectTestAction() => Task.FromResult("String Test");
- static object StaticValueTaskStringAsObjectTestAction() => ValueTask.FromResult("String Test");
+ // Dynamic via object
+ static object StaticStringAsObjectTestAction() => "String Test";
+ static object StaticTaskStringAsObjectTestAction() => Task.FromResult("String Test");
+ static object StaticValueTaskStringAsObjectTestAction() => ValueTask.FromResult("String Test");
- // Dynamic via Task<object>
- static Task<object> StaticStringAsTaskObjectTestAction() => Task.FromResult<object>("String Test");
+ // Dynamic via Task<object>
+ static Task<object> StaticStringAsTaskObjectTestAction() => Task.FromResult<object>("String Test");
- // Dynamic via ValueTask<object>
- static ValueTask<object> StaticStringAsValueTaskObjectTestAction() => ValueTask.FromResult<object>("String Test");
+ // Dynamic via ValueTask<object>
+ static ValueTask<object> StaticStringAsValueTaskObjectTestAction() => ValueTask.FromResult<object>("String Test");
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<string>)TestAction },
new object[] { (Func<Task<string>>)TaskTestAction },
@@ -2094,56 +2094,56 @@ namespace Microsoft.AspNetCore.Routing.Internal
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(StringResult))]
- public async Task RequestDelegateWritesStringReturnValueAndSetContentTypeWhenNull(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(StringResult))]
+ public async Task RequestDelegateWritesStringReturnValueAndSetContentTypeWhenNull(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal("String Test", responseBody);
- Assert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType);
- }
+ Assert.Equal("String Test", responseBody);
+ Assert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType);
+ }
- [Theory]
- [MemberData(nameof(StringResult))]
- public async Task RequestDelegateWritesStringReturnDoNotChangeContentType(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- httpContext.Response.ContentType = "application/json; charset=utf-8";
+ [Theory]
+ [MemberData(nameof(StringResult))]
+ public async Task RequestDelegateWritesStringReturnDoNotChangeContentType(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ httpContext.Response.ContentType = "application/json; charset=utf-8";
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType);
- }
+ Assert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType);
+ }
- public static IEnumerable<object[]> IntResult
+ public static IEnumerable<object[]> IntResult
+ {
+ get
{
- get
- {
- int TestAction() => 42;
- Task<int> TaskTestAction() => Task.FromResult(42);
- ValueTask<int> ValueTaskTestAction() => ValueTask.FromResult(42);
+ int TestAction() => 42;
+ Task<int> TaskTestAction() => Task.FromResult(42);
+ ValueTask<int> ValueTaskTestAction() => ValueTask.FromResult(42);
- static int StaticTestAction() => 42;
- static Task<int> StaticTaskTestAction() => Task.FromResult(42);
- static ValueTask<int> StaticValueTaskTestAction() => ValueTask.FromResult(42);
+ static int StaticTestAction() => 42;
+ static Task<int> StaticTaskTestAction() => Task.FromResult(42);
+ static ValueTask<int> StaticValueTaskTestAction() => ValueTask.FromResult(42);
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<int>)TestAction },
new object[] { (Func<Task<int>>)TaskTestAction },
@@ -2152,40 +2152,40 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<Task<int>>)StaticTaskTestAction },
new object[] { (Func<ValueTask<int>>)StaticValueTaskTestAction },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(IntResult))]
- public async Task RequestDelegateWritesIntReturnValue(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(IntResult))]
+ public async Task RequestDelegateWritesIntReturnValue(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal("42", responseBody);
- }
+ Assert.Equal("42", responseBody);
+ }
- public static IEnumerable<object[]> BoolResult
+ public static IEnumerable<object[]> BoolResult
+ {
+ get
{
- get
- {
- bool TestAction() => true;
- Task<bool> TaskTestAction() => Task.FromResult(true);
- ValueTask<bool> ValueTaskTestAction() => ValueTask.FromResult(true);
+ bool TestAction() => true;
+ Task<bool> TaskTestAction() => Task.FromResult(true);
+ ValueTask<bool> ValueTaskTestAction() => ValueTask.FromResult(true);
- static bool StaticTestAction() => true;
- static Task<bool> StaticTaskTestAction() => Task.FromResult(true);
- static ValueTask<bool> StaticValueTaskTestAction() => ValueTask.FromResult(true);
+ static bool StaticTestAction() => true;
+ static Task<bool> StaticTaskTestAction() => Task.FromResult(true);
+ static ValueTask<bool> StaticValueTaskTestAction() => ValueTask.FromResult(true);
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<bool>)TestAction },
new object[] { (Func<Task<bool>>)TaskTestAction },
@@ -2194,38 +2194,38 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<Task<bool>>)StaticTaskTestAction },
new object[] { (Func<ValueTask<bool>>)StaticValueTaskTestAction },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(BoolResult))]
- public async Task RequestDelegateWritesBoolReturnValue(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(BoolResult))]
+ public async Task RequestDelegateWritesBoolReturnValue(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal("true", responseBody);
- }
+ Assert.Equal("true", responseBody);
+ }
- public static IEnumerable<object[]> NullResult
+ public static IEnumerable<object[]> NullResult
+ {
+ get
{
- get
- {
- IResult? TestAction() => null;
- Task<bool?>? TaskBoolAction() => null;
- Task<IResult?>? TaskNullAction() => null;
- Task<IResult?> TaskTestAction() => Task.FromResult<IResult?>(null);
- ValueTask<IResult?> ValueTaskTestAction() => ValueTask.FromResult<IResult?>(null);
+ IResult? TestAction() => null;
+ Task<bool?>? TaskBoolAction() => null;
+ Task<IResult?>? TaskNullAction() => null;
+ Task<IResult?> TaskTestAction() => Task.FromResult<IResult?>(null);
+ ValueTask<IResult?> ValueTaskTestAction() => ValueTask.FromResult<IResult?>(null);
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<IResult?>)TestAction, "The IResult returned by the Delegate must not be null." },
new object[] { (Func<Task<IResult?>?>)TaskNullAction, "The IResult in Task<IResult> response must not be null." },
@@ -2233,41 +2233,41 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<Task<IResult?>>)TaskTestAction, "The IResult returned by the Delegate must not be null." },
new object[] { (Func<ValueTask<IResult?>>)ValueTaskTestAction, "The IResult returned by the Delegate must not be null." },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(NullResult))]
- public async Task RequestDelegateThrowsInvalidOperationExceptionOnNullDelegate(Delegate @delegate, string message)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(NullResult))]
+ public async Task RequestDelegateThrowsInvalidOperationExceptionOnNullDelegate(Delegate @delegate, string message)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- var exception = await Assert.ThrowsAnyAsync<InvalidOperationException>(async () => await requestDelegate(httpContext));
- Assert.Contains(message, exception.Message);
- }
+ var exception = await Assert.ThrowsAnyAsync<InvalidOperationException>(async () => await requestDelegate(httpContext));
+ Assert.Contains(message, exception.Message);
+ }
- public static IEnumerable<object[]> NullContentResult
+ public static IEnumerable<object[]> NullContentResult
+ {
+ get
{
- get
- {
- bool? TestBoolAction() => null;
- Task<bool?> TaskTestBoolAction() => Task.FromResult<bool?>(null);
- ValueTask<bool?> ValueTaskTestBoolAction() => ValueTask.FromResult<bool?>(null);
+ bool? TestBoolAction() => null;
+ Task<bool?> TaskTestBoolAction() => Task.FromResult<bool?>(null);
+ ValueTask<bool?> ValueTaskTestBoolAction() => ValueTask.FromResult<bool?>(null);
- int? TestIntAction() => null;
- Task<int?> TaskTestIntAction() => Task.FromResult<int?>(null);
- ValueTask<int?> ValueTaskTestIntAction() => ValueTask.FromResult<int?>(null);
+ int? TestIntAction() => null;
+ Task<int?> TaskTestIntAction() => Task.FromResult<int?>(null);
+ ValueTask<int?> ValueTaskTestIntAction() => ValueTask.FromResult<int?>(null);
- Todo? TestTodoAction() => null;
- Task<Todo?> TaskTestTodoAction() => Task.FromResult<Todo?>(null);
- ValueTask<Todo?> ValueTaskTestTodoAction() => ValueTask.FromResult<Todo?>(null);
+ Todo? TestTodoAction() => null;
+ Task<Todo?> TaskTestTodoAction() => Task.FromResult<Todo?>(null);
+ ValueTask<Todo?> ValueTaskTestTodoAction() => ValueTask.FromResult<Todo?>(null);
- return new List<object[]>
+ return new List<object[]>
{
new object[] { (Func<bool?>)TestBoolAction },
new object[] { (Func<Task<bool?>>)TaskTestBoolAction },
@@ -2279,39 +2279,39 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object[] { (Func<Task<Todo?>>)TaskTestTodoAction },
new object[] { (Func<ValueTask<Todo?>>)ValueTaskTestTodoAction },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(NullContentResult))]
- public async Task RequestDelegateWritesNullReturnNullValue(Delegate @delegate)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(NullContentResult))]
+ public async Task RequestDelegateWritesNullReturnNullValue(Delegate @delegate)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ var responseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal("null", responseBody);
- }
+ Assert.Equal("null", responseBody);
+ }
- public static IEnumerable<object?[]> QueryParamOptionalityData
+ public static IEnumerable<object?[]> QueryParamOptionalityData
+ {
+ get
{
- get
- {
- string requiredQueryParam(string name) => $"Hello {name}!";
- string defaultValueQueryParam(string name = "DefaultName") => $"Hello {name}!";
- string nullableQueryParam(string? name) => $"Hello {name}!";
- string requiredParseableQueryParam(int age) => $"Age: {age}";
- string defaultValueParseableQueryParam(int age = 12) => $"Age: {age}";
- string nullableQueryParseableParam(int? age) => $"Age: {age}";
+ string requiredQueryParam(string name) => $"Hello {name}!";
+ string defaultValueQueryParam(string name = "DefaultName") => $"Hello {name}!";
+ string nullableQueryParam(string? name) => $"Hello {name}!";
+ string requiredParseableQueryParam(int age) => $"Age: {age}";
+ string defaultValueParseableQueryParam(int age = 12) => $"Age: {age}";
+ string nullableQueryParseableParam(int? age) => $"Age: {age}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<string, string>)requiredQueryParam, "name", null, true, null},
new object?[] { (Func<string, string>)requiredQueryParam, "name", "TestName", false, "Hello TestName!" },
@@ -2327,62 +2327,62 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object?[] { (Func<int?, string>)nullableQueryParseableParam, "age", null, false, "Age: " },
new object?[] { (Func<int?, string>)nullableQueryParseableParam, "age", "42", false, "Age: 42"},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(QueryParamOptionalityData))]
- public async Task RequestDelegateHandlesQueryParamOptionality(Delegate @delegate, string paramName, string? queryParam, bool isInvalid, string? expectedResponse)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(QueryParamOptionalityData))]
+ public async Task RequestDelegateHandlesQueryParamOptionality(Delegate @delegate, string paramName, string? queryParam, bool isInvalid, string? expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- if (queryParam is not null)
+ if (queryParam is not null)
+ {
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- [paramName] = queryParam
- });
- }
+ [paramName] = queryParam
+ });
+ }
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var logs = TestSink.Writes.ToArray();
+ var logs = TestSink.Writes.ToArray();
- if (isInvalid)
- {
- Assert.Equal(400, httpContext.Response.StatusCode);
- var log = Assert.Single(logs);
- Assert.Equal(LogLevel.Debug, log.LogLevel);
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
- var expectedType = paramName == "age" ? "int age" : "string name";
- Assert.Equal($@"Required parameter ""{expectedType}"" was not provided from route or query string.", log.Message);
- }
- else
- {
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ if (isInvalid)
+ {
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ var log = Assert.Single(logs);
+ Assert.Equal(LogLevel.Debug, log.LogLevel);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
+ var expectedType = paramName == "age" ? "int age" : "string name";
+ Assert.Equal($@"Required parameter ""{expectedType}"" was not provided from route or query string.", log.Message);
}
+ else
+ {
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
+ }
- public static IEnumerable<object?[]> RouteParamOptionalityData
+ public static IEnumerable<object?[]> RouteParamOptionalityData
+ {
+ get
{
- get
- {
- string requiredRouteParam(string name) => $"Hello {name}!";
- string defaultValueRouteParam(string name = "DefaultName") => $"Hello {name}!";
- string nullableRouteParam(string? name) => $"Hello {name}!";
- string requiredParseableRouteParam(int age) => $"Age: {age}";
- string defaultValueParseableRouteParam(int age = 12) => $"Age: {age}";
- string nullableParseableRouteParam(int? age) => $"Age: {age}";
+ string requiredRouteParam(string name) => $"Hello {name}!";
+ string defaultValueRouteParam(string name = "DefaultName") => $"Hello {name}!";
+ string nullableRouteParam(string? name) => $"Hello {name}!";
+ string requiredParseableRouteParam(int age) => $"Age: {age}";
+ string defaultValueParseableRouteParam(int age = 12) => $"Age: {age}";
+ string nullableParseableRouteParam(int? age) => $"Age: {age}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<string, string>)requiredRouteParam, "name", null, true, null},
new object?[] { (Func<string, string>)requiredRouteParam, "name", "TestName", false, "Hello TestName!" },
@@ -2398,60 +2398,60 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object?[] { (Func<int?, string>)nullableParseableRouteParam, "age", null, false, "Age: " },
new object?[] { (Func<int?, string>)nullableParseableRouteParam, "age", "42", false, "Age: 42"},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(RouteParamOptionalityData))]
- public async Task RequestDelegateHandlesRouteParamOptionality(Delegate @delegate, string paramName, string? routeParam, bool isInvalid, string? expectedResponse)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(RouteParamOptionalityData))]
+ public async Task RequestDelegateHandlesRouteParamOptionality(Delegate @delegate, string paramName, string? routeParam, bool isInvalid, string? expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- if (routeParam is not null)
- {
- httpContext.Request.RouteValues[paramName] = routeParam;
- }
+ if (routeParam is not null)
+ {
+ httpContext.Request.RouteValues[paramName] = routeParam;
+ }
- var factoryResult = RequestDelegateFactory.Create(@delegate, new()
- {
- RouteParameterNames = routeParam is not null ? new[] { paramName } : Array.Empty<string>()
- });
+ var factoryResult = RequestDelegateFactory.Create(@delegate, new()
+ {
+ RouteParameterNames = routeParam is not null ? new[] { paramName } : Array.Empty<string>()
+ });
- var requestDelegate = factoryResult.RequestDelegate;
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var logs = TestSink.Writes.ToArray();
+ var logs = TestSink.Writes.ToArray();
- if (isInvalid)
- {
- Assert.Equal(400, httpContext.Response.StatusCode);
- var log = Assert.Single(logs);
- Assert.Equal(LogLevel.Debug, log.LogLevel);
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
- var expectedType = paramName == "age" ? "int age" : "string name";
- Assert.Equal($@"Required parameter ""{expectedType}"" was not provided from query string.", log.Message);
- }
- else
- {
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ if (isInvalid)
+ {
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ var log = Assert.Single(logs);
+ Assert.Equal(LogLevel.Debug, log.LogLevel);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
+ var expectedType = paramName == "age" ? "int age" : "string name";
+ Assert.Equal($@"Required parameter ""{expectedType}"" was not provided from query string.", log.Message);
+ }
+ else
+ {
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
}
+ }
- public static IEnumerable<object?[]> BodyParamOptionalityData
+ public static IEnumerable<object?[]> BodyParamOptionalityData
+ {
+ get
{
- get
- {
- string requiredBodyParam(Todo todo) => $"Todo: {todo.Name}";
- string defaultValueBodyParam(Todo? todo = null) => $"Todo: {todo?.Name}";
- string nullableBodyParam(Todo? todo) => $"Todo: {todo?.Name}";
+ string requiredBodyParam(Todo todo) => $"Todo: {todo.Name}";
+ string defaultValueBodyParam(Todo? todo = null) => $"Todo: {todo?.Name}";
+ string nullableBodyParam(Todo? todo) => $"Todo: {todo?.Name}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<Todo, string>)requiredBodyParam, false, true, null },
new object?[] { (Func<Todo, string>)requiredBodyParam, true, false, "Todo: Default Todo"},
@@ -2460,101 +2460,101 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object?[] { (Func<Todo?, string>)nullableBodyParam, false, false, "Todo: " },
new object?[] { (Func<Todo?, string>)nullableBodyParam, true, false, "Todo: Default Todo" },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(BodyParamOptionalityData))]
- public async Task RequestDelegateHandlesBodyParamOptionality(Delegate @delegate, bool hasBody, bool isInvalid, string? expectedResponse)
+ [Theory]
+ [MemberData(nameof(BodyParamOptionalityData))]
+ public async Task RequestDelegateHandlesBodyParamOptionality(Delegate @delegate, bool hasBody, bool isInvalid, string? expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
+
+ if (hasBody)
{
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ var todo = new Todo() { Name = "Default Todo" };
+ var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(todo);
+ var stream = new MemoryStream(requestBodyBytes);
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "application/json";
+ httpContext.Request.ContentLength = stream.Length;
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ }
- if (hasBody)
- {
- var todo = new Todo() { Name = "Default Todo" };
- var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(todo);
- var stream = new MemoryStream(requestBodyBytes);
- httpContext.Request.Body = stream;
- httpContext.Request.Headers["Content-Type"] = "application/json";
- httpContext.Request.ContentLength = stream.Length;
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- }
+ var jsonOptions = new JsonOptions();
+ jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
- var jsonOptions = new JsonOptions();
- jsonOptions.SerializerOptions.Converters.Add(new TodoJsonConverter());
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(LoggerFactory);
+ serviceCollection.AddSingleton(Options.Create(jsonOptions));
+ httpContext.RequestServices = serviceCollection.BuildServiceProvider();
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(LoggerFactory);
- serviceCollection.AddSingleton(Options.Create(jsonOptions));
- httpContext.RequestServices = serviceCollection.BuildServiceProvider();
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var request = requestDelegate(httpContext);
- var request = requestDelegate(httpContext);
+ if (isInvalid)
+ {
+ var logs = TestSink.Writes.ToArray();
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ var log = Assert.Single(logs);
+ Assert.Equal(LogLevel.Debug, log.LogLevel);
+ Assert.Equal(new EventId(5, "ImplicitBodyNotProvided"), log.EventId);
+ Assert.Equal(@"Implicit body inferred for parameter ""todo"" but no body was provided. Did you mean to use a Service instead?", log.Message);
+ }
+ else
+ {
+ await request;
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
+ }
- if (isInvalid)
+ public static IEnumerable<object?[]> BindAsyncParamOptionalityData
+ {
+ get
+ {
+ void requiredReferenceType(HttpContext context, MyBindAsyncRecord myBindAsyncRecord)
{
- var logs = TestSink.Writes.ToArray();
- Assert.Equal(400, httpContext.Response.StatusCode);
- var log = Assert.Single(logs);
- Assert.Equal(LogLevel.Debug, log.LogLevel);
- Assert.Equal(new EventId(5, "ImplicitBodyNotProvided"), log.EventId);
- Assert.Equal(@"Implicit body inferred for parameter ""todo"" but no body was provided. Did you mean to use a Service instead?", log.Message);
+ context.Items["uri"] = myBindAsyncRecord.Uri;
}
- else
+ void defaultReferenceType(HttpContext context, MyBindAsyncRecord? myBindAsyncRecord = null)
{
- await request;
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
+ context.Items["uri"] = myBindAsyncRecord?.Uri;
}
- }
-
- public static IEnumerable<object?[]> BindAsyncParamOptionalityData
- {
- get
+ void nullableReferenceType(HttpContext context, MyBindAsyncRecord? myBindAsyncRecord)
{
- void requiredReferenceType(HttpContext context, MyBindAsyncRecord myBindAsyncRecord)
- {
- context.Items["uri"] = myBindAsyncRecord.Uri;
- }
- void defaultReferenceType(HttpContext context, MyBindAsyncRecord? myBindAsyncRecord = null)
- {
- context.Items["uri"] = myBindAsyncRecord?.Uri;
- }
- void nullableReferenceType(HttpContext context, MyBindAsyncRecord? myBindAsyncRecord)
- {
- context.Items["uri"] = myBindAsyncRecord?.Uri;
- }
- void requiredReferenceTypeSimple(HttpContext context, MySimpleBindAsyncRecord mySimpleBindAsyncRecord)
- {
- context.Items["uri"] = mySimpleBindAsyncRecord.Uri;
- }
+ context.Items["uri"] = myBindAsyncRecord?.Uri;
+ }
+ void requiredReferenceTypeSimple(HttpContext context, MySimpleBindAsyncRecord mySimpleBindAsyncRecord)
+ {
+ context.Items["uri"] = mySimpleBindAsyncRecord.Uri;
+ }
- void requiredValueType(HttpContext context, MyNullableBindAsyncStruct myNullableBindAsyncStruct)
- {
- context.Items["uri"] = myNullableBindAsyncStruct.Uri;
- }
- void defaultValueType(HttpContext context, MyNullableBindAsyncStruct? myNullableBindAsyncStruct = null)
- {
- context.Items["uri"] = myNullableBindAsyncStruct?.Uri;
- }
- void nullableValueType(HttpContext context, MyNullableBindAsyncStruct? myNullableBindAsyncStruct)
- {
- context.Items["uri"] = myNullableBindAsyncStruct?.Uri;
- }
- void requiredValueTypeSimple(HttpContext context, MySimpleBindAsyncStruct mySimpleBindAsyncStruct)
- {
- context.Items["uri"] = mySimpleBindAsyncStruct.Uri;
- }
+ void requiredValueType(HttpContext context, MyNullableBindAsyncStruct myNullableBindAsyncStruct)
+ {
+ context.Items["uri"] = myNullableBindAsyncStruct.Uri;
+ }
+ void defaultValueType(HttpContext context, MyNullableBindAsyncStruct? myNullableBindAsyncStruct = null)
+ {
+ context.Items["uri"] = myNullableBindAsyncStruct?.Uri;
+ }
+ void nullableValueType(HttpContext context, MyNullableBindAsyncStruct? myNullableBindAsyncStruct)
+ {
+ context.Items["uri"] = myNullableBindAsyncStruct?.Uri;
+ }
+ void requiredValueTypeSimple(HttpContext context, MySimpleBindAsyncStruct mySimpleBindAsyncStruct)
+ {
+ context.Items["uri"] = mySimpleBindAsyncStruct.Uri;
+ }
- return new object?[][]
- {
+ return new object?[][]
+ {
new object?[] { (Action<HttpContext, MyBindAsyncRecord>)requiredReferenceType, false, true, false },
new object?[] { (Action<HttpContext, MyBindAsyncRecord>)requiredReferenceType, true, false, false, },
new object?[] { (Action<HttpContext, MySimpleBindAsyncRecord>)requiredReferenceTypeSimple, true, false, false },
@@ -2574,68 +2574,68 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object?[] { (Action<HttpContext, MyNullableBindAsyncStruct?>)nullableValueType, false, false, true },
new object?[] { (Action<HttpContext, MyNullableBindAsyncStruct?>)nullableValueType, true, false, true },
- };
- }
+ };
}
+ }
- [Theory]
- [MemberData(nameof(BindAsyncParamOptionalityData))]
- public async Task RequestDelegateHandlesBindAsyncOptionality(Delegate routeHandler, bool includeReferer, bool isInvalid, bool isStruct)
+ [Theory]
+ [MemberData(nameof(BindAsyncParamOptionalityData))]
+ public async Task RequestDelegateHandlesBindAsyncOptionality(Delegate routeHandler, bool includeReferer, bool isInvalid, bool isStruct)
+ {
+ var httpContext = CreateHttpContext();
+
+ if (includeReferer)
{
- var httpContext = CreateHttpContext();
+ httpContext.Request.Headers.Referer = "https://example.org";
+ }
- if (includeReferer)
- {
- httpContext.Request.Headers.Referer = "https://example.org";
- }
+ var factoryResult = RequestDelegateFactory.Create(routeHandler);
- var factoryResult = RequestDelegateFactory.Create(routeHandler);
+ var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ if (isInvalid)
+ {
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ var log = Assert.Single(TestSink.Writes);
+ Assert.Equal(LogLevel.Debug, log.LogLevel);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
- if (isInvalid)
+ if (isStruct)
{
- Assert.Equal(400, httpContext.Response.StatusCode);
- var log = Assert.Single(TestSink.Writes);
- Assert.Equal(LogLevel.Debug, log.LogLevel);
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
-
- if (isStruct)
- {
- Assert.Equal(@"Required parameter ""MyNullableBindAsyncStruct myNullableBindAsyncStruct"" was not provided from MyNullableBindAsyncStruct.BindAsync(HttpContext, ParameterInfo).", log.Message);
- }
- else
- {
- Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", log.Message);
- }
+ Assert.Equal(@"Required parameter ""MyNullableBindAsyncStruct myNullableBindAsyncStruct"" was not provided from MyNullableBindAsyncStruct.BindAsync(HttpContext, ParameterInfo).", log.Message);
}
else
{
- Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.Equal(@"Required parameter ""MyBindAsyncRecord myBindAsyncRecord"" was not provided from MyBindAsyncRecord.BindAsync(HttpContext, ParameterInfo).", log.Message);
+ }
+ }
+ else
+ {
+ Assert.Equal(200, httpContext.Response.StatusCode);
- if (includeReferer)
- {
- Assert.Equal(new Uri("https://example.org"), httpContext.Items["uri"]);
- }
- else
- {
- Assert.Null(httpContext.Items["uri"]);
- }
+ if (includeReferer)
+ {
+ Assert.Equal(new Uri("https://example.org"), httpContext.Items["uri"]);
+ }
+ else
+ {
+ Assert.Null(httpContext.Items["uri"]);
}
}
+ }
- public static IEnumerable<object?[]> ServiceParamOptionalityData
+ public static IEnumerable<object?[]> ServiceParamOptionalityData
+ {
+ get
{
- get
- {
- string requiredExplicitService([FromService] MyService service) => $"Service: {service}";
- string defaultValueExplicitServiceParam([FromService] MyService? service = null) => $"Service: {service}";
- string nullableExplicitServiceParam([FromService] MyService? service) => $"Service: {service}";
+ string requiredExplicitService([FromService] MyService service) => $"Service: {service}";
+ string defaultValueExplicitServiceParam([FromService] MyService? service = null) => $"Service: {service}";
+ string nullableExplicitServiceParam([FromService] MyService? service) => $"Service: {service}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<MyService, string>)requiredExplicitService, false, true},
new object?[] { (Func<MyService, string>)requiredExplicitService, true, false},
@@ -2646,303 +2646,303 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object?[] { (Func<MyService?, string>)nullableExplicitServiceParam, false, false},
new object?[] { (Func<MyService?, string>)nullableExplicitServiceParam, true, false},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(ServiceParamOptionalityData))]
- public async Task RequestDelegateHandlesServiceParamOptionality(Delegate @delegate, bool hasService, bool isInvalid)
- {
- var httpContext = CreateHttpContext();
+ [Theory]
+ [MemberData(nameof(ServiceParamOptionalityData))]
+ public async Task RequestDelegateHandlesServiceParamOptionality(Delegate @delegate, bool hasService, bool isInvalid)
+ {
+ var httpContext = CreateHttpContext();
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(LoggerFactory);
- if (hasService)
- {
- var service = new MyService();
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(LoggerFactory);
+ if (hasService)
+ {
+ var service = new MyService();
- serviceCollection.AddSingleton(service);
- }
- var services = serviceCollection.BuildServiceProvider();
- httpContext.RequestServices = services;
- RequestDelegateFactoryOptions options = new() { ServiceProvider = services };
+ serviceCollection.AddSingleton(service);
+ }
+ var services = serviceCollection.BuildServiceProvider();
+ httpContext.RequestServices = services;
+ RequestDelegateFactoryOptions options = new() { ServiceProvider = services };
- var factoryResult = RequestDelegateFactory.Create(@delegate, options);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate, options);
+ var requestDelegate = factoryResult.RequestDelegate;
- if (!isInvalid)
- {
- await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- }
- else
- {
- await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegate(httpContext));
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- }
+ if (!isInvalid)
+ {
+ await requestDelegate(httpContext);
+ Assert.Equal(200, httpContext.Response.StatusCode);
}
+ else
+ {
+ await Assert.ThrowsAsync<InvalidOperationException>(() => requestDelegate(httpContext));
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ }
+ }
- public static IEnumerable<object?[]> AllowEmptyData
+ public static IEnumerable<object?[]> AllowEmptyData
+ {
+ get
{
- get
- {
- string disallowEmptyAndNonOptional([FromBody(AllowEmpty = false)] Todo todo) => $"{todo}";
- string allowEmptyAndNonOptional([FromBody(AllowEmpty = true)] Todo todo) => $"{todo}";
- string allowEmptyAndOptional([FromBody(AllowEmpty = true)] Todo? todo = null) => $"{todo}";
- string disallowEmptyAndOptional([FromBody(AllowEmpty = false)] Todo? todo = null) => $"{todo}";
+ string disallowEmptyAndNonOptional([FromBody(AllowEmpty = false)] Todo todo) => $"{todo}";
+ string allowEmptyAndNonOptional([FromBody(AllowEmpty = true)] Todo todo) => $"{todo}";
+ string allowEmptyAndOptional([FromBody(AllowEmpty = true)] Todo? todo = null) => $"{todo}";
+ string disallowEmptyAndOptional([FromBody(AllowEmpty = false)] Todo? todo = null) => $"{todo}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<Todo, string>)disallowEmptyAndNonOptional, false },
new object?[] { (Func<Todo, string>)allowEmptyAndNonOptional, true },
new object?[] { (Func<Todo, string>)allowEmptyAndOptional, true },
new object?[] { (Func<Todo, string>)disallowEmptyAndOptional, true }
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(AllowEmptyData))]
- public async Task AllowEmptyOverridesOptionality(Delegate @delegate, bool allowsEmptyRequest)
- {
- var httpContext = CreateHttpContext();
+ [Theory]
+ [MemberData(nameof(AllowEmptyData))]
+ public async Task AllowEmptyOverridesOptionality(Delegate @delegate, bool allowsEmptyRequest)
+ {
+ var httpContext = CreateHttpContext();
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- var logs = TestSink.Writes.ToArray();
+ var logs = TestSink.Writes.ToArray();
- if (!allowsEmptyRequest)
- {
- Assert.Equal(400, httpContext.Response.StatusCode);
- var log = Assert.Single(logs);
- Assert.Equal(LogLevel.Debug, log.LogLevel);
- Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
- Assert.Equal(@"Required parameter ""Todo todo"" was not provided from body.", log.Message);
- }
- else
- {
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- }
+ if (!allowsEmptyRequest)
+ {
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ var log = Assert.Single(logs);
+ Assert.Equal(LogLevel.Debug, log.LogLevel);
+ Assert.Equal(new EventId(4, "RequiredParameterNotProvided"), log.EventId);
+ Assert.Equal(@"Required parameter ""Todo todo"" was not provided from body.", log.Message);
}
+ else
+ {
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ }
+ }
#nullable disable
- [Theory]
- [InlineData(true, "Hello TestName!")]
- [InlineData(false, "Hello !")]
- public async Task CanSetStringParamAsOptionalWithNullabilityDisability(bool provideValue, string expectedResponse)
- {
- string optionalQueryParam(string name = null) => $"Hello {name}!";
+ [Theory]
+ [InlineData(true, "Hello TestName!")]
+ [InlineData(false, "Hello !")]
+ public async Task CanSetStringParamAsOptionalWithNullabilityDisability(bool provideValue, string expectedResponse)
+ {
+ string optionalQueryParam(string name = null) => $"Hello {name}!";
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- if (provideValue)
+ if (provideValue)
+ {
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["name"] = "TestName"
- });
- }
+ ["name"] = "TestName"
+ });
+ }
- var factoryResult = RequestDelegateFactory.Create(optionalQueryParam);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(optionalQueryParam);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
- [Theory]
- [InlineData(true, "Age: 42")]
- [InlineData(false, "Age: 0")]
- public async Task CanSetParseableStringParamAsOptionalWithNullabilityDisability(bool provideValue, string expectedResponse)
- {
- string optionalQueryParam(int age = default(int)) => $"Age: {age}";
+ [Theory]
+ [InlineData(true, "Age: 42")]
+ [InlineData(false, "Age: 0")]
+ public async Task CanSetParseableStringParamAsOptionalWithNullabilityDisability(bool provideValue, string expectedResponse)
+ {
+ string optionalQueryParam(int age = default(int)) => $"Age: {age}";
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- if (provideValue)
+ if (provideValue)
+ {
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["age"] = "42"
- });
- }
+ ["age"] = "42"
+ });
+ }
- var factoryResult = RequestDelegateFactory.Create(optionalQueryParam);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(optionalQueryParam);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
- [Theory]
- [InlineData(true, "Age: 42")]
- [InlineData(false, "Age: ")]
- public async Task TreatsUnknownNullabilityAsOptionalForReferenceType(bool provideValue, string expectedResponse)
- {
- string optionalQueryParam(string age) => $"Age: {age}";
+ [Theory]
+ [InlineData(true, "Age: 42")]
+ [InlineData(false, "Age: ")]
+ public async Task TreatsUnknownNullabilityAsOptionalForReferenceType(bool provideValue, string expectedResponse)
+ {
+ string optionalQueryParam(string age) => $"Age: {age}";
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- if (provideValue)
+ if (provideValue)
+ {
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["age"] = "42"
- });
- }
+ ["age"] = "42"
+ });
+ }
- var factoryResult = RequestDelegateFactory.Create(optionalQueryParam);
+ var factoryResult = RequestDelegateFactory.Create(optionalQueryParam);
- var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
#nullable enable
- [Fact]
- public async Task CanExecuteRequestDelegateWithResultsExtension()
- {
- IResult actionWithExtensionsResult(string name) => Results.Extensions.TestResult(name);
+ [Fact]
+ public async Task CanExecuteRequestDelegateWithResultsExtension()
+ {
+ IResult actionWithExtensionsResult(string name) => Results.Extensions.TestResult(name);
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["name"] = "Tester"
- });
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["name"] = "Tester"
+ });
- var factoryResult = RequestDelegateFactory.Create(actionWithExtensionsResult);
+ var factoryResult = RequestDelegateFactory.Create(actionWithExtensionsResult);
- var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(@"""Hello Tester. This is from an extension method.""", decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(@"""Hello Tester. This is from an extension method.""", decodedResponseBody);
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task RequestDelegateRejectsNonJsonContent(bool shouldThrow)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.Headers["Content-Type"] = "application/xml";
- httpContext.Request.Headers["Content-Length"] = "1";
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task RequestDelegateRejectsNonJsonContent(bool shouldThrow)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Headers["Content-Type"] = "application/xml";
+ httpContext.Request.Headers["Content-Length"] = "1";
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(LoggerFactory);
- httpContext.RequestServices = serviceCollection.BuildServiceProvider();
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(LoggerFactory);
+ httpContext.RequestServices = serviceCollection.BuildServiceProvider();
- var factoryResult = RequestDelegateFactory.Create((HttpContext context, Todo todo) =>
- {
- }, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = shouldThrow });
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create((HttpContext context, Todo todo) =>
+ {
+ }, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = shouldThrow });
+ var requestDelegate = factoryResult.RequestDelegate;
- var request = requestDelegate(httpContext);
+ var request = requestDelegate(httpContext);
- if (shouldThrow)
- {
- var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => request);
- Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", ex.Message);
- Assert.Equal(StatusCodes.Status415UnsupportedMediaType, ex.StatusCode);
- }
- else
- {
- await request;
+ if (shouldThrow)
+ {
+ var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => request);
+ Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", ex.Message);
+ Assert.Equal(StatusCodes.Status415UnsupportedMediaType, ex.StatusCode);
+ }
+ else
+ {
+ await request;
- Assert.Equal(415, httpContext.Response.StatusCode);
- var logMessage = Assert.Single(TestSink.Writes);
- Assert.Equal(new EventId(6, "UnexpectedContentType"), logMessage.EventId);
- Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
- Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", logMessage.Message);
- }
+ Assert.Equal(415, httpContext.Response.StatusCode);
+ var logMessage = Assert.Single(TestSink.Writes);
+ Assert.Equal(new EventId(6, "UnexpectedContentType"), logMessage.EventId);
+ Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+ Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", logMessage.Message);
}
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task RequestDelegateWithBindAndImplicitBodyRejectsNonJsonContent(bool shouldThrow)
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task RequestDelegateWithBindAndImplicitBodyRejectsNonJsonContent(bool shouldThrow)
+ {
+ Todo originalTodo = new()
{
- Todo originalTodo = new()
- {
- Name = "Write more tests!"
- };
+ Name = "Write more tests!"
+ };
- var httpContext = new DefaultHttpContext();
+ var httpContext = new DefaultHttpContext();
- var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
- var stream = new MemoryStream(requestBodyBytes);
- httpContext.Request.Body = stream;
- httpContext.Request.Headers["Content-Type"] = "application/xml";
- httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
- httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
+ var requestBodyBytes = JsonSerializer.SerializeToUtf8Bytes(originalTodo);
+ var stream = new MemoryStream(requestBodyBytes);
+ httpContext.Request.Body = stream;
+ httpContext.Request.Headers["Content-Type"] = "application/xml";
+ httpContext.Request.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
+ httpContext.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(true));
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton(LoggerFactory);
- httpContext.RequestServices = serviceCollection.BuildServiceProvider();
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddSingleton(LoggerFactory);
+ httpContext.RequestServices = serviceCollection.BuildServiceProvider();
- var factoryResult = RequestDelegateFactory.Create((HttpContext context, JsonTodo customTodo, Todo todo) =>
- {
- }, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = shouldThrow });
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create((HttpContext context, JsonTodo customTodo, Todo todo) =>
+ {
+ }, new RequestDelegateFactoryOptions() { ThrowOnBadRequest = shouldThrow });
+ var requestDelegate = factoryResult.RequestDelegate;
- var request = requestDelegate(httpContext);
+ var request = requestDelegate(httpContext);
- if (shouldThrow)
- {
- var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => request);
- Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", ex.Message);
- Assert.Equal(StatusCodes.Status415UnsupportedMediaType, ex.StatusCode);
- }
- else
- {
- await request;
+ if (shouldThrow)
+ {
+ var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => request);
+ Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", ex.Message);
+ Assert.Equal(StatusCodes.Status415UnsupportedMediaType, ex.StatusCode);
+ }
+ else
+ {
+ await request;
- Assert.Equal(415, httpContext.Response.StatusCode);
- var logMessage = Assert.Single(TestSink.Writes);
- Assert.Equal(new EventId(6, "UnexpectedContentType"), logMessage.EventId);
- Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
- Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", logMessage.Message);
- }
+ Assert.Equal(415, httpContext.Response.StatusCode);
+ var logMessage = Assert.Single(TestSink.Writes);
+ Assert.Equal(new EventId(6, "UnexpectedContentType"), logMessage.EventId);
+ Assert.Equal(LogLevel.Debug, logMessage.LogLevel);
+ Assert.Equal("Expected a supported JSON media type but got \"application/xml\".", logMessage.Message);
}
+ }
- public static IEnumerable<object?[]> DateTimeDelegates
+ public static IEnumerable<object?[]> DateTimeDelegates
+ {
+ get
{
- get
- {
- string dateTimeParsing(DateTime time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}, Kind: {time.Kind}";
+ string dateTimeParsing(DateTime time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}, Kind: {time.Kind}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<DateTime, string>)dateTimeParsing, "9/20/2021 4:18:44 PM", "Time: 2021-09-20T16:18:44.0000000, Kind: Unspecified" },
new object?[] { (Func<DateTime, string>)dateTimeParsing, "2021-09-20 4:18:44", "Time: 2021-09-20T04:18:44.0000000, Kind: Unspecified" },
@@ -2953,490 +2953,489 @@ namespace Microsoft.AspNetCore.Routing.Internal
new object?[] { (Func<DateTime, string>)dateTimeParsing, " 2021-09-20T23:30: 02.000+00:00 ", "Time: 2021-09-20T23:30:02.0000000Z, Kind: Utc" },
new object?[] { (Func<DateTime, string>)dateTimeParsing, "2021-09-20 16:48:02-07:00", "Time: 2021-09-20T23:48:02.0000000Z, Kind: Utc" },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DateTimeDelegates))]
- public async Task RequestDelegateCanProcessDateTimesToUtc(Delegate @delegate, string inputTime, string expectedResponse)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(DateTimeDelegates))]
+ public async Task RequestDelegateCanProcessDateTimesToUtc(Delegate @delegate, string inputTime, string expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["time"] = inputTime
- });
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["time"] = inputTime
+ });
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
- public static IEnumerable<object?[]> DateTimeOffsetDelegates
+ public static IEnumerable<object?[]> DateTimeOffsetDelegates
+ {
+ get
{
- get
- {
- string dateTimeOffsetParsing(DateTimeOffset time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}, Offset: {time.Offset}";
+ string dateTimeOffsetParsing(DateTimeOffset time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}, Offset: {time.Offset}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<DateTimeOffset, string>)dateTimeOffsetParsing, "09/20/2021 16:35:12 +00:00", "Time: 2021-09-20T16:35:12.0000000+00:00, Offset: 00:00:00" },
new object?[] { (Func<DateTimeOffset, string>)dateTimeOffsetParsing, "09/20/2021 11:35:12 +07:00", "Time: 2021-09-20T11:35:12.0000000+07:00, Offset: 07:00:00" },
new object?[] { (Func<DateTimeOffset, string>)dateTimeOffsetParsing, "09/20/2021 16:35:12", "Time: 2021-09-20T16:35:12.0000000+00:00, Offset: 00:00:00" },
new object?[] { (Func<DateTimeOffset, string>)dateTimeOffsetParsing, " 09/20/2021 16:35:12 ", "Time: 2021-09-20T16:35:12.0000000+00:00, Offset: 00:00:00" },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DateTimeOffsetDelegates))]
- public async Task RequestDelegateCanProcessDateTimeOffsetsToUtc(Delegate @delegate, string inputTime, string expectedResponse)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(DateTimeOffsetDelegates))]
+ public async Task RequestDelegateCanProcessDateTimeOffsetsToUtc(Delegate @delegate, string inputTime, string expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["time"] = inputTime
- });
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["time"] = inputTime
+ });
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
- public static IEnumerable<object?[]> DateOnlyDelegates
+ public static IEnumerable<object?[]> DateOnlyDelegates
+ {
+ get
{
- get
- {
- string dateOnlyParsing(DateOnly time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}";
+ string dateOnlyParsing(DateOnly time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<DateOnly, string>)dateOnlyParsing, "9/20/2021", "Time: 2021-09-20" },
new object?[] { (Func<DateOnly, string>)dateOnlyParsing, "9 /20 /2021", "Time: 2021-09-20" },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DateOnlyDelegates))]
- public async Task RequestDelegateCanProcessDateOnlyValues(Delegate @delegate, string inputTime, string expectedResponse)
- {
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ [Theory]
+ [MemberData(nameof(DateOnlyDelegates))]
+ public async Task RequestDelegateCanProcessDateOnlyValues(Delegate @delegate, string inputTime, string expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["time"] = inputTime
- });
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["time"] = inputTime
+ });
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- await requestDelegate(httpContext);
+ await requestDelegate(httpContext);
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
- public static IEnumerable<object?[]> TimeOnlyDelegates
+ public static IEnumerable<object?[]> TimeOnlyDelegates
+ {
+ get
{
- get
- {
- string timeOnlyParsing(TimeOnly time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}";
+ string timeOnlyParsing(TimeOnly time) => $"Time: {time.ToString("O", CultureInfo.InvariantCulture)}";
- return new List<object?[]>
+ return new List<object?[]>
{
new object?[] { (Func<TimeOnly, string>)timeOnlyParsing, "4:34 PM", "Time: 16:34:00.0000000" },
new object?[] { (Func<TimeOnly, string>)timeOnlyParsing, " 4:34 PM ", "Time: 16:34:00.0000000" },
};
- }
}
+ }
+
+ [Theory]
+ [MemberData(nameof(TimeOnlyDelegates))]
+ public async Task RequestDelegateCanProcessTimeOnlyValues(Delegate @delegate, string inputTime, string expectedResponse)
+ {
+ var httpContext = CreateHttpContext();
+ var responseBodyStream = new MemoryStream();
+ httpContext.Response.Body = responseBodyStream;
- [Theory]
- [MemberData(nameof(TimeOnlyDelegates))]
- public async Task RequestDelegateCanProcessTimeOnlyValues(Delegate @delegate, string inputTime, string expectedResponse)
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
{
- var httpContext = CreateHttpContext();
- var responseBodyStream = new MemoryStream();
- httpContext.Response.Body = responseBodyStream;
+ ["time"] = inputTime
+ });
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["time"] = inputTime
- });
+ var factoryResult = RequestDelegateFactory.Create(@delegate);
+ var requestDelegate = factoryResult.RequestDelegate;
- var factoryResult = RequestDelegateFactory.Create(@delegate);
- var requestDelegate = factoryResult.RequestDelegate;
+ await requestDelegate(httpContext);
- await requestDelegate(httpContext);
+ Assert.Equal(200, httpContext.Response.StatusCode);
+ Assert.False(httpContext.RequestAborted.IsCancellationRequested);
+ var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
+ Assert.Equal(expectedResponse, decodedResponseBody);
+ }
- Assert.Equal(200, httpContext.Response.StatusCode);
- Assert.False(httpContext.RequestAborted.IsCancellationRequested);
- var decodedResponseBody = Encoding.UTF8.GetString(responseBodyStream.ToArray());
- Assert.Equal(expectedResponse, decodedResponseBody);
- }
+ private DefaultHttpContext CreateHttpContext()
+ {
+ var responseFeature = new TestHttpResponseFeature();
- private DefaultHttpContext CreateHttpContext()
+ return new()
{
- var responseFeature = new TestHttpResponseFeature();
-
- return new()
- {
- RequestServices = new ServiceCollection().AddSingleton(LoggerFactory).BuildServiceProvider(),
- Features =
+ RequestServices = new ServiceCollection().AddSingleton(LoggerFactory).BuildServiceProvider(),
+ Features =
{
[typeof(IHttpResponseFeature)] = responseFeature,
[typeof(IHttpResponseBodyFeature)] = responseFeature,
[typeof(IHttpRequestLifetimeFeature)] = new TestHttpRequestLifetimeFeature(),
}
- };
- }
+ };
+ }
- private class Todo : ITodo
- {
- public int Id { get; set; }
- public string? Name { get; set; } = "Todo";
- public bool IsComplete { get; set; }
- }
+ private class Todo : ITodo
+ {
+ public int Id { get; set; }
+ public string? Name { get; set; } = "Todo";
+ public bool IsComplete { get; set; }
+ }
- private class CustomTodo : Todo
+ private class CustomTodo : Todo
+ {
+ public static async ValueTask<CustomTodo?> BindAsync(HttpContext context, ParameterInfo parameter)
{
- public static async ValueTask<CustomTodo?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- Assert.Equal(typeof(CustomTodo), parameter.ParameterType);
- Assert.Equal("customTodo", parameter.Name);
+ Assert.Equal(typeof(CustomTodo), parameter.ParameterType);
+ Assert.Equal("customTodo", parameter.Name);
- var body = await context.Request.ReadFromJsonAsync<CustomTodo>();
- context.Request.Body.Position = 0;
- return body;
- }
+ var body = await context.Request.ReadFromJsonAsync<CustomTodo>();
+ context.Request.Body.Position = 0;
+ return body;
}
+ }
- private class JsonTodo : Todo
+ private class JsonTodo : Todo
+ {
+ public static async ValueTask<JsonTodo?> BindAsync(HttpContext context, ParameterInfo parameter)
{
- public static async ValueTask<JsonTodo?> BindAsync(HttpContext context, ParameterInfo parameter)
- {
- // manually call deserialize so we don't check content type
- var body = await JsonSerializer.DeserializeAsync<JsonTodo>(context.Request.Body);
- context.Request.Body.Position = 0;
- return body;
- }
+ // manually call deserialize so we don't check content type
+ var body = await JsonSerializer.DeserializeAsync<JsonTodo>(context.Request.Body);
+ context.Request.Body.Position = 0;
+ return body;
}
+ }
- private record struct TodoStruct(int Id, string? Name, bool IsComplete) : ITodo;
+ private record struct TodoStruct(int Id, string? Name, bool IsComplete) : ITodo;
- private interface ITodo
- {
- public int Id { get; }
- public string? Name { get; }
- public bool IsComplete { get; }
- }
+ private interface ITodo
+ {
+ public int Id { get; }
+ public string? Name { get; }
+ public bool IsComplete { get; }
+ }
- class TodoJsonConverter : JsonConverter<ITodo>
+ class TodoJsonConverter : JsonConverter<ITodo>
+ {
+ public override ITodo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- public override ITodo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ var todo = new Todo();
+ while (reader.Read())
{
- var todo = new Todo();
- while (reader.Read())
+ if (reader.TokenType == JsonTokenType.EndObject)
{
- if (reader.TokenType == JsonTokenType.EndObject)
- {
- break;
- }
-
- var property = reader.GetString()!;
- reader.Read();
-
- switch (property.ToLowerInvariant())
- {
- case "id":
- todo.Id = reader.GetInt32();
- break;
- case "name":
- todo.Name = reader.GetString();
- break;
- case "iscomplete":
- todo.IsComplete = reader.GetBoolean();
- break;
- default:
- break;
- }
+ break;
}
- return todo;
- }
+ var property = reader.GetString()!;
+ reader.Read();
- public override void Write(Utf8JsonWriter writer, ITodo value, JsonSerializerOptions options)
- {
- throw new NotImplementedException();
+ switch (property.ToLowerInvariant())
+ {
+ case "id":
+ todo.Id = reader.GetInt32();
+ break;
+ case "name":
+ todo.Name = reader.GetString();
+ break;
+ case "iscomplete":
+ todo.IsComplete = reader.GetBoolean();
+ break;
+ default:
+ break;
+ }
}
- }
- private struct BodyStruct
- {
- public int Id { get; set; }
+ return todo;
}
- private class FromRouteAttribute : Attribute, IFromRouteMetadata
+ public override void Write(Utf8JsonWriter writer, ITodo value, JsonSerializerOptions options)
{
- public string? Name { get; set; }
+ throw new NotImplementedException();
}
+ }
- private class FromQueryAttribute : Attribute, IFromQueryMetadata
- {
- public string? Name { get; set; }
- }
+ private struct BodyStruct
+ {
+ public int Id { get; set; }
+ }
- private class FromHeaderAttribute : Attribute, IFromHeaderMetadata
- {
- public string? Name { get; set; }
- }
+ private class FromRouteAttribute : Attribute, IFromRouteMetadata
+ {
+ public string? Name { get; set; }
+ }
- private class FromBodyAttribute : Attribute, IFromBodyMetadata
- {
- public bool AllowEmpty { get; set; }
- }
+ private class FromQueryAttribute : Attribute, IFromQueryMetadata
+ {
+ public string? Name { get; set; }
+ }
- private class FromServiceAttribute : Attribute, IFromServiceMetadata
- {
- }
+ private class FromHeaderAttribute : Attribute, IFromHeaderMetadata
+ {
+ public string? Name { get; set; }
+ }
- class HttpHandler
- {
- private int _calls;
+ private class FromBodyAttribute : Attribute, IFromBodyMetadata
+ {
+ public bool AllowEmpty { get; set; }
+ }
- public void Handle(HttpContext httpContext)
- {
- _calls++;
- httpContext.Items["calls"] = _calls;
- }
- }
+ private class FromServiceAttribute : Attribute, IFromServiceMetadata
+ {
+ }
- private interface IMyService
- {
- }
+ class HttpHandler
+ {
+ private int _calls;
- private class MyService : IMyService
+ public void Handle(HttpContext httpContext)
{
+ _calls++;
+ httpContext.Items["calls"] = _calls;
}
+ }
- private class CustomResult : IResult
- {
- private readonly string _resultString;
+ private interface IMyService
+ {
+ }
- public CustomResult(string resultString)
- {
- _resultString = resultString;
- }
+ private class MyService : IMyService
+ {
+ }
- public Task ExecuteAsync(HttpContext httpContext)
- {
- return httpContext.Response.WriteAsync(_resultString);
- }
+ private class CustomResult : IResult
+ {
+ private readonly string _resultString;
+
+ public CustomResult(string resultString)
+ {
+ _resultString = resultString;
}
- private struct StructResult : IResult
+ public Task ExecuteAsync(HttpContext httpContext)
{
- private readonly string _resultString;
+ return httpContext.Response.WriteAsync(_resultString);
+ }
+ }
- public StructResult(string resultString)
- {
- _resultString = resultString;
- }
+ private struct StructResult : IResult
+ {
+ private readonly string _resultString;
- public Task ExecuteAsync(HttpContext httpContext)
- {
- return httpContext.Response.WriteAsync(_resultString);
- }
+ public StructResult(string resultString)
+ {
+ _resultString = resultString;
}
- private class ExceptionThrowingRequestBodyStream : Stream
+ public Task ExecuteAsync(HttpContext httpContext)
{
- private readonly Exception _exceptionToThrow;
-
- public ExceptionThrowingRequestBodyStream(Exception exceptionToThrow)
- {
- _exceptionToThrow = exceptionToThrow;
- }
-
- public override bool CanRead => true;
+ return httpContext.Response.WriteAsync(_resultString);
+ }
+ }
- public override bool CanSeek => false;
+ private class ExceptionThrowingRequestBodyStream : Stream
+ {
+ private readonly Exception _exceptionToThrow;
- public override bool CanWrite => false;
+ public ExceptionThrowingRequestBodyStream(Exception exceptionToThrow)
+ {
+ _exceptionToThrow = exceptionToThrow;
+ }
- public override long Length => throw new NotImplementedException();
+ public override bool CanRead => true;
- public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+ public override bool CanSeek => false;
- public override void Flush()
- {
- throw new NotImplementedException();
- }
+ public override bool CanWrite => false;
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw _exceptionToThrow;
- }
+ public override long Length => throw new NotImplementedException();
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotImplementedException();
- }
+ public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
- public override void SetLength(long value)
- {
- throw new NotImplementedException();
- }
+ public override void Flush()
+ {
+ throw new NotImplementedException();
+ }
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw _exceptionToThrow;
}
- private class EmptyServiceProvider : IServiceScope, IServiceProvider, IServiceScopeFactory
+ public override long Seek(long offset, SeekOrigin origin)
{
- public IServiceProvider ServiceProvider => this;
+ throw new NotImplementedException();
+ }
- public IServiceScope CreateScope()
- {
- return new EmptyServiceProvider();
- }
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
- public void Dispose()
- {
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+ }
- }
+ private class EmptyServiceProvider : IServiceScope, IServiceProvider, IServiceScopeFactory
+ {
+ public IServiceProvider ServiceProvider => this;
- public object? GetService(Type serviceType)
- {
- if (serviceType == typeof(IServiceScopeFactory))
- {
- return this;
- }
- return null;
- }
+ public IServiceScope CreateScope()
+ {
+ return new EmptyServiceProvider();
}
- private class TestHttpRequestLifetimeFeature : IHttpRequestLifetimeFeature
+ public void Dispose()
{
- private readonly CancellationTokenSource _requestAbortedCts = new();
- public CancellationToken RequestAborted { get => _requestAbortedCts.Token; set => throw new NotImplementedException(); }
+ }
- public void Abort()
+ public object? GetService(Type serviceType)
+ {
+ if (serviceType == typeof(IServiceScopeFactory))
{
- _requestAbortedCts.Cancel();
+ return this;
}
+ return null;
}
+ }
+
+ private class TestHttpRequestLifetimeFeature : IHttpRequestLifetimeFeature
+ {
+ private readonly CancellationTokenSource _requestAbortedCts = new();
- private class TestHttpResponseFeature : IHttpResponseFeature, IHttpResponseBodyFeature
+ public CancellationToken RequestAborted { get => _requestAbortedCts.Token; set => throw new NotImplementedException(); }
+
+ public void Abort()
{
- public int StatusCode { get; set; } = 200;
- public string? ReasonPhrase { get; set; }
- public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
+ _requestAbortedCts.Cancel();
+ }
+ }
- public bool HasStarted { get; private set; }
+ private class TestHttpResponseFeature : IHttpResponseFeature, IHttpResponseBodyFeature
+ {
+ public int StatusCode { get; set; } = 200;
+ public string? ReasonPhrase { get; set; }
+ public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
- // Assume any access to the response Body/Stream/Writer is writing for test purposes.
- public Stream Body
- {
- get
- {
- HasStarted = true;
- return Stream.Null;
- }
- set
- {
- }
- }
+ public bool HasStarted { get; private set; }
- public Stream Stream
+ // Assume any access to the response Body/Stream/Writer is writing for test purposes.
+ public Stream Body
+ {
+ get
{
- get
- {
- HasStarted = true;
- return Stream.Null;
- }
+ HasStarted = true;
+ return Stream.Null;
}
-
- public PipeWriter Writer
+ set
{
- get
- {
- HasStarted = true;
- return PipeWriter.Create(Stream.Null);
- }
}
+ }
- public Task StartAsync(CancellationToken cancellationToken = default)
+ public Stream Stream
+ {
+ get
{
HasStarted = true;
- return Task.CompletedTask;
+ return Stream.Null;
}
+ }
- public Task CompleteAsync()
+ public PipeWriter Writer
+ {
+ get
{
HasStarted = true;
- return Task.CompletedTask;
+ return PipeWriter.Create(Stream.Null);
}
+ }
- public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken = default)
- {
- HasStarted = true;
- return Task.CompletedTask;
- }
+ public Task StartAsync(CancellationToken cancellationToken = default)
+ {
+ HasStarted = true;
+ return Task.CompletedTask;
+ }
- public void DisableBuffering()
- {
- }
+ public Task CompleteAsync()
+ {
+ HasStarted = true;
+ return Task.CompletedTask;
+ }
- public void OnStarting(Func<object, Task> callback, object state)
- {
- }
+ public Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken = default)
+ {
+ HasStarted = true;
+ return Task.CompletedTask;
+ }
- public void OnCompleted(Func<object, Task> callback, object state)
- {
- }
+ public void DisableBuffering()
+ {
}
- private class RequestBodyDetectionFeature : IHttpRequestBodyDetectionFeature
+ public void OnStarting(Func<object, Task> callback, object state)
{
- public RequestBodyDetectionFeature(bool canHaveBody)
- {
- CanHaveBody = canHaveBody;
- }
+ }
- public bool CanHaveBody { get; }
+ public void OnCompleted(Func<object, Task> callback, object state)
+ {
}
}
- internal static class TestExtensionResults
+ private class RequestBodyDetectionFeature : IHttpRequestBodyDetectionFeature
{
- public static IResult TestResult(this IResultExtensions resultExtensions, string name)
+ public RequestBodyDetectionFeature(bool canHaveBody)
{
- return Results.Ok(FormattableString.Invariant($"Hello {name}. This is from an extension method."));
+ CanHaveBody = canHaveBody;
}
+
+ public bool CanHaveBody { get; }
+ }
+}
+
+internal static class TestExtensionResults
+{
+ public static IResult TestResult(this IResultExtensions resultExtensions, string name)
+ {
+ return Results.Ok(FormattableString.Invariant($"Hello {name}. This is from an extension method."));
}
}
diff --git a/src/Http/Http.Extensions/test/ResponseExtensionTests.cs b/src/Http/Http.Extensions/test/ResponseExtensionTests.cs
index ac3570068b..c996a49851 100644
--- a/src/Http/Http.Extensions/test/ResponseExtensionTests.cs
+++ b/src/Http/Http.Extensions/test/ResponseExtensionTests.cs
@@ -10,72 +10,71 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.Net.Http.Headers;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+public class ResponseExtensionTests
{
- public class ResponseExtensionTests
+ [Fact]
+ public void Clear_ResetsResponse()
{
- [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);
+ 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();
+ 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);
- }
+ 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());
+ [Fact]
+ public void Clear_AlreadyStarted_Throws()
+ {
+ var context = new DefaultHttpContext();
+ context.Features.Set<IHttpResponseFeature>(new StartedResponseFeature());
- Assert.Throws<InvalidOperationException>(() => context.Response.Clear());
- }
+ Assert.Throws<InvalidOperationException>(() => context.Response.Clear());
+ }
- [Theory]
- [InlineData(true, false, 301)]
- [InlineData(false, false, 302)]
- [InlineData(true, true, 308)]
- [InlineData(false, true, 307)]
- public void Redirect_SetsResponseCorrectly(bool permanent, bool preserveMethod, int expectedStatusCode)
- {
- var location = "http://localhost/redirect";
- var context = new DefaultHttpContext();
- context.Response.StatusCode = StatusCodes.Status200OK;
+ [Theory]
+ [InlineData(true, false, 301)]
+ [InlineData(false, false, 302)]
+ [InlineData(true, true, 308)]
+ [InlineData(false, true, 307)]
+ public void Redirect_SetsResponseCorrectly(bool permanent, bool preserveMethod, int expectedStatusCode)
+ {
+ var location = "http://localhost/redirect";
+ var context = new DefaultHttpContext();
+ context.Response.StatusCode = StatusCodes.Status200OK;
- context.Response.Redirect(location, permanent, preserveMethod);
+ context.Response.Redirect(location, permanent, preserveMethod);
- Assert.Equal(location, context.Response.Headers.Location.First());
- Assert.Equal(expectedStatusCode, context.Response.StatusCode);
- }
+ Assert.Equal(location, context.Response.Headers.Location.First());
+ Assert.Equal(expectedStatusCode, context.Response.StatusCode);
+ }
- private class StartedResponseFeature : IHttpResponseFeature
- {
- public Stream Body { get; set; }
+ private class StartedResponseFeature : IHttpResponseFeature
+ {
+ public Stream Body { get; set; }
- public bool HasStarted { get { return true; } }
+ public bool HasStarted { get { return true; } }
- public IHeaderDictionary Headers { get; set; }
+ public IHeaderDictionary Headers { get; set; }
- public string ReasonPhrase { get; set; }
+ public string ReasonPhrase { get; set; }
- public int StatusCode { get; set; }
+ public int StatusCode { get; set; }
- public void OnCompleted(Func<object, Task> callback, object state)
- {
- throw new NotImplementedException();
- }
+ public void OnCompleted(Func<object, Task> callback, object state)
+ {
+ throw new NotImplementedException();
+ }
- public void OnStarting(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
index 2e3ecdbddf..33871c97e0 100644
--- a/src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs
+++ b/src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs
@@ -9,137 +9,136 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Extensions.Tests
+namespace Microsoft.AspNetCore.Http.Extensions.Tests;
+
+public class SendFileResponseExtensionsTests
{
- public class SendFileResponseExtensionsTests
+ [Fact]
+ public Task SendFileWhenFileNotFoundThrows()
{
- [Fact]
- public Task SendFileWhenFileNotFoundThrows()
- {
- var response = new DefaultHttpContext().Response;
- return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
- }
+ 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 FakeResponseBodyFeature();
- context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
+ [Fact]
+ public async Task SendFileWorks()
+ {
+ var context = new DefaultHttpContext();
+ var response = context.Response;
+ var fakeFeature = new FakeResponseBodyFeature();
+ context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
- await response.SendFileAsync("bob", 1, 3, CancellationToken.None);
+ 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);
- }
+ Assert.Equal("bob", fakeFeature.Name);
+ Assert.Equal(1, fakeFeature.Offset);
+ Assert.Equal(3, fakeFeature.Length);
+ Assert.Equal(CancellationToken.None, fakeFeature.Token);
+ }
- [Fact]
- public async Task SendFile_FallsBackToBodyStream()
- {
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- var response = context.Response;
- response.Body = body;
+ [Fact]
+ public async Task SendFile_FallsBackToBodyStream()
+ {
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ var response = context.Response;
+ response.Body = body;
- await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);
+ await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);
- Assert.Equal(3, body.Length);
- }
+ Assert.Equal(3, body.Length);
+ }
- [Fact]
- public async Task SendFile_Stream_ThrowsWhenCanceled()
- {
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- var response = context.Response;
- response.Body = body;
+ [Fact]
+ public async Task SendFile_Stream_ThrowsWhenCanceled()
+ {
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ var response = context.Response;
+ response.Body = body;
- await Assert.ThrowsAnyAsync<OperationCanceledException>(
- () => response.SendFileAsync("testfile1kb.txt", 1, 3, new CancellationToken(canceled: true)));
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(
+ () => response.SendFileAsync("testfile1kb.txt", 1, 3, new CancellationToken(canceled: true)));
- Assert.Equal(0, body.Length);
- }
+ Assert.Equal(0, body.Length);
+ }
- [Fact]
- public async Task SendFile_Feature_ThrowsWhenCanceled()
- {
- var context = new DefaultHttpContext();
- var fakeFeature = new FakeResponseBodyFeature();
- context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
- var response = context.Response;
+ [Fact]
+ public async Task SendFile_Feature_ThrowsWhenCanceled()
+ {
+ var context = new DefaultHttpContext();
+ var fakeFeature = new FakeResponseBodyFeature();
+ context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
+ var response = context.Response;
- await Assert.ThrowsAsync<OperationCanceledException>(
- () => response.SendFileAsync("testfile1kb.txt", 1, 3, new CancellationToken(canceled: true)));
- }
+ await Assert.ThrowsAsync<OperationCanceledException>(
+ () => response.SendFileAsync("testfile1kb.txt", 1, 3, new CancellationToken(canceled: true)));
+ }
- [Fact]
- public async Task SendFile_Stream_AbortsSilentlyWhenRequestCanceled()
- {
- var body = new MemoryStream();
- var context = new DefaultHttpContext();
- context.RequestAborted = new CancellationToken(canceled: true);
- var response = context.Response;
- response.Body = body;
+ [Fact]
+ public async Task SendFile_Stream_AbortsSilentlyWhenRequestCanceled()
+ {
+ var body = new MemoryStream();
+ var context = new DefaultHttpContext();
+ context.RequestAborted = new CancellationToken(canceled: true);
+ var response = context.Response;
+ response.Body = body;
+
+ await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);
+
+ Assert.Equal(0, body.Length);
+ }
+
+ [Fact]
+ public async Task SendFile_Feature_AbortsSilentlyWhenRequestCanceled()
+ {
+ var context = new DefaultHttpContext();
+ var fakeFeature = new FakeResponseBodyFeature();
+ context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
+ var token = new CancellationToken(canceled: true);
+ context.RequestAborted = token;
+ var response = context.Response;
- await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);
+ await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);
- Assert.Equal(0, body.Length);
+ Assert.Equal(token, fakeFeature.Token);
+ }
+
+ private class FakeResponseBodyFeature : IHttpResponseBodyFeature
+ {
+ public string Name { get; set; } = null;
+ public long Offset { get; set; } = 0;
+ public long? Length { get; set; } = null;
+ public CancellationToken Token { get; set; }
+
+ public Stream Stream => throw new System.NotImplementedException();
+
+ public PipeWriter Writer => throw new System.NotImplementedException();
+
+ public Task CompleteAsync()
+ {
+ throw new System.NotImplementedException();
}
- [Fact]
- public async Task SendFile_Feature_AbortsSilentlyWhenRequestCanceled()
+ public void DisableBuffering()
{
- var context = new DefaultHttpContext();
- var fakeFeature = new FakeResponseBodyFeature();
- context.Features.Set<IHttpResponseBodyFeature>(fakeFeature);
- var token = new CancellationToken(canceled: true);
- context.RequestAborted = token;
- var response = context.Response;
+ throw new System.NotImplementedException();
+ }
- await response.SendFileAsync("testfile1kb.txt", 1, 3, CancellationToken.None);
+ public Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
+ {
+ Name = path;
+ Offset = offset;
+ Length = length;
+ Token = cancellation;
- Assert.Equal(token, fakeFeature.Token);
+ cancellation.ThrowIfCancellationRequested();
+ return Task.FromResult(0);
}
- private class FakeResponseBodyFeature : IHttpResponseBodyFeature
+ public Task StartAsync(CancellationToken token = default)
{
- public string Name { get; set; } = null;
- public long Offset { get; set; } = 0;
- public long? Length { get; set; } = null;
- public CancellationToken Token { get; set; }
-
- public Stream Stream => throw new System.NotImplementedException();
-
- public PipeWriter Writer => throw new System.NotImplementedException();
-
- public Task CompleteAsync()
- {
- throw new System.NotImplementedException();
- }
-
- public void DisableBuffering()
- {
- throw new System.NotImplementedException();
- }
-
- public Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
- {
- Name = path;
- Offset = offset;
- Length = length;
- Token = cancellation;
-
- cancellation.ThrowIfCancellationRequested();
- return Task.FromResult(0);
- }
-
- public Task StartAsync(CancellationToken token = default)
- {
- throw new System.NotImplementedException();
- }
+ throw new System.NotImplementedException();
}
}
}
diff --git a/src/Http/Http.Extensions/test/TestStream.cs b/src/Http/Http.Extensions/test/TestStream.cs
index 6590f23499..809dec4bd2 100644
--- a/src/Http/Http.Extensions/test/TestStream.cs
+++ b/src/Http/Http.Extensions/test/TestStream.cs
@@ -6,53 +6,52 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Extensions.Tests
+namespace Microsoft.AspNetCore.Http.Extensions.Tests;
+
+public class TestStream : Stream
{
- public class TestStream : Stream
+ public override bool CanRead { get; }
+ public override bool CanSeek { get; }
+ public override bool CanWrite { get; }
+ public override long Length { get; }
+ public override long Position { get; set; }
+
+ public override void Flush()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ var tcs = new TaskCompletionSource<int>();
+ cancellationToken.Register(s => ((TaskCompletionSource<int>)s).SetCanceled(), tcs);
+ return new ValueTask<int>(tcs.Task);
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
- public override bool CanRead { get; }
- public override bool CanSeek { get; }
- public override bool CanWrite { get; }
- public override long Length { get; }
- public override long Position { get; set; }
-
- public override void Flush()
- {
- throw new NotImplementedException();
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotImplementedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotImplementedException();
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotImplementedException();
- }
-
- public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
- {
- var tcs = new TaskCompletionSource<int>();
- cancellationToken.Register(s => ((TaskCompletionSource<int>)s).SetCanceled(), tcs);
- return new ValueTask<int>(tcs.Task);
- }
-
- public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
- {
- var tcs = new TaskCompletionSource<int>();
- cancellationToken.Register(s => ((TaskCompletionSource<int>)s).SetCanceled(), tcs);
- return new ValueTask(tcs.Task);
- }
+ var tcs = new TaskCompletionSource<int>();
+ cancellationToken.Register(s => ((TaskCompletionSource<int>)s).SetCanceled(), tcs);
+ return new ValueTask(tcs.Task);
}
}
diff --git a/src/Http/Http.Extensions/test/UriHelperTests.cs b/src/Http/Http.Extensions/test/UriHelperTests.cs
index a5a6b9819f..777cefc767 100644
--- a/src/Http/Http.Extensions/test/UriHelperTests.cs
+++ b/src/Http/Http.Extensions/test/UriHelperTests.cs
@@ -4,196 +4,195 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Extensions
+namespace Microsoft.AspNetCore.Http.Extensions;
+
+public class UriHelperTests
{
- public class UriHelperTests
+ [Fact]
+ public void EncodeEmptyPartialUrl()
+ {
+ var result = UriHelper.BuildRelative();
+
+ Assert.Equal("/", result);
+ }
+
+ [Fact]
+ public void EncodePartialUrl()
{
- [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);
- }
-
- [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", "/base", "/foo", "?bar=1", "#col=2", "http://example.com/base/foo?bar=1#col=2")]
- [InlineData("http", "example.com", "/base/", "/foo", "?bar=1", "#col=2", "http://example.com/base/foo?bar=1#col=2")]
- [InlineData("http", "example.com", "/base/", "", "?bar=1", "#col=2", "http://example.com/base/?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#o", "", "#col=2", "http://example.com/fo%23o#col=2")]
- [InlineData("http", "example.com", "", "/fo?o", "", "#col=2", "http://example.com/fo%3Fo#col=2")]
- [InlineData("ftp", "example.com", "", "/", "", "", "ftp://example.com/")]
- [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///")]
- [InlineData("http", "example.com", "///", "///", "", "", "http://example.com/////")]
- public void BuildAbsoluteGenerationChecks(
- string scheme,
- string host,
- string pathBase,
- string path,
- string query,
- string fragment,
- string expectedUri)
- {
- var uri = UriHelper.BuildAbsolute(
- scheme,
- new HostString(host),
- new PathString(pathBase),
- new PathString(path),
- new QueryString(query),
- new FragmentString(fragment));
-
- Assert.Equal(expectedUri, uri);
- }
-
- [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());
- }
-
- [Theory]
- [InlineData("/un?escaped/base")]
- [InlineData(null)]
- public void GetDisplayUrlFromRequest(string pathBase)
- {
- var request = new DefaultHttpContext().Request;
- request.Scheme = "http";
- request.Host = new HostString("my.HoΨst:80");
- request.PathBase = new PathString(pathBase);
- request.Path = new PathString("/un?escaped");
- request.QueryString = new QueryString("?name=val%23ue");
-
- Assert.Equal("http://my.hoψst:80" + pathBase + "/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));
-
- }
+ 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);
+ }
+
+ [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", "/base", "/foo", "?bar=1", "#col=2", "http://example.com/base/foo?bar=1#col=2")]
+ [InlineData("http", "example.com", "/base/", "/foo", "?bar=1", "#col=2", "http://example.com/base/foo?bar=1#col=2")]
+ [InlineData("http", "example.com", "/base/", "", "?bar=1", "#col=2", "http://example.com/base/?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#o", "", "#col=2", "http://example.com/fo%23o#col=2")]
+ [InlineData("http", "example.com", "", "/fo?o", "", "#col=2", "http://example.com/fo%3Fo#col=2")]
+ [InlineData("ftp", "example.com", "", "/", "", "", "ftp://example.com/")]
+ [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///")]
+ [InlineData("http", "example.com", "///", "///", "", "", "http://example.com/////")]
+ public void BuildAbsoluteGenerationChecks(
+ string scheme,
+ string host,
+ string pathBase,
+ string path,
+ string query,
+ string fragment,
+ string expectedUri)
+ {
+ var uri = UriHelper.BuildAbsolute(
+ scheme,
+ new HostString(host),
+ new PathString(pathBase),
+ new PathString(path),
+ new QueryString(query),
+ new FragmentString(fragment));
+
+ Assert.Equal(expectedUri, uri);
+ }
+
+ [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());
+ }
+
+ [Theory]
+ [InlineData("/un?escaped/base")]
+ [InlineData(null)]
+ public void GetDisplayUrlFromRequest(string pathBase)
+ {
+ var request = new DefaultHttpContext().Request;
+ request.Scheme = "http";
+ request.Host = new HostString("my.HoΨst:80");
+ request.PathBase = new PathString(pathBase);
+ request.Path = new PathString("/un?escaped");
+ request.QueryString = new QueryString("?name=val%23ue");
+
+ Assert.Equal("http://my.hoψst:80" + pathBase + "/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/IHttpAuthenticationFeature.cs b/src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs
index 55bd77898d..80b3ac93d9 100644
--- a/src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs
+++ b/src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs
@@ -3,16 +3,15 @@
using System.Security.Claims;
-namespace Microsoft.AspNetCore.Http.Features.Authentication
+namespace Microsoft.AspNetCore.Http.Features.Authentication;
+
+/// <summary>
+/// The HTTP authentication feature.
+/// </summary>
+public interface IHttpAuthenticationFeature
{
/// <summary>
- /// The HTTP authentication feature.
+ /// Gets or sets the <see cref="ClaimsPrincipal"/> associated with the HTTP request.
/// </summary>
- public interface IHttpAuthenticationFeature
- {
- /// <summary>
- /// Gets or sets the <see cref="ClaimsPrincipal"/> associated with the HTTP request.
- /// </summary>
- ClaimsPrincipal? User { get; set; }
- }
+ ClaimsPrincipal? User { get; set; }
}
diff --git a/src/Http/Http.Features/src/CookieOptions.cs b/src/Http/Http.Features/src/CookieOptions.cs
index e663b54606..79d2dbc07c 100644
--- a/src/Http/Http.Features/src/CookieOptions.cs
+++ b/src/Http/Http.Features/src/CookieOptions.cs
@@ -3,67 +3,66 @@
using System;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Options used to create a new cookie.
+/// </summary>
+public class CookieOptions
{
/// <summary>
- /// Options used to create a new cookie.
+ /// Creates a default cookie with a path of '/'.
/// </summary>
- public class CookieOptions
+ public CookieOptions()
{
- /// <summary>
- /// Creates a default cookie with a path of '/'.
- /// </summary>
- public CookieOptions()
- {
- Path = "/";
- }
+ 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 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 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 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 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.Unspecified"/>
- /// </summary>
- /// <returns>The <see cref="SameSiteMode"/> representing the enforcement mode of the cookie.</returns>
- public SameSiteMode SameSite { get; set; } = SameSiteMode.Unspecified;
+ /// <summary>
+ /// Gets or sets the value for the SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.Unspecified"/>
+ /// </summary>
+ /// <returns>The <see cref="SameSiteMode"/> representing the enforcement mode of the cookie.</returns>
+ public SameSiteMode SameSite { get; set; } = SameSiteMode.Unspecified;
- /// <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 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>
+ /// 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; }
- }
+ /// <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/HttpsCompressionMode.cs b/src/Http/Http.Features/src/HttpsCompressionMode.cs
index e3932cacf4..b367478303 100644
--- a/src/Http/Http.Features/src/HttpsCompressionMode.cs
+++ b/src/Http/Http.Features/src/HttpsCompressionMode.cs
@@ -1,28 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Use to dynamically control response compression for HTTPS requests.
+/// </summary>
+public enum HttpsCompressionMode
{
/// <summary>
- /// Use to dynamically control response compression for HTTPS requests.
+ /// No value has been specified, use the configured defaults.
/// </summary>
- public enum HttpsCompressionMode
- {
- /// <summary>
- /// No value has been specified, use the configured defaults.
- /// </summary>
- Default = 0,
+ Default = 0,
- /// <summary>
- /// Opts out of compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content
- /// may expose security problems.
- /// </summary>
- DoNotCompress,
+ /// <summary>
+ /// Opts out of compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content
+ /// may expose security problems.
+ /// </summary>
+ DoNotCompress,
- /// <summary>
- /// Opts into compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content
- /// may expose security problems.
- /// </summary>
- Compress,
- }
+ /// <summary>
+ /// Opts into compression over HTTPS. Enabling compression on HTTPS requests for remotely manipulable content
+ /// may expose security problems.
+ /// </summary>
+ Compress,
}
diff --git a/src/Http/Http.Features/src/IBadRequestExceptionFeature.cs b/src/Http/Http.Features/src/IBadRequestExceptionFeature.cs
index 056696f182..b98206756d 100644
--- a/src/Http/Http.Features/src/IBadRequestExceptionFeature.cs
+++ b/src/Http/Http.Features/src/IBadRequestExceptionFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides information about rejected HTTP requests.
+/// </summary>
+public interface IBadRequestExceptionFeature
{
/// <summary>
- /// Provides information about rejected HTTP requests.
+ /// Synchronously retrieves the exception associated with the rejected HTTP request.
/// </summary>
- public interface IBadRequestExceptionFeature
- {
- /// <summary>
- /// Synchronously retrieves the exception associated with the rejected HTTP request.
- /// </summary>
- Exception? Error { get; }
- }
+ Exception? Error { get; }
}
diff --git a/src/Http/Http.Features/src/IFormCollection.cs b/src/Http/Http.Features/src/IFormCollection.cs
index 0d3d17264c..64254f83cd 100644
--- a/src/Http/Http.Features/src/IFormCollection.cs
+++ b/src/Http/Http.Features/src/IFormCollection.cs
@@ -4,91 +4,90 @@
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the parsed form values sent with the HttpRequest.
+/// </summary>
+public interface IFormCollection : IEnumerable<KeyValuePair<string, StringValues>>
{
/// <summary>
- /// Represents the parsed form values sent with the HttpRequest.
+ /// Gets the number of elements contained in the <see cref="IFormCollection" />.
/// </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; }
+ /// <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>
+ /// 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>
+ /// 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 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>
+ /// 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; }
- }
+ /// <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
index 8782c7470a..1df30f9534 100644
--- a/src/Http/Http.Features/src/IFormFeature.cs
+++ b/src/Http/Http.Features/src/IFormFeature.cs
@@ -4,44 +4,43 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Allows reading the request body as a HTTP form.
+/// </summary>
+public interface IFormFeature
{
/// <summary>
- /// Allows reading the request body as a HTTP form.
+ /// Indicates if the request has a supported form content-type.
/// </summary>
- public interface IFormFeature
- {
- /// <summary>
- /// Indicates if the request has a supported form content-type.
- /// </summary>
- bool HasFormContentType { get; }
+ bool HasFormContentType { get; }
- /// <summary>
- /// Gets or sets the parsed form.
- /// <para>
- /// This API will return a non-null value if the
- /// request body was read using <see cref="ReadFormAsync(CancellationToken)"/> or <see cref="ReadForm"/>, or
- /// if a value was explicitly assigned.
- /// </para>
- /// </summary>
- IFormCollection? Form { get; set; }
+ /// <summary>
+ /// Gets or sets the parsed form.
+ /// <para>
+ /// This API will return a non-null value if the
+ /// request body was read using <see cref="ReadFormAsync(CancellationToken)"/> or <see cref="ReadForm"/>, or
+ /// if a value was explicitly assigned.
+ /// </para>
+ /// </summary>
+ IFormCollection? Form { get; set; }
- /// <summary>
- /// Parses the request body as a form.
- /// <para>
- /// If the request body has not been previously read, this API performs a synchronous (blocking) read
- /// on the HTTP input stream which may be unsupported or can adversely affect application performance.
- /// Consider using <see cref="ReadFormAsync(CancellationToken)"/> instead.
- /// </para>
- /// </summary>
- /// <returns>The <see cref="IFormCollection"/>.</returns>
- IFormCollection ReadForm();
+ /// <summary>
+ /// Parses the request body as a form.
+ /// <para>
+ /// If the request body has not been previously read, this API performs a synchronous (blocking) read
+ /// on the HTTP input stream which may be unsupported or can adversely affect application performance.
+ /// Consider using <see cref="ReadFormAsync(CancellationToken)"/> instead.
+ /// </para>
+ /// </summary>
+ /// <returns>The <see cref="IFormCollection"/>.</returns>
+ IFormCollection ReadForm();
- /// <summary>
- /// Parses the request body as a form.
- /// </summary>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
- Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken);
- }
+ /// <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
index ef95e56e12..7df30b6846 100644
--- a/src/Http/Http.Features/src/IFormFile.cs
+++ b/src/Http/Http.Features/src/IFormFile.cs
@@ -5,59 +5,58 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents a file sent with the HttpRequest.
+/// </summary>
+public interface IFormFile
{
/// <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));
- }
+ /// 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
index dd05b25921..25092b31a3 100644
--- a/src/Http/Http.Features/src/IFormFileCollection.cs
+++ b/src/Http/Http.Features/src/IFormFileCollection.cs
@@ -3,40 +3,39 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the collection of files sent with the HttpRequest.
+/// </summary>
+public interface IFormFileCollection : IReadOnlyList<IFormFile>
{
/// <summary>
- /// Represents the collection of files sent with the HttpRequest.
+ /// Gets the first file with the specified name.
/// </summary>
- public interface IFormFileCollection : IReadOnlyList<IFormFile>
- {
- /// <summary>
- /// Gets the first file with the specified name.
- /// </summary>
- /// <param name="name">The name of the file to get.</param>
- /// <returns>
- /// The requested file, or null if it is not present.
- /// </returns>
- IFormFile? this[string name] { get; }
+ /// <param name="name">The name of the file to get.</param>
+ /// <returns>
+ /// The requested file, or null if it is not present.
+ /// </returns>
+ IFormFile? this[string name] { get; }
- /// <summary>
- /// Gets the first file with the specified name.
- /// </summary>
- /// <param name="name">The name of the file to get.</param>
- /// <returns>
- /// The requested file, or null if it is not present.
- /// </returns>
- IFormFile? GetFile(string name);
+ /// <summary>
+ /// Gets the first file with the specified name.
+ /// </summary>
+ /// <param name="name">The name of the file to get.</param>
+ /// <returns>
+ /// The requested file, or null if it is not present.
+ /// </returns>
+ IFormFile? GetFile(string name);
- /// <summary>
- /// Gets an <see cref="IReadOnlyList{T}" /> containing the files of the
- /// <see cref="IFormFileCollection" /> with the specified name.
- /// </summary>
- /// <param name="name">The name of the files to get.</param>
- /// <returns>
- /// An <see cref="IReadOnlyList{T}" /> containing the files of the object
- /// that implements <see cref="IFormFileCollection" />.
- /// </returns>
- IReadOnlyList<IFormFile> GetFiles(string name);
- }
+ /// <summary>
+ /// Gets an <see cref="IReadOnlyList{T}" /> containing the files of the
+ /// <see cref="IFormFileCollection" /> with the specified name.
+ /// </summary>
+ /// <param name="name">The name of the files to get.</param>
+ /// <returns>
+ /// An <see cref="IReadOnlyList{T}" /> containing the files of the object
+ /// that implements <see cref="IFormFileCollection" />.
+ /// </returns>
+ IReadOnlyList<IFormFile> GetFiles(string name);
}
diff --git a/src/Http/Http.Features/src/IHeaderDictionary.Keyed.cs b/src/Http/Http.Features/src/IHeaderDictionary.Keyed.cs
index 5455e2ad93..332eac40dd 100644
--- a/src/Http/Http.Features/src/IHeaderDictionary.Keyed.cs
+++ b/src/Http/Http.Features/src/IHeaderDictionary.Keyed.cs
@@ -3,275 +3,274 @@
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public partial interface IHeaderDictionary
{
- public partial interface IHeaderDictionary
- {
- /// <summary>Gets or sets the <c>Accept</c> HTTP header.</summary>
- StringValues Accept { get => this[HeaderNames.Accept]; set => this[HeaderNames.Accept] = value; }
+ /// <summary>Gets or sets the <c>Accept</c> HTTP header.</summary>
+ StringValues Accept { get => this[HeaderNames.Accept]; set => this[HeaderNames.Accept] = value; }
- /// <summary>Gets or sets the <c>Accept-Charset</c> HTTP header.</summary>
- StringValues AcceptCharset { get => this[HeaderNames.AcceptCharset]; set => this[HeaderNames.AcceptCharset] = value; }
+ /// <summary>Gets or sets the <c>Accept-Charset</c> HTTP header.</summary>
+ StringValues AcceptCharset { get => this[HeaderNames.AcceptCharset]; set => this[HeaderNames.AcceptCharset] = value; }
- /// <summary>Gets or sets the <c>Accept-Encoding</c> HTTP header.</summary>
- StringValues AcceptEncoding { get => this[HeaderNames.AcceptEncoding]; set => this[HeaderNames.AcceptEncoding] = value; }
+ /// <summary>Gets or sets the <c>Accept-Encoding</c> HTTP header.</summary>
+ StringValues AcceptEncoding { get => this[HeaderNames.AcceptEncoding]; set => this[HeaderNames.AcceptEncoding] = value; }
- /// <summary>Gets or sets the <c>Accept-Language</c> HTTP header.</summary>
- StringValues AcceptLanguage { get => this[HeaderNames.AcceptLanguage]; set => this[HeaderNames.AcceptLanguage] = value; }
+ /// <summary>Gets or sets the <c>Accept-Language</c> HTTP header.</summary>
+ StringValues AcceptLanguage { get => this[HeaderNames.AcceptLanguage]; set => this[HeaderNames.AcceptLanguage] = value; }
- /// <summary>Gets or sets the <c>Accept-Ranges</c> HTTP header.</summary>
- StringValues AcceptRanges { get => this[HeaderNames.AcceptRanges]; set => this[HeaderNames.AcceptRanges] = value; }
+ /// <summary>Gets or sets the <c>Accept-Ranges</c> HTTP header.</summary>
+ StringValues AcceptRanges { get => this[HeaderNames.AcceptRanges]; set => this[HeaderNames.AcceptRanges] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Allow-Credentials</c> HTTP header.</summary>
- StringValues AccessControlAllowCredentials { get => this[HeaderNames.AccessControlAllowCredentials]; set => this[HeaderNames.AccessControlAllowCredentials] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Allow-Credentials</c> HTTP header.</summary>
+ StringValues AccessControlAllowCredentials { get => this[HeaderNames.AccessControlAllowCredentials]; set => this[HeaderNames.AccessControlAllowCredentials] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Allow-Headers</c> HTTP header.</summary>
- StringValues AccessControlAllowHeaders { get => this[HeaderNames.AccessControlAllowHeaders]; set => this[HeaderNames.AccessControlAllowHeaders] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Allow-Headers</c> HTTP header.</summary>
+ StringValues AccessControlAllowHeaders { get => this[HeaderNames.AccessControlAllowHeaders]; set => this[HeaderNames.AccessControlAllowHeaders] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Allow-Methods</c> HTTP header.</summary>
- StringValues AccessControlAllowMethods { get => this[HeaderNames.AccessControlAllowMethods]; set => this[HeaderNames.AccessControlAllowMethods] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Allow-Methods</c> HTTP header.</summary>
+ StringValues AccessControlAllowMethods { get => this[HeaderNames.AccessControlAllowMethods]; set => this[HeaderNames.AccessControlAllowMethods] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Allow-Origin</c> HTTP header.</summary>
- StringValues AccessControlAllowOrigin { get => this[HeaderNames.AccessControlAllowOrigin]; set => this[HeaderNames.AccessControlAllowOrigin] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Allow-Origin</c> HTTP header.</summary>
+ StringValues AccessControlAllowOrigin { get => this[HeaderNames.AccessControlAllowOrigin]; set => this[HeaderNames.AccessControlAllowOrigin] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Expose-Headers</c> HTTP header.</summary>
- StringValues AccessControlExposeHeaders { get => this[HeaderNames.AccessControlExposeHeaders]; set => this[HeaderNames.AccessControlExposeHeaders] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Expose-Headers</c> HTTP header.</summary>
+ StringValues AccessControlExposeHeaders { get => this[HeaderNames.AccessControlExposeHeaders]; set => this[HeaderNames.AccessControlExposeHeaders] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Max-Age</c> HTTP header.</summary>
- StringValues AccessControlMaxAge { get => this[HeaderNames.AccessControlMaxAge]; set => this[HeaderNames.AccessControlMaxAge] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Max-Age</c> HTTP header.</summary>
+ StringValues AccessControlMaxAge { get => this[HeaderNames.AccessControlMaxAge]; set => this[HeaderNames.AccessControlMaxAge] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Request-Headers</c> HTTP header.</summary>
- StringValues AccessControlRequestHeaders { get => this[HeaderNames.AccessControlRequestHeaders]; set => this[HeaderNames.AccessControlRequestHeaders] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Request-Headers</c> HTTP header.</summary>
+ StringValues AccessControlRequestHeaders { get => this[HeaderNames.AccessControlRequestHeaders]; set => this[HeaderNames.AccessControlRequestHeaders] = value; }
- /// <summary>Gets or sets the <c>Access-Control-Request-Method</c> HTTP header.</summary>
- StringValues AccessControlRequestMethod { get => this[HeaderNames.AccessControlRequestMethod]; set => this[HeaderNames.AccessControlRequestMethod] = value; }
+ /// <summary>Gets or sets the <c>Access-Control-Request-Method</c> HTTP header.</summary>
+ StringValues AccessControlRequestMethod { get => this[HeaderNames.AccessControlRequestMethod]; set => this[HeaderNames.AccessControlRequestMethod] = value; }
- /// <summary>Gets or sets the <c>Age</c> HTTP header.</summary>
- StringValues Age { get => this[HeaderNames.Age]; set => this[HeaderNames.Age] = value; }
+ /// <summary>Gets or sets the <c>Age</c> HTTP header.</summary>
+ StringValues Age { get => this[HeaderNames.Age]; set => this[HeaderNames.Age] = value; }
- /// <summary>Gets or sets the <c>Allow</c> HTTP header.</summary>
- StringValues Allow { get => this[HeaderNames.Allow]; set => this[HeaderNames.Allow] = value; }
+ /// <summary>Gets or sets the <c>Allow</c> HTTP header.</summary>
+ StringValues Allow { get => this[HeaderNames.Allow]; set => this[HeaderNames.Allow] = value; }
- /// <summary>Gets or sets the <c>Alt-Svc</c> HTTP header.</summary>
- StringValues AltSvc { get => this[HeaderNames.AltSvc]; set => this[HeaderNames.AltSvc] = value; }
+ /// <summary>Gets or sets the <c>Alt-Svc</c> HTTP header.</summary>
+ StringValues AltSvc { get => this[HeaderNames.AltSvc]; set => this[HeaderNames.AltSvc] = value; }
- /// <summary>Gets or sets the <c>Authorization</c> HTTP header.</summary>
- StringValues Authorization { get => this[HeaderNames.Authorization]; set => this[HeaderNames.Authorization] = value; }
+ /// <summary>Gets or sets the <c>Authorization</c> HTTP header.</summary>
+ StringValues Authorization { get => this[HeaderNames.Authorization]; set => this[HeaderNames.Authorization] = value; }
- /// <summary>Gets or sets the <c>baggage</c> HTTP header.</summary>
- StringValues Baggage { get => this[HeaderNames.Baggage]; set => this[HeaderNames.Baggage] = value; }
+ /// <summary>Gets or sets the <c>baggage</c> HTTP header.</summary>
+ StringValues Baggage { get => this[HeaderNames.Baggage]; set => this[HeaderNames.Baggage] = value; }
- /// <summary>Gets or sets the <c>Cache-Control</c> HTTP header.</summary>
- StringValues CacheControl { get => this[HeaderNames.CacheControl]; set => this[HeaderNames.CacheControl] = value; }
+ /// <summary>Gets or sets the <c>Cache-Control</c> HTTP header.</summary>
+ StringValues CacheControl { get => this[HeaderNames.CacheControl]; set => this[HeaderNames.CacheControl] = value; }
- /// <summary>Gets or sets the <c>Connection</c> HTTP header.</summary>
- StringValues Connection { get => this[HeaderNames.Connection]; set => this[HeaderNames.Connection] = value; }
+ /// <summary>Gets or sets the <c>Connection</c> HTTP header.</summary>
+ StringValues Connection { get => this[HeaderNames.Connection]; set => this[HeaderNames.Connection] = value; }
- /// <summary>Gets or sets the <c>Content-Disposition</c> HTTP header.</summary>
- StringValues ContentDisposition { get => this[HeaderNames.ContentDisposition]; set => this[HeaderNames.ContentDisposition] = value; }
+ /// <summary>Gets or sets the <c>Content-Disposition</c> HTTP header.</summary>
+ StringValues ContentDisposition { get => this[HeaderNames.ContentDisposition]; set => this[HeaderNames.ContentDisposition] = value; }
- /// <summary>Gets or sets the <c>Content-Encoding</c> HTTP header.</summary>
- StringValues ContentEncoding { get => this[HeaderNames.ContentEncoding]; set => this[HeaderNames.ContentEncoding] = value; }
+ /// <summary>Gets or sets the <c>Content-Encoding</c> HTTP header.</summary>
+ StringValues ContentEncoding { get => this[HeaderNames.ContentEncoding]; set => this[HeaderNames.ContentEncoding] = value; }
- /// <summary>Gets or sets the <c>Content-Language</c> HTTP header.</summary>
- StringValues ContentLanguage { get => this[HeaderNames.ContentLanguage]; set => this[HeaderNames.ContentLanguage] = value; }
+ /// <summary>Gets or sets the <c>Content-Language</c> HTTP header.</summary>
+ StringValues ContentLanguage { get => this[HeaderNames.ContentLanguage]; set => this[HeaderNames.ContentLanguage] = value; }
- /// <summary>Gets or sets the <c>Content-Location</c> HTTP header.</summary>
- StringValues ContentLocation { get => this[HeaderNames.ContentLocation]; set => this[HeaderNames.ContentLocation] = value; }
+ /// <summary>Gets or sets the <c>Content-Location</c> HTTP header.</summary>
+ StringValues ContentLocation { get => this[HeaderNames.ContentLocation]; set => this[HeaderNames.ContentLocation] = value; }
- /// <summary>Gets or sets the <c>Content-MD5</c> HTTP header.</summary>
- StringValues ContentMD5 { get => this[HeaderNames.ContentMD5]; set => this[HeaderNames.ContentMD5] = value; }
+ /// <summary>Gets or sets the <c>Content-MD5</c> HTTP header.</summary>
+ StringValues ContentMD5 { get => this[HeaderNames.ContentMD5]; set => this[HeaderNames.ContentMD5] = value; }
- /// <summary>Gets or sets the <c>Content-Range</c> HTTP header.</summary>
- StringValues ContentRange { get => this[HeaderNames.ContentRange]; set => this[HeaderNames.ContentRange] = value; }
+ /// <summary>Gets or sets the <c>Content-Range</c> HTTP header.</summary>
+ StringValues ContentRange { get => this[HeaderNames.ContentRange]; set => this[HeaderNames.ContentRange] = value; }
- /// <summary>Gets or sets the <c>Content-Security-Policy</c> HTTP header.</summary>
- StringValues ContentSecurityPolicy { get => this[HeaderNames.ContentSecurityPolicy]; set => this[HeaderNames.ContentSecurityPolicy] = value; }
+ /// <summary>Gets or sets the <c>Content-Security-Policy</c> HTTP header.</summary>
+ StringValues ContentSecurityPolicy { get => this[HeaderNames.ContentSecurityPolicy]; set => this[HeaderNames.ContentSecurityPolicy] = value; }
- /// <summary>Gets or sets the <c>Content-Security-Policy-Report-Only</c> HTTP header.</summary>
- StringValues ContentSecurityPolicyReportOnly { get => this[HeaderNames.ContentSecurityPolicyReportOnly]; set => this[HeaderNames.ContentSecurityPolicyReportOnly] = value; }
+ /// <summary>Gets or sets the <c>Content-Security-Policy-Report-Only</c> HTTP header.</summary>
+ StringValues ContentSecurityPolicyReportOnly { get => this[HeaderNames.ContentSecurityPolicyReportOnly]; set => this[HeaderNames.ContentSecurityPolicyReportOnly] = value; }
- /// <summary>Gets or sets the <c>Content-Type</c> HTTP header.</summary>
- StringValues ContentType { get => this[HeaderNames.ContentType]; set => this[HeaderNames.ContentType] = value; }
+ /// <summary>Gets or sets the <c>Content-Type</c> HTTP header.</summary>
+ StringValues ContentType { get => this[HeaderNames.ContentType]; set => this[HeaderNames.ContentType] = value; }
- /// <summary>Gets or sets the <c>Correlation-Context</c> HTTP header.</summary>
- StringValues CorrelationContext { get => this[HeaderNames.CorrelationContext]; set => this[HeaderNames.CorrelationContext] = value; }
+ /// <summary>Gets or sets the <c>Correlation-Context</c> HTTP header.</summary>
+ StringValues CorrelationContext { get => this[HeaderNames.CorrelationContext]; set => this[HeaderNames.CorrelationContext] = value; }
- /// <summary>Gets or sets the <c>Cookie</c> HTTP header.</summary>
- StringValues Cookie { get => this[HeaderNames.Cookie]; set => this[HeaderNames.Cookie] = value; }
+ /// <summary>Gets or sets the <c>Cookie</c> HTTP header.</summary>
+ StringValues Cookie { get => this[HeaderNames.Cookie]; set => this[HeaderNames.Cookie] = value; }
- /// <summary>Gets or sets the <c>Date</c> HTTP header.</summary>
- StringValues Date { get => this[HeaderNames.Date]; set => this[HeaderNames.Date] = value; }
+ /// <summary>Gets or sets the <c>Date</c> HTTP header.</summary>
+ StringValues Date { get => this[HeaderNames.Date]; set => this[HeaderNames.Date] = value; }
- /// <summary>Gets or sets the <c>ETag</c> HTTP header.</summary>
- StringValues ETag { get => this[HeaderNames.ETag]; set => this[HeaderNames.ETag] = value; }
+ /// <summary>Gets or sets the <c>ETag</c> HTTP header.</summary>
+ StringValues ETag { get => this[HeaderNames.ETag]; set => this[HeaderNames.ETag] = value; }
- /// <summary>Gets or sets the <c>Expires</c> HTTP header.</summary>
- StringValues Expires { get => this[HeaderNames.Expires]; set => this[HeaderNames.Expires] = value; }
+ /// <summary>Gets or sets the <c>Expires</c> HTTP header.</summary>
+ StringValues Expires { get => this[HeaderNames.Expires]; set => this[HeaderNames.Expires] = value; }
- /// <summary>Gets or sets the <c>Expect</c> HTTP header.</summary>
- StringValues Expect { get => this[HeaderNames.Expect]; set => this[HeaderNames.Expect] = value; }
+ /// <summary>Gets or sets the <c>Expect</c> HTTP header.</summary>
+ StringValues Expect { get => this[HeaderNames.Expect]; set => this[HeaderNames.Expect] = value; }
- /// <summary>Gets or sets the <c>From</c> HTTP header.</summary>
- StringValues From { get => this[HeaderNames.From]; set => this[HeaderNames.From] = value; }
+ /// <summary>Gets or sets the <c>From</c> HTTP header.</summary>
+ StringValues From { get => this[HeaderNames.From]; set => this[HeaderNames.From] = value; }
- /// <summary>Gets or sets the <c>Grpc-Accept-Encoding</c> HTTP header.</summary>
- StringValues GrpcAcceptEncoding { get => this[HeaderNames.GrpcAcceptEncoding]; set => this[HeaderNames.GrpcAcceptEncoding] = value; }
+ /// <summary>Gets or sets the <c>Grpc-Accept-Encoding</c> HTTP header.</summary>
+ StringValues GrpcAcceptEncoding { get => this[HeaderNames.GrpcAcceptEncoding]; set => this[HeaderNames.GrpcAcceptEncoding] = value; }
- /// <summary>Gets or sets the <c>Grpc-Encoding</c> HTTP header.</summary>
- StringValues GrpcEncoding { get => this[HeaderNames.GrpcEncoding]; set => this[HeaderNames.GrpcEncoding] = value; }
+ /// <summary>Gets or sets the <c>Grpc-Encoding</c> HTTP header.</summary>
+ StringValues GrpcEncoding { get => this[HeaderNames.GrpcEncoding]; set => this[HeaderNames.GrpcEncoding] = value; }
- /// <summary>Gets or sets the <c>Grpc-Message</c> HTTP header.</summary>
- StringValues GrpcMessage { get => this[HeaderNames.GrpcMessage]; set => this[HeaderNames.GrpcMessage] = value; }
+ /// <summary>Gets or sets the <c>Grpc-Message</c> HTTP header.</summary>
+ StringValues GrpcMessage { get => this[HeaderNames.GrpcMessage]; set => this[HeaderNames.GrpcMessage] = value; }
- /// <summary>Gets or sets the <c>Grpc-Status</c> HTTP header.</summary>
- StringValues GrpcStatus { get => this[HeaderNames.GrpcStatus]; set => this[HeaderNames.GrpcStatus] = value; }
+ /// <summary>Gets or sets the <c>Grpc-Status</c> HTTP header.</summary>
+ StringValues GrpcStatus { get => this[HeaderNames.GrpcStatus]; set => this[HeaderNames.GrpcStatus] = value; }
- /// <summary>Gets or sets the <c>Grpc-Timeout</c> HTTP header.</summary>
- StringValues GrpcTimeout { get => this[HeaderNames.GrpcTimeout]; set => this[HeaderNames.GrpcTimeout] = value; }
+ /// <summary>Gets or sets the <c>Grpc-Timeout</c> HTTP header.</summary>
+ StringValues GrpcTimeout { get => this[HeaderNames.GrpcTimeout]; set => this[HeaderNames.GrpcTimeout] = value; }
- /// <summary>Gets or sets the <c>Host</c> HTTP header.</summary>
- StringValues Host { get => this[HeaderNames.Host]; set => this[HeaderNames.Host] = value; }
+ /// <summary>Gets or sets the <c>Host</c> HTTP header.</summary>
+ StringValues Host { get => this[HeaderNames.Host]; set => this[HeaderNames.Host] = value; }
- /// <summary>Gets or sets the <c>Keep-Alive</c> HTTP header.</summary>
- StringValues KeepAlive { get => this[HeaderNames.KeepAlive]; set => this[HeaderNames.KeepAlive] = value; }
+ /// <summary>Gets or sets the <c>Keep-Alive</c> HTTP header.</summary>
+ StringValues KeepAlive { get => this[HeaderNames.KeepAlive]; set => this[HeaderNames.KeepAlive] = value; }
- /// <summary>Gets or sets the <c>If-Match</c> HTTP header.</summary>
- StringValues IfMatch { get => this[HeaderNames.IfMatch]; set => this[HeaderNames.IfMatch] = value; }
+ /// <summary>Gets or sets the <c>If-Match</c> HTTP header.</summary>
+ StringValues IfMatch { get => this[HeaderNames.IfMatch]; set => this[HeaderNames.IfMatch] = value; }
- /// <summary>Gets or sets the <c>If-Modified-Since</c> HTTP header.</summary>
- StringValues IfModifiedSince { get => this[HeaderNames.IfModifiedSince]; set => this[HeaderNames.IfModifiedSince] = value; }
+ /// <summary>Gets or sets the <c>If-Modified-Since</c> HTTP header.</summary>
+ StringValues IfModifiedSince { get => this[HeaderNames.IfModifiedSince]; set => this[HeaderNames.IfModifiedSince] = value; }
- /// <summary>Gets or sets the <c>If-None-Match</c> HTTP header.</summary>
- StringValues IfNoneMatch { get => this[HeaderNames.IfNoneMatch]; set => this[HeaderNames.IfNoneMatch] = value; }
+ /// <summary>Gets or sets the <c>If-None-Match</c> HTTP header.</summary>
+ StringValues IfNoneMatch { get => this[HeaderNames.IfNoneMatch]; set => this[HeaderNames.IfNoneMatch] = value; }
- /// <summary>Gets or sets the <c>If-Range</c> HTTP header.</summary>
- StringValues IfRange { get => this[HeaderNames.IfRange]; set => this[HeaderNames.IfRange] = value; }
+ /// <summary>Gets or sets the <c>If-Range</c> HTTP header.</summary>
+ StringValues IfRange { get => this[HeaderNames.IfRange]; set => this[HeaderNames.IfRange] = value; }
- /// <summary>Gets or sets the <c>If-Unmodified-Since</c> HTTP header.</summary>
- StringValues IfUnmodifiedSince { get => this[HeaderNames.IfUnmodifiedSince]; set => this[HeaderNames.IfUnmodifiedSince] = value; }
+ /// <summary>Gets or sets the <c>If-Unmodified-Since</c> HTTP header.</summary>
+ StringValues IfUnmodifiedSince { get => this[HeaderNames.IfUnmodifiedSince]; set => this[HeaderNames.IfUnmodifiedSince] = value; }
- /// <summary>Gets or sets the <c>Last-Modified</c> HTTP header.</summary>
- StringValues LastModified { get => this[HeaderNames.LastModified]; set => this[HeaderNames.LastModified] = value; }
+ /// <summary>Gets or sets the <c>Last-Modified</c> HTTP header.</summary>
+ StringValues LastModified { get => this[HeaderNames.LastModified]; set => this[HeaderNames.LastModified] = value; }
- /// <summary>Gets or sets the <c>Link</c> HTTP header.</summary>
- StringValues Link { get => this[HeaderNames.Link]; set => this[HeaderNames.Link] = value; }
+ /// <summary>Gets or sets the <c>Link</c> HTTP header.</summary>
+ StringValues Link { get => this[HeaderNames.Link]; set => this[HeaderNames.Link] = value; }
- /// <summary>Gets or sets the <c>Location</c> HTTP header.</summary>
- StringValues Location { get => this[HeaderNames.Location]; set => this[HeaderNames.Location] = value; }
+ /// <summary>Gets or sets the <c>Location</c> HTTP header.</summary>
+ StringValues Location { get => this[HeaderNames.Location]; set => this[HeaderNames.Location] = value; }
- /// <summary>Gets or sets the <c>Max-Forwards</c> HTTP header.</summary>
- StringValues MaxForwards { get => this[HeaderNames.MaxForwards]; set => this[HeaderNames.MaxForwards] = value; }
+ /// <summary>Gets or sets the <c>Max-Forwards</c> HTTP header.</summary>
+ StringValues MaxForwards { get => this[HeaderNames.MaxForwards]; set => this[HeaderNames.MaxForwards] = value; }
- /// <summary>Gets or sets the <c>Origin</c> HTTP header.</summary>
- StringValues Origin { get => this[HeaderNames.Origin]; set => this[HeaderNames.Origin] = value; }
+ /// <summary>Gets or sets the <c>Origin</c> HTTP header.</summary>
+ StringValues Origin { get => this[HeaderNames.Origin]; set => this[HeaderNames.Origin] = value; }
- /// <summary>Gets or sets the <c>Pragma</c> HTTP header.</summary>
- StringValues Pragma { get => this[HeaderNames.Pragma]; set => this[HeaderNames.Pragma] = value; }
+ /// <summary>Gets or sets the <c>Pragma</c> HTTP header.</summary>
+ StringValues Pragma { get => this[HeaderNames.Pragma]; set => this[HeaderNames.Pragma] = value; }
- /// <summary>Gets or sets the <c>Proxy-Authenticate</c> HTTP header.</summary>
- StringValues ProxyAuthenticate { get => this[HeaderNames.ProxyAuthenticate]; set => this[HeaderNames.ProxyAuthenticate] = value; }
+ /// <summary>Gets or sets the <c>Proxy-Authenticate</c> HTTP header.</summary>
+ StringValues ProxyAuthenticate { get => this[HeaderNames.ProxyAuthenticate]; set => this[HeaderNames.ProxyAuthenticate] = value; }
- /// <summary>Gets or sets the <c>Proxy-Authorization</c> HTTP header.</summary>
- StringValues ProxyAuthorization { get => this[HeaderNames.ProxyAuthorization]; set => this[HeaderNames.ProxyAuthorization] = value; }
+ /// <summary>Gets or sets the <c>Proxy-Authorization</c> HTTP header.</summary>
+ StringValues ProxyAuthorization { get => this[HeaderNames.ProxyAuthorization]; set => this[HeaderNames.ProxyAuthorization] = value; }
- /// <summary>Gets or sets the <c>Proxy-Connection</c> HTTP header.</summary>
- StringValues ProxyConnection { get => this[HeaderNames.ProxyConnection]; set => this[HeaderNames.ProxyConnection] = value; }
+ /// <summary>Gets or sets the <c>Proxy-Connection</c> HTTP header.</summary>
+ StringValues ProxyConnection { get => this[HeaderNames.ProxyConnection]; set => this[HeaderNames.ProxyConnection] = value; }
- /// <summary>Gets or sets the <c>Range</c> HTTP header.</summary>
- StringValues Range { get => this[HeaderNames.Range]; set => this[HeaderNames.Range] = value; }
+ /// <summary>Gets or sets the <c>Range</c> HTTP header.</summary>
+ StringValues Range { get => this[HeaderNames.Range]; set => this[HeaderNames.Range] = value; }
- /// <summary>Gets or sets the <c>Referer</c> HTTP header.</summary>
- StringValues Referer { get => this[HeaderNames.Referer]; set => this[HeaderNames.Referer] = value; }
+ /// <summary>Gets or sets the <c>Referer</c> HTTP header.</summary>
+ StringValues Referer { get => this[HeaderNames.Referer]; set => this[HeaderNames.Referer] = value; }
- /// <summary>Gets or sets the <c>Retry-After</c> HTTP header.</summary>
- StringValues RetryAfter { get => this[HeaderNames.RetryAfter]; set => this[HeaderNames.RetryAfter] = value; }
+ /// <summary>Gets or sets the <c>Retry-After</c> HTTP header.</summary>
+ StringValues RetryAfter { get => this[HeaderNames.RetryAfter]; set => this[HeaderNames.RetryAfter] = value; }
- /// <summary>Gets or sets the <c>Request-Id</c> HTTP header.</summary>
- StringValues RequestId { get => this[HeaderNames.RequestId]; set => this[HeaderNames.RequestId] = value; }
+ /// <summary>Gets or sets the <c>Request-Id</c> HTTP header.</summary>
+ StringValues RequestId { get => this[HeaderNames.RequestId]; set => this[HeaderNames.RequestId] = value; }
- /// <summary>Gets or sets the <c>Sec-WebSocket-Accept</c> HTTP header.</summary>
- StringValues SecWebSocketAccept { get => this[HeaderNames.SecWebSocketAccept]; set => this[HeaderNames.SecWebSocketAccept] = value; }
+ /// <summary>Gets or sets the <c>Sec-WebSocket-Accept</c> HTTP header.</summary>
+ StringValues SecWebSocketAccept { get => this[HeaderNames.SecWebSocketAccept]; set => this[HeaderNames.SecWebSocketAccept] = value; }
- /// <summary>Gets or sets the <c>Sec-WebSocket-Key</c> HTTP header.</summary>
- StringValues SecWebSocketKey { get => this[HeaderNames.SecWebSocketKey]; set => this[HeaderNames.SecWebSocketKey] = value; }
+ /// <summary>Gets or sets the <c>Sec-WebSocket-Key</c> HTTP header.</summary>
+ StringValues SecWebSocketKey { get => this[HeaderNames.SecWebSocketKey]; set => this[HeaderNames.SecWebSocketKey] = value; }
- /// <summary>Gets or sets the <c>Sec-WebSocket-Protocol</c> HTTP header.</summary>
- StringValues SecWebSocketProtocol { get => this[HeaderNames.SecWebSocketProtocol]; set => this[HeaderNames.SecWebSocketProtocol] = value; }
+ /// <summary>Gets or sets the <c>Sec-WebSocket-Protocol</c> HTTP header.</summary>
+ StringValues SecWebSocketProtocol { get => this[HeaderNames.SecWebSocketProtocol]; set => this[HeaderNames.SecWebSocketProtocol] = value; }
- /// <summary>Gets or sets the <c>Sec-WebSocket-Version</c> HTTP header.</summary>
- StringValues SecWebSocketVersion { get => this[HeaderNames.SecWebSocketVersion]; set => this[HeaderNames.SecWebSocketVersion] = value; }
+ /// <summary>Gets or sets the <c>Sec-WebSocket-Version</c> HTTP header.</summary>
+ StringValues SecWebSocketVersion { get => this[HeaderNames.SecWebSocketVersion]; set => this[HeaderNames.SecWebSocketVersion] = value; }
- /// <summary>Gets or sets the <c>Sec-WebSocket-Extensions</c> HTTP header.</summary>
- StringValues SecWebSocketExtensions { get => this[HeaderNames.SecWebSocketExtensions]; set => this[HeaderNames.SecWebSocketExtensions] = value; }
+ /// <summary>Gets or sets the <c>Sec-WebSocket-Extensions</c> HTTP header.</summary>
+ StringValues SecWebSocketExtensions { get => this[HeaderNames.SecWebSocketExtensions]; set => this[HeaderNames.SecWebSocketExtensions] = value; }
- /// <summary>Gets or sets the <c>Server</c> HTTP header.</summary>
- StringValues Server { get => this[HeaderNames.Server]; set => this[HeaderNames.Server] = value; }
+ /// <summary>Gets or sets the <c>Server</c> HTTP header.</summary>
+ StringValues Server { get => this[HeaderNames.Server]; set => this[HeaderNames.Server] = value; }
- /// <summary>Gets or sets the <c>Set-Cookie</c> HTTP header.</summary>
- StringValues SetCookie { get => this[HeaderNames.SetCookie]; set => this[HeaderNames.SetCookie] = value; }
+ /// <summary>Gets or sets the <c>Set-Cookie</c> HTTP header.</summary>
+ StringValues SetCookie { get => this[HeaderNames.SetCookie]; set => this[HeaderNames.SetCookie] = value; }
- /// <summary>Gets or sets the <c>Strict-Transport-Security</c> HTTP header.</summary>
- StringValues StrictTransportSecurity { get => this[HeaderNames.StrictTransportSecurity]; set => this[HeaderNames.StrictTransportSecurity] = value; }
+ /// <summary>Gets or sets the <c>Strict-Transport-Security</c> HTTP header.</summary>
+ StringValues StrictTransportSecurity { get => this[HeaderNames.StrictTransportSecurity]; set => this[HeaderNames.StrictTransportSecurity] = value; }
- /// <summary>Gets or sets the <c>TE</c> HTTP header.</summary>
- StringValues TE { get => this[HeaderNames.TE]; set => this[HeaderNames.TE] = value; }
+ /// <summary>Gets or sets the <c>TE</c> HTTP header.</summary>
+ StringValues TE { get => this[HeaderNames.TE]; set => this[HeaderNames.TE] = value; }
- /// <summary>Gets or sets the <c>Trailer</c> HTTP header.</summary>
- StringValues Trailer { get => this[HeaderNames.Trailer]; set => this[HeaderNames.Trailer] = value; }
+ /// <summary>Gets or sets the <c>Trailer</c> HTTP header.</summary>
+ StringValues Trailer { get => this[HeaderNames.Trailer]; set => this[HeaderNames.Trailer] = value; }
- /// <summary>Gets or sets the <c>Transfer-Encoding</c> HTTP header.</summary>
- StringValues TransferEncoding { get => this[HeaderNames.TransferEncoding]; set => this[HeaderNames.TransferEncoding] = value; }
+ /// <summary>Gets or sets the <c>Transfer-Encoding</c> HTTP header.</summary>
+ StringValues TransferEncoding { get => this[HeaderNames.TransferEncoding]; set => this[HeaderNames.TransferEncoding] = value; }
- /// <summary>Gets or sets the <c>Translate</c> HTTP header.</summary>
- StringValues Translate { get => this[HeaderNames.Translate]; set => this[HeaderNames.Translate] = value; }
+ /// <summary>Gets or sets the <c>Translate</c> HTTP header.</summary>
+ StringValues Translate { get => this[HeaderNames.Translate]; set => this[HeaderNames.Translate] = value; }
- /// <summary>Gets or sets the <c>traceparent</c> HTTP header.</summary>
- StringValues TraceParent { get => this[HeaderNames.TraceParent]; set => this[HeaderNames.TraceParent] = value; }
+ /// <summary>Gets or sets the <c>traceparent</c> HTTP header.</summary>
+ StringValues TraceParent { get => this[HeaderNames.TraceParent]; set => this[HeaderNames.TraceParent] = value; }
- /// <summary>Gets or sets the <c>tracestate</c> HTTP header.</summary>
- StringValues TraceState { get => this[HeaderNames.TraceState]; set => this[HeaderNames.TraceState] = value; }
+ /// <summary>Gets or sets the <c>tracestate</c> HTTP header.</summary>
+ StringValues TraceState { get => this[HeaderNames.TraceState]; set => this[HeaderNames.TraceState] = value; }
- /// <summary>Gets or sets the <c>Upgrade</c> HTTP header.</summary>
- StringValues Upgrade { get => this[HeaderNames.Upgrade]; set => this[HeaderNames.Upgrade] = value; }
+ /// <summary>Gets or sets the <c>Upgrade</c> HTTP header.</summary>
+ StringValues Upgrade { get => this[HeaderNames.Upgrade]; set => this[HeaderNames.Upgrade] = value; }
- /// <summary>Gets or sets the <c>Upgrade-Insecure-Requests</c> HTTP header.</summary>
- StringValues UpgradeInsecureRequests { get => this[HeaderNames.UpgradeInsecureRequests]; set => this[HeaderNames.UpgradeInsecureRequests] = value; }
+ /// <summary>Gets or sets the <c>Upgrade-Insecure-Requests</c> HTTP header.</summary>
+ StringValues UpgradeInsecureRequests { get => this[HeaderNames.UpgradeInsecureRequests]; set => this[HeaderNames.UpgradeInsecureRequests] = value; }
- /// <summary>Gets or sets the <c>User-Agent</c> HTTP header.</summary>
- StringValues UserAgent { get => this[HeaderNames.UserAgent]; set => this[HeaderNames.UserAgent] = value; }
+ /// <summary>Gets or sets the <c>User-Agent</c> HTTP header.</summary>
+ StringValues UserAgent { get => this[HeaderNames.UserAgent]; set => this[HeaderNames.UserAgent] = value; }
- /// <summary>Gets or sets the <c>Vary</c> HTTP header.</summary>
- StringValues Vary { get => this[HeaderNames.Vary]; set => this[HeaderNames.Vary] = value; }
+ /// <summary>Gets or sets the <c>Vary</c> HTTP header.</summary>
+ StringValues Vary { get => this[HeaderNames.Vary]; set => this[HeaderNames.Vary] = value; }
- /// <summary>Gets or sets the <c>Via</c> HTTP header.</summary>
- StringValues Via { get => this[HeaderNames.Via]; set => this[HeaderNames.Via] = value; }
+ /// <summary>Gets or sets the <c>Via</c> HTTP header.</summary>
+ StringValues Via { get => this[HeaderNames.Via]; set => this[HeaderNames.Via] = value; }
- /// <summary>Gets or sets the <c>Warning</c> HTTP header.</summary>
- StringValues Warning { get => this[HeaderNames.Warning]; set => this[HeaderNames.Warning] = value; }
+ /// <summary>Gets or sets the <c>Warning</c> HTTP header.</summary>
+ StringValues Warning { get => this[HeaderNames.Warning]; set => this[HeaderNames.Warning] = value; }
- /// <summary>Gets or sets the <c>Sec-WebSocket-Protocol</c> HTTP header.</summary>
- StringValues WebSocketSubProtocols { get => this[HeaderNames.WebSocketSubProtocols]; set => this[HeaderNames.WebSocketSubProtocols] = value; }
+ /// <summary>Gets or sets the <c>Sec-WebSocket-Protocol</c> HTTP header.</summary>
+ StringValues WebSocketSubProtocols { get => this[HeaderNames.WebSocketSubProtocols]; set => this[HeaderNames.WebSocketSubProtocols] = value; }
- /// <summary>Gets or sets the <c>WWW-Authenticate</c> HTTP header.</summary>
- StringValues WWWAuthenticate { get => this[HeaderNames.WWWAuthenticate]; set => this[HeaderNames.WWWAuthenticate] = value; }
+ /// <summary>Gets or sets the <c>WWW-Authenticate</c> HTTP header.</summary>
+ StringValues WWWAuthenticate { get => this[HeaderNames.WWWAuthenticate]; set => this[HeaderNames.WWWAuthenticate] = value; }
- /// <summary>Gets or sets the <c>X-Content-Type-Options</c> HTTP header.</summary>
- StringValues XContentTypeOptions { get => this[HeaderNames.XContentTypeOptions]; set => this[HeaderNames.XContentTypeOptions] = value; }
+ /// <summary>Gets or sets the <c>X-Content-Type-Options</c> HTTP header.</summary>
+ StringValues XContentTypeOptions { get => this[HeaderNames.XContentTypeOptions]; set => this[HeaderNames.XContentTypeOptions] = value; }
- /// <summary>Gets or sets the <c>X-Frame-Options</c> HTTP header.</summary>
- StringValues XFrameOptions { get => this[HeaderNames.XFrameOptions]; set => this[HeaderNames.XFrameOptions] = value; }
+ /// <summary>Gets or sets the <c>X-Frame-Options</c> HTTP header.</summary>
+ StringValues XFrameOptions { get => this[HeaderNames.XFrameOptions]; set => this[HeaderNames.XFrameOptions] = value; }
- /// <summary>Gets or sets the <c>X-Powered-By</c> HTTP header.</summary>
- StringValues XPoweredBy { get => this[HeaderNames.XPoweredBy]; set => this[HeaderNames.XPoweredBy] = value; }
+ /// <summary>Gets or sets the <c>X-Powered-By</c> HTTP header.</summary>
+ StringValues XPoweredBy { get => this[HeaderNames.XPoweredBy]; set => this[HeaderNames.XPoweredBy] = value; }
- /// <summary>Gets or sets the <c>X-Requested-With</c> HTTP header.</summary>
- StringValues XRequestedWith { get => this[HeaderNames.XRequestedWith]; set => this[HeaderNames.XRequestedWith] = value; }
+ /// <summary>Gets or sets the <c>X-Requested-With</c> HTTP header.</summary>
+ StringValues XRequestedWith { get => this[HeaderNames.XRequestedWith]; set => this[HeaderNames.XRequestedWith] = value; }
- /// <summary>Gets or sets the <c>X-UA-Compatible</c> HTTP header.</summary>
- StringValues XUACompatible { get => this[HeaderNames.XUACompatible]; set => this[HeaderNames.XUACompatible] = value; }
+ /// <summary>Gets or sets the <c>X-UA-Compatible</c> HTTP header.</summary>
+ StringValues XUACompatible { get => this[HeaderNames.XUACompatible]; set => this[HeaderNames.XUACompatible] = value; }
- /// <summary>Gets or sets the <c>X-XSS-Protection</c> HTTP header.</summary>
- StringValues XXSSProtection { get => this[HeaderNames.XXSSProtection]; set => this[HeaderNames.XXSSProtection] = value; }
- }
+ /// <summary>Gets or sets the <c>X-XSS-Protection</c> HTTP header.</summary>
+ StringValues XXSSProtection { get => this[HeaderNames.XXSSProtection]; set => this[HeaderNames.XXSSProtection] = value; }
}
diff --git a/src/Http/Http.Features/src/IHeaderDictionary.cs b/src/Http/Http.Features/src/IHeaderDictionary.cs
index 8bf46a1052..6e5d3703d4 100644
--- a/src/Http/Http.Features/src/IHeaderDictionary.cs
+++ b/src/Http/Http.Features/src/IHeaderDictionary.cs
@@ -4,23 +4,22 @@
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents HttpRequest and HttpResponse headers
+/// </summary>
+public partial interface IHeaderDictionary : IDictionary<string, StringValues>
{
/// <summary>
- /// Represents HttpRequest and HttpResponse headers
+ /// IHeaderDictionary has a different indexer contract than IDictionary, where it will return StringValues.Empty for missing entries.
/// </summary>
- public partial 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; }
+ /// <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; }
- }
+ /// <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
index c000317789..01ee7904c2 100644
--- a/src/Http/Http.Features/src/IHttpBodyControlFeature.cs
+++ b/src/Http/Http.Features/src/IHttpBodyControlFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+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>
- /// Controls the IO behavior for the <see cref="IHttpRequestFeature.Body"/> and <see cref="IHttpResponseFeature.Body"/>
+ /// Gets or sets a value that controls whether synchronous IO is allowed 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; }
- }
+ bool AllowSynchronousIO { get; set; }
}
diff --git a/src/Http/Http.Features/src/IHttpConnectionFeature.cs b/src/Http/Http.Features/src/IHttpConnectionFeature.cs
index 2a260df3ee..fca9a367c8 100644
--- a/src/Http/Http.Features/src/IHttpConnectionFeature.cs
+++ b/src/Http/Http.Features/src/IHttpConnectionFeature.cs
@@ -3,36 +3,35 @@
using System.Net;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Information regarding the TCP/IP connection carrying the request.
+/// </summary>
+public interface IHttpConnectionFeature
{
/// <summary>
- /// Information regarding the TCP/IP connection carrying the request.
+ /// Gets or sets the unique identifier for the connection the request was received on. This is primarily for diagnostic purposes.
/// </summary>
- public interface IHttpConnectionFeature
- {
- /// <summary>
- /// Gets or sets the unique identifier for the connection the request was received on. This is primarily for diagnostic purposes.
- /// </summary>
- string ConnectionId { get; set; }
+ string ConnectionId { get; set; }
- /// <summary>
- /// Gets or sets 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>
+ /// Gets or sets 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>
- /// Gets or sets the local IPAddress on which the request was received.
- /// </summary>
- IPAddress? LocalIpAddress { get; set; }
+ /// <summary>
+ /// Gets or sets the local IPAddress on which the request was received.
+ /// </summary>
+ IPAddress? LocalIpAddress { get; set; }
- /// <summary>
- /// Gets or sets the remote port of the client making the request.
- /// </summary>
- int RemotePort { get; set; }
+ /// <summary>
+ /// Gets or sets the remote port of the client making the request.
+ /// </summary>
+ int RemotePort { get; set; }
- /// <summary>
- /// Gets or sets the local port on which the request was received.
- /// </summary>
- int LocalPort { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the local port on which the request was received.
+ /// </summary>
+ int LocalPort { get; set; }
}
diff --git a/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs b/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs
index 2f0af2db5d..541cf8a6ec 100644
--- a/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs
+++ b/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs
@@ -1,29 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+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>
- /// Feature to inspect and modify the maximum request body size for a single request.
+ /// 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>
- 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; }
+ 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
+ /// <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; }
+}
diff --git a/src/Http/Http.Features/src/IHttpRequestBodyDetectionFeature.cs b/src/Http/Http.Features/src/IHttpRequestBodyDetectionFeature.cs
index 15eed71722..7d9d64c3fd 100644
--- a/src/Http/Http.Features/src/IHttpRequestBodyDetectionFeature.cs
+++ b/src/Http/Http.Features/src/IHttpRequestBodyDetectionFeature.cs
@@ -1,29 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Used to indicate if the request can have a body.
+/// </summary>
+public interface IHttpRequestBodyDetectionFeature
{
/// <summary>
- /// Used to indicate if the request can have a body.
+ /// Indicates if the request can have a body.
/// </summary>
- public interface IHttpRequestBodyDetectionFeature
- {
- /// <summary>
- /// Indicates if the request can have a body.
- /// </summary>
- /// <remarks>
- /// This returns true when:
- /// - It's an HTTP/1.x request with a non-zero Content-Length or a 'Transfer-Encoding: chunked' header.
- /// - It's an HTTP/2 request that did not set the END_STREAM flag on the initial headers frame.
- /// The final request body length may still be zero for the chunked or HTTP/2 scenarios.
- ///
- /// This returns false when:
- /// - It's an HTTP/1.x request with no Content-Length or 'Transfer-Encoding: chunked' header, or the Content-Length is 0.
- /// - It's an HTTP/1.x request with Connection: Upgrade (e.g. WebSockets). There is no HTTP request body for these requests and
- /// no data should be received until after the upgrade.
- /// - It's an HTTP/2 request that set END_STREAM on the initial headers frame.
- /// When false, the request body should never return data.
- /// </remarks>
- bool CanHaveBody { get; }
- }
+ /// <remarks>
+ /// This returns true when:
+ /// - It's an HTTP/1.x request with a non-zero Content-Length or a 'Transfer-Encoding: chunked' header.
+ /// - It's an HTTP/2 request that did not set the END_STREAM flag on the initial headers frame.
+ /// The final request body length may still be zero for the chunked or HTTP/2 scenarios.
+ ///
+ /// This returns false when:
+ /// - It's an HTTP/1.x request with no Content-Length or 'Transfer-Encoding: chunked' header, or the Content-Length is 0.
+ /// - It's an HTTP/1.x request with Connection: Upgrade (e.g. WebSockets). There is no HTTP request body for these requests and
+ /// no data should be received until after the upgrade.
+ /// - It's an HTTP/2 request that set END_STREAM on the initial headers frame.
+ /// When false, the request body should never return data.
+ /// </remarks>
+ bool CanHaveBody { get; }
}
diff --git a/src/Http/Http.Features/src/IHttpRequestFeature.cs b/src/Http/Http.Features/src/IHttpRequestFeature.cs
index 7f2e93baba..9a014bc7f5 100644
--- a/src/Http/Http.Features/src/IHttpRequestFeature.cs
+++ b/src/Http/Http.Features/src/IHttpRequestFeature.cs
@@ -3,90 +3,89 @@
using System.IO;
-namespace Microsoft.AspNetCore.Http.Features
+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>
- /// Contains the details of a given request. These properties should all be mutable.
- /// None of these properties should ever be set to null.
+ /// Gets or set the HTTP-version as defined in RFC 7230. E.g. "HTTP/1.1"
/// </summary>
- public interface IHttpRequestFeature
- {
- /// <summary>
- /// Gets or set the HTTP-version as defined in RFC 7230. E.g. "HTTP/1.1"
- /// </summary>
- string Protocol { get; set; }
+ string Protocol { get; set; }
- /// <summary>
- /// Gets or set the request uri scheme. E.g. "http" or "https".
- /// <para>
- /// Note this value is not included in the original request,
- /// it is inferred by checking if the transport used a TLS
- /// connection or not.
- /// </para>
- /// </summary>
- string Scheme { get; set; }
+ /// <summary>
+ /// Gets or set the request uri scheme. E.g. "http" or "https".
+ /// <para>
+ /// Note this value is not included in the original request,
+ /// it is inferred by checking if the transport used a TLS
+ /// connection or not.
+ /// </para>
+ /// </summary>
+ string Scheme { get; set; }
- /// <summary>
- /// Gets or sets the request method as defined in RFC 7230. E.g. "GET", "HEAD", "POST", etc..
- /// </summary>
- string Method { get; set; }
+ /// <summary>
+ /// Gets or sets the request method as defined in RFC 7230. E.g. "GET", "HEAD", "POST", etc..
+ /// </summary>
+ string Method { get; set; }
- /// <summary>
- /// Gets or sets the first portion of the request path associated with application root.
- /// <para>
- /// The value is un-escaped. The value may be <see cref="string.Empty"/>.
- /// </para>
- /// </summary>
- string PathBase { get; set; }
+ /// <summary>
+ /// Gets or sets the first portion of the request path associated with application root.
+ /// <para>
+ /// The value is un-escaped. The value may be <see cref="string.Empty"/>.
+ /// </para>
+ /// </summary>
+ string PathBase { get; set; }
- /// <summary>
- /// Gets or sets the portion of the request path that identifies the requested resource.
- /// <para>
- /// The value is un-escaped. The value may be <see cref="string.Empty"/> if <see cref="PathBase"/> contains the
- /// full path.
- /// </para>
- /// </summary>
- string Path { get; set; }
+ /// <summary>
+ /// Gets or sets the portion of the request path that identifies the requested resource.
+ /// <para>
+ /// The value is un-escaped. The value may be <see cref="string.Empty"/> if <see cref="PathBase"/> contains the
+ /// full path.
+ /// </para>
+ /// </summary>
+ string Path { get; set; }
- /// <summary>
- /// Gets or sets the query portion of the request-target as defined in RFC 7230. The value
- /// may be <see cref="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>
+ /// Gets or sets the query portion of the request-target as defined in RFC 7230. The value
+ /// may be <see cref="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>
- /// Gets or sets the request target as it was sent in the HTTP request.
- /// <para>
- /// 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).
- /// </para>
- /// </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>
+ /// Gets or sets the request target as it was sent in the HTTP request.
+ /// <para>
+ /// 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).
+ /// </para>
+ /// </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>
- /// Gets or sets headers included in the request, aggregated by header name.
- /// <para>
- /// The values are not split or merged across header lines. E.g. The following headers:
- /// <list type="bullet">
- /// <item>HeaderA: value1, value2</item>
- /// <item>HeaderA: value3</item>
- /// </list>
- /// Result in Headers["HeaderA"] = { "value1, value2", "value3" }
- /// </para>
- /// </summary>
- IHeaderDictionary Headers { get; set; }
+ /// <summary>
+ /// Gets or sets headers included in the request, aggregated by header name.
+ /// <para>
+ /// The values are not split or merged across header lines. E.g. The following headers:
+ /// <list type="bullet">
+ /// <item>HeaderA: value1, value2</item>
+ /// <item>HeaderA: value3</item>
+ /// </list>
+ /// Result in Headers["HeaderA"] = { "value1, value2", "value3" }
+ /// </para>
+ /// </summary>
+ IHeaderDictionary Headers { get; set; }
- /// <summary>
- /// Gets or sets a <see cref="Stream"/> representing the request body, if any.
- /// <para>
- /// <see cref="Stream.Null"/> may be used to represent an empty request body.
- /// </para>
- /// </summary>
- Stream Body { get; set; }
- }
+ /// <summary>
+ /// Gets or sets a <see cref="Stream"/> representing the request body, if any.
+ /// <para>
+ /// <see cref="Stream.Null"/> may be used to represent an empty request body.
+ /// </para>
+ /// </summary>
+ Stream Body { get; set; }
}
diff --git a/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs b/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs
index 34464b2b1a..76dffaa236 100644
--- a/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs
+++ b/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Feature to uniquely identify a request.
+/// </summary>
+public interface IHttpRequestIdentifierFeature
{
/// <summary>
- /// Feature to uniquely identify a request.
+ /// Gets or sets a value to uniquely identify a request.
+ /// This can be used for logging and diagnostics.
/// </summary>
- public interface IHttpRequestIdentifierFeature
- {
- /// <summary>
- /// Gets or sets a value to uniquely identify a request.
- /// This can be used for logging and diagnostics.
- /// </summary>
- string TraceIdentifier { get; set; }
- }
+ string TraceIdentifier { get; set; }
}
diff --git a/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs b/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs
index 263b68b021..a8b80c5cd1 100644
--- a/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs
+++ b/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs
@@ -3,24 +3,23 @@
using System.Threading;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to the HTTP request lifetime operations.
+/// </summary>
+public interface IHttpRequestLifetimeFeature
{
/// <summary>
- /// Provides access to the HTTP request lifetime operations.
+ /// 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>
- 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; }
+ 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();
- }
+ /// <summary>
+ /// Forcefully aborts the request if it has not already completed. This will result in
+ /// RequestAborted being triggered.
+ /// </summary>
+ void Abort();
}
diff --git a/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs b/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs
index daf65eddbc..063f63a2f2 100644
--- a/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs
+++ b/src/Http/Http.Features/src/IHttpRequestTrailersFeature.cs
@@ -3,24 +3,23 @@
using System;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// This feature exposes HTTP request trailer headers, either for HTTP/1.1 chunked bodies or HTTP/2 trailing headers.
+/// </summary>
+public interface IHttpRequestTrailersFeature
{
/// <summary>
- /// This feature exposes HTTP request trailer headers, either for HTTP/1.1 chunked bodies or HTTP/2 trailing headers.
+ /// Indicates if the <see cref="Trailers"/> are available yet. They may not be available until the
+ /// request body is fully read.
/// </summary>
- public interface IHttpRequestTrailersFeature
- {
- /// <summary>
- /// Indicates if the <see cref="Trailers"/> are available yet. They may not be available until the
- /// request body is fully read.
- /// </summary>
- bool Available { get; }
+ bool Available { get; }
- /// <summary>
- /// The trailing headers received. This will throw <see cref="InvalidOperationException"/> if <see cref="Available"/>
- /// returns false. They may not be available until the request body is fully read. If there are no trailers this will
- /// return an empty collection.
- /// </summary>
- IHeaderDictionary Trailers { get; }
- }
+ /// <summary>
+ /// The trailing headers received. This will throw <see cref="InvalidOperationException"/> if <see cref="Available"/>
+ /// returns false. They may not be available until the request body is fully read. If there are no trailers this will
+ /// return an empty collection.
+ /// </summary>
+ IHeaderDictionary Trailers { get; }
}
diff --git a/src/Http/Http.Features/src/IHttpResetFeature.cs b/src/Http/Http.Features/src/IHttpResetFeature.cs
index 8e3bfe2df8..a11b7cd4e6 100644
--- a/src/Http/Http.Features/src/IHttpResetFeature.cs
+++ b/src/Http/Http.Features/src/IHttpResetFeature.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Used to send reset messages for protocols that support them such as HTTP/2 or HTTP/3.
+/// </summary>
+public interface IHttpResetFeature
{
/// <summary>
- /// Used to send reset messages for protocols that support them such as HTTP/2 or HTTP/3.
+ /// Send a reset message with the given error code. The request will be considered aborted.
/// </summary>
- public interface IHttpResetFeature
- {
- /// <summary>
- /// Send a reset message with the given error code. The request will be considered aborted.
- /// </summary>
- /// <param name="errorCode">The error code to send in the reset message.</param>
- void Reset(int errorCode);
- }
+ /// <param name="errorCode">The error code to send in the reset message.</param>
+ void Reset(int errorCode);
}
diff --git a/src/Http/Http.Features/src/IHttpResponseBodyFeature.cs b/src/Http/Http.Features/src/IHttpResponseBodyFeature.cs
index 3c0f9370de..df6eacf8f2 100644
--- a/src/Http/Http.Features/src/IHttpResponseBodyFeature.cs
+++ b/src/Http/Http.Features/src/IHttpResponseBodyFeature.cs
@@ -6,48 +6,47 @@ using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// An aggregate of the different ways to interact with the response body.
+/// </summary>
+public interface IHttpResponseBodyFeature
{
/// <summary>
- /// An aggregate of the different ways to interact with the response body.
+ /// The <see cref="System.IO.Stream"/> for writing the response body.
+ /// </summary>
+ Stream Stream { get; }
+
+ /// <summary>
+ /// A <see cref="PipeWriter"/> representing the response body, if any.
+ /// </summary>
+ PipeWriter Writer { get; }
+
+ /// <summary>
+ /// Opts out of write buffering for the response.
+ /// </summary>
+ void DisableBuffering();
+
+ /// <summary>
+ /// Starts the response by calling OnStarting() and making headers unmodifiable.
+ /// </summary>
+ Task StartAsync(CancellationToken cancellationToken = default);
+
+ /// <summary>
+ /// Sends the requested file in the response body. 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="cancellationToken">A <see cref="CancellationToken"/> used to abort the transmission.</param>
+ /// <returns></returns>
+ Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken = default);
+
+ /// <summary>
+ /// Flush any remaining response headers, data, or trailers.
+ /// This may throw if the response is in an invalid state such as a Content-Length mismatch.
/// </summary>
- public interface IHttpResponseBodyFeature
- {
- /// <summary>
- /// The <see cref="System.IO.Stream"/> for writing the response body.
- /// </summary>
- Stream Stream { get; }
-
- /// <summary>
- /// A <see cref="PipeWriter"/> representing the response body, if any.
- /// </summary>
- PipeWriter Writer { get; }
-
- /// <summary>
- /// Opts out of write buffering for the response.
- /// </summary>
- void DisableBuffering();
-
- /// <summary>
- /// Starts the response by calling OnStarting() and making headers unmodifiable.
- /// </summary>
- Task StartAsync(CancellationToken cancellationToken = default);
-
- /// <summary>
- /// Sends the requested file in the response body. 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="cancellationToken">A <see cref="CancellationToken"/> used to abort the transmission.</param>
- /// <returns></returns>
- Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken = default);
-
- /// <summary>
- /// Flush any remaining response headers, data, or trailers.
- /// This may throw if the response is in an invalid state such as a Content-Length mismatch.
- /// </summary>
- /// <returns></returns>
- Task CompleteAsync();
- }
+ /// <returns></returns>
+ Task CompleteAsync();
}
diff --git a/src/Http/Http.Features/src/IHttpResponseFeature.cs b/src/Http/Http.Features/src/IHttpResponseFeature.cs
index dc759f6106..2ca35fd491 100644
--- a/src/Http/Http.Features/src/IHttpResponseFeature.cs
+++ b/src/Http/Http.Features/src/IHttpResponseFeature.cs
@@ -5,62 +5,61 @@ using System;
using System.IO;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Represents the fields and state of an HTTP response.
+/// </summary>
+public interface IHttpResponseFeature
{
/// <summary>
- /// Represents the fields and state of an HTTP response.
+ /// Gets or sets the status-code as defined in RFC 7230.
/// </summary>
- public interface IHttpResponseFeature
- {
- /// <summary>
- /// Gets or sets the status-code as defined in RFC 7230.
- /// </summary>
- /// <value>Defaults to <c>200</c>.</value>
- int StatusCode { get; set; }
+ /// <value>Defaults to <c>200</c>.</value>
+ int StatusCode { get; set; }
- /// <summary>
- /// Gets or sets the reason-phrase as defined in RFC 7230. Note this field is no longer supported by HTTP/2.
- /// </summary>
- string? ReasonPhrase { get; set; }
+ /// <summary>
+ /// Gets or sets the reason-phrase as defined in RFC 7230. Note this field is no longer supported by HTTP/2.
+ /// </summary>
+ string? ReasonPhrase { get; set; }
- /// <summary>
- /// Gets or sets the response headers to send. Headers with multiple values will be emitted as multiple headers.
- /// </summary>
- IHeaderDictionary Headers { get; set; }
+ /// <summary>
+ /// Gets or sets the response headers to send. Headers with multiple values will be emitted as multiple headers.
+ /// </summary>
+ IHeaderDictionary Headers { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="Stream"/> for writing the response body.
- /// </summary>
- [Obsolete("Use IHttpResponseBodyFeature.Stream instead.", error: false)]
- Stream Body { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="Stream"/> for writing the response body.
+ /// </summary>
+ [Obsolete("Use IHttpResponseBodyFeature.Stream instead.", error: false)]
+ Stream Body { get; set; }
- /// <summary>
- /// Gets a value that indicates if the response has started.
- /// <para>
- /// If <see langword="true"/>, the <see cref="StatusCode"/>,
- /// <see cref="ReasonPhrase"/>, and <see cref="Headers"/> are now immutable, and
- /// <see cref="OnStarting(Func{object, Task}, object)"/> should no longer be called.
- /// </para>
- /// </summary>
- bool HasStarted { get; }
+ /// <summary>
+ /// Gets a value that indicates if the response has started.
+ /// <para>
+ /// If <see langword="true"/>, the <see cref="StatusCode"/>,
+ /// <see cref="ReasonPhrase"/>, and <see cref="Headers"/> are now immutable, and
+ /// <see cref="OnStarting(Func{object, Task}, object)"/> should no longer be called.
+ /// </para>
+ /// </summary>
+ bool HasStarted { get; }
- /// <summary>
- /// Registers a callback to be invoked just before the response starts.
- /// <para>
- /// This is the last chance to modify the <see cref="Headers"/>, <see cref="StatusCode"/>, or
- /// <see cref="ReasonPhrase"/>.
- /// </para>
- /// </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 just before the response starts.
+ /// <para>
+ /// This is the last chance to modify the <see cref="Headers"/>, <see cref="StatusCode"/>, or
+ /// <see cref="ReasonPhrase"/>.
+ /// </para>
+ /// </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);
- }
+ /// <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/IHttpResponseTrailersFeature.cs b/src/Http/Http.Features/src/IHttpResponseTrailersFeature.cs
index c1d97bcbb0..9526516b7b 100644
--- a/src/Http/Http.Features/src/IHttpResponseTrailersFeature.cs
+++ b/src/Http/Http.Features/src/IHttpResponseTrailersFeature.cs
@@ -1,20 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to response trailers.
+/// <para>
+/// Response trailers allow for additional headers to be sent at the end of an HTTP/1.1 (chunked) or HTTP/2 response.
+/// For more details, see <see href="https://tools.ietf.org/html/rfc7230#section-4.1.2">RFC7230</see>.
+/// </para>
+/// </summary>
+public interface IHttpResponseTrailersFeature
{
/// <summary>
- /// Provides access to response trailers.
- /// <para>
- /// Response trailers allow for additional headers to be sent at the end of an HTTP/1.1 (chunked) or HTTP/2 response.
- /// For more details, see <see href="https://tools.ietf.org/html/rfc7230#section-4.1.2">RFC7230</see>.
- /// </para>
+ /// Gets or sets the trailer headers.
/// </summary>
- public interface IHttpResponseTrailersFeature
- {
- /// <summary>
- /// Gets or sets the trailer headers.
- /// </summary>
- IHeaderDictionary Trailers { get; set; }
- }
+ IHeaderDictionary Trailers { get; set; }
}
diff --git a/src/Http/Http.Features/src/IHttpUpgradeFeature.cs b/src/Http/Http.Features/src/IHttpUpgradeFeature.cs
index b1476d6bd3..a3c77ccef1 100644
--- a/src/Http/Http.Features/src/IHttpUpgradeFeature.cs
+++ b/src/Http/Http.Features/src/IHttpUpgradeFeature.cs
@@ -4,24 +4,23 @@
using System.IO;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to server upgrade features.
+/// </summary>
+public interface IHttpUpgradeFeature
{
/// <summary>
- /// Provides access to server upgrade features.
+ /// Indicates if the server can upgrade this request to an opaque, bidirectional stream.
/// </summary>
- public interface IHttpUpgradeFeature
- {
- /// <summary>
- /// Indicates if the server can upgrade this request to an opaque, bidirectional stream.
- /// </summary>
- bool IsUpgradableRequest { get; }
+ 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();
- }
+ /// <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();
}
diff --git a/src/Http/Http.Features/src/IHttpWebSocketFeature.cs b/src/Http/Http.Features/src/IHttpWebSocketFeature.cs
index 68a394449d..dc1404ae95 100644
--- a/src/Http/Http.Features/src/IHttpWebSocketFeature.cs
+++ b/src/Http/Http.Features/src/IHttpWebSocketFeature.cs
@@ -4,24 +4,23 @@
using System.Net.WebSockets;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to server websocket features.
+/// </summary>
+public interface IHttpWebSocketFeature
{
/// <summary>
- /// Provides access to server websocket features.
+ /// Indicates if this is a WebSocket upgrade request.
/// </summary>
- public interface IHttpWebSocketFeature
- {
- /// <summary>
- /// Indicates if this is a WebSocket upgrade request.
- /// </summary>
- bool IsWebSocketRequest { get; }
+ 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">The <see cref="WebSocketAcceptContext"/>.</param>
- /// <returns>A <see cref="WebSocket"/>.</returns>
- Task<WebSocket> AcceptAsync(WebSocketAcceptContext context);
- }
+ /// <summary>
+ /// Attempts to upgrade the request to a <see cref="WebSocket"/>. Check <see cref="IsWebSocketRequest"/>
+ /// before invoking this.
+ /// </summary>
+ /// <param name="context">The <see cref="WebSocketAcceptContext"/>.</param>
+ /// <returns>A <see cref="WebSocket"/>.</returns>
+ Task<WebSocket> AcceptAsync(WebSocketAcceptContext context);
}
diff --git a/src/Http/Http.Features/src/IHttpsCompressionFeature.cs b/src/Http/Http.Features/src/IHttpsCompressionFeature.cs
index 73dd2f5942..38e010cd65 100644
--- a/src/Http/Http.Features/src/IHttpsCompressionFeature.cs
+++ b/src/Http/Http.Features/src/IHttpsCompressionFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Configures response compression behavior for HTTPS on a per-request basis.
+/// </summary>
+public interface IHttpsCompressionFeature
{
/// <summary>
- /// Configures response compression behavior for HTTPS on a per-request basis.
+ /// The <see cref="HttpsCompressionMode"/> to use.
/// </summary>
- public interface IHttpsCompressionFeature
- {
- /// <summary>
- /// The <see cref="HttpsCompressionMode"/> to use.
- /// </summary>
- HttpsCompressionMode Mode { get; set; }
- }
+ HttpsCompressionMode Mode { get; set; }
}
diff --git a/src/Http/Http.Features/src/IItemsFeature.cs b/src/Http/Http.Features/src/IItemsFeature.cs
index ef760c6503..b4807660bd 100644
--- a/src/Http/Http.Features/src/IItemsFeature.cs
+++ b/src/Http/Http.Features/src/IItemsFeature.cs
@@ -3,16 +3,15 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides a key/value collection that can be used to share data within the scope of this request.
+/// </summary>
+public interface IItemsFeature
{
/// <summary>
- /// Provides a key/value collection that can be used to share data within the scope of this request.
+ /// Gets or sets a a key/value collection that can be used to share data within the scope of this request.
/// </summary>
- public interface IItemsFeature
- {
- /// <summary>
- /// Gets or sets a a key/value collection that can be used to share data within the scope of this request.
- /// </summary>
- IDictionary<object, object?> Items { get; set; }
- }
+ IDictionary<object, object?> Items { get; set; }
}
diff --git a/src/Http/Http.Features/src/IQueryCollection.cs b/src/Http/Http.Features/src/IQueryCollection.cs
index 47dd570570..3ae7786327 100644
--- a/src/Http/Http.Features/src/IQueryCollection.cs
+++ b/src/Http/Http.Features/src/IQueryCollection.cs
@@ -4,85 +4,84 @@
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the HttpRequest query string collection
+/// </summary>
+public interface IQueryCollection : IEnumerable<KeyValuePair<string, StringValues>>
{
/// <summary>
- /// Represents the HttpRequest query string collection
+ /// Gets the number of elements contained in the <see cref="IQueryCollection" />.
/// </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; }
+ /// <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>
+ /// 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>
+ /// 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 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; }
- }
+ /// <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
index 9b9eb96eac..c2186fc5a5 100644
--- a/src/Http/Http.Features/src/IQueryFeature.cs
+++ b/src/Http/Http.Features/src/IQueryFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to the <see cref="IQueryCollection"/> associated with the HTTP request.
+/// </summary>
+public interface IQueryFeature
{
/// <summary>
- /// Provides access to the <see cref="IQueryCollection"/> associated with the HTTP request.
+ /// Gets or sets the <see cref="IQueryCollection"/>.
/// </summary>
- public interface IQueryFeature
- {
- /// <summary>
- /// Gets or sets the <see cref="IQueryCollection"/>.
- /// </summary>
- IQueryCollection Query { get; set; }
- }
+ IQueryCollection Query { get; set; }
}
diff --git a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs
index 506f15e8b5..471fed84d0 100644
--- a/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs
+++ b/src/Http/Http.Features/src/IRequestBodyPipeFeature.cs
@@ -3,16 +3,15 @@
using System.IO.Pipelines;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Represents the HTTP request body as a <see cref="PipeReader"/>.
+/// </summary>
+public interface IRequestBodyPipeFeature
{
/// <summary>
- /// Represents the HTTP request body as a <see cref="PipeReader"/>.
+ /// Gets a <see cref="PipeReader"/> representing the request body, if any.
/// </summary>
- public interface IRequestBodyPipeFeature
- {
- /// <summary>
- /// Gets a <see cref="PipeReader"/> representing the request body, if any.
- /// </summary>
- PipeReader Reader { get; }
- }
+ PipeReader Reader { get; }
}
diff --git a/src/Http/Http.Features/src/IRequestCookieCollection.cs b/src/Http/Http.Features/src/IRequestCookieCollection.cs
index 9e77610a33..c9590acb76 100644
--- a/src/Http/Http.Features/src/IRequestCookieCollection.cs
+++ b/src/Http/Http.Features/src/IRequestCookieCollection.cs
@@ -4,85 +4,84 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents the HttpRequest cookie collection
+/// </summary>
+public interface IRequestCookieCollection : IEnumerable<KeyValuePair<string, string>>
{
/// <summary>
- /// Represents the HttpRequest cookie collection
+ /// Gets the number of elements contained in the <see cref="IRequestCookieCollection" />.
/// </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; }
+ /// <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>
+ /// 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>
+ /// 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, [MaybeNullWhen(false)] out string? value);
+ /// <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, [MaybeNullWhen(false)] 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>null</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>null</c> for missing entries
- /// rather than throwing an Exception.
- /// </remarks>
- string? this[string key] { get; }
- }
+ /// <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>null</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>null</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
index b4afd39db2..86143bad0b 100644
--- a/src/Http/Http.Features/src/IRequestCookiesFeature.cs
+++ b/src/Http/Http.Features/src/IRequestCookiesFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to request cookie collection.
+/// </summary>
+public interface IRequestCookiesFeature
{
/// <summary>
- /// Provides access to request cookie collection.
+ /// Gets or sets the request cookies.
/// </summary>
- public interface IRequestCookiesFeature
- {
- /// <summary>
- /// Gets or sets the request cookies.
- /// </summary>
- IRequestCookieCollection Cookies { get; set; }
- }
+ IRequestCookieCollection Cookies { get; set; }
}
diff --git a/src/Http/Http.Features/src/IResponseCookies.cs b/src/Http/Http.Features/src/IResponseCookies.cs
index 1f2a65f2fc..c9bf5c3c40 100644
--- a/src/Http/Http.Features/src/IResponseCookies.cs
+++ b/src/Http/Http.Features/src/IResponseCookies.cs
@@ -4,55 +4,54 @@
using System;
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A wrapper for the response Set-Cookie header.
+/// </summary>
+public interface IResponseCookies
{
/// <summary>
- /// A wrapper for the response Set-Cookie header.
+ /// Add a new cookie and value.
/// </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);
+ /// <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>
+ /// 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>
- /// Add elements of specified collection as cookies.
- /// </summary>
- /// <param name="keyValuePairs">Key value pair collections whose elements will be added as cookies.</param>
- /// <param name="options"><see cref="CookieOptions"/> included in new cookie settings.</param>
- void Append(ReadOnlySpan<KeyValuePair<string, string>> keyValuePairs, CookieOptions options)
+ /// <summary>
+ /// Add elements of specified collection as cookies.
+ /// </summary>
+ /// <param name="keyValuePairs">Key value pair collections whose elements will be added as cookies.</param>
+ /// <param name="options"><see cref="CookieOptions"/> included in new cookie settings.</param>
+ void Append(ReadOnlySpan<KeyValuePair<string, string>> keyValuePairs, CookieOptions options)
+ {
+ foreach (var keyValuePair in keyValuePairs)
{
- foreach (var keyValuePair in keyValuePairs)
- {
- Append(keyValuePair.Key, keyValuePair.Value, options);
- }
+ Append(keyValuePair.Key, keyValuePair.Value, 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>
+ 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);
- }
+ /// <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
index ff8de1eac9..a7eede55a4 100644
--- a/src/Http/Http.Features/src/IResponseCookiesFeature.cs
+++ b/src/Http/Http.Features/src/IResponseCookiesFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// A helper for creating the response Set-Cookie header.
+/// </summary>
+public interface IResponseCookiesFeature
{
/// <summary>
- /// A helper for creating the response Set-Cookie header.
+ /// Gets the wrapper for 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
+ IResponseCookies Cookies { get; }
+}
diff --git a/src/Http/Http.Features/src/IServerVariablesFeature.cs b/src/Http/Http.Features/src/IServerVariablesFeature.cs
index b25ac6ec1b..7b8b17e219 100644
--- a/src/Http/Http.Features/src/IServerVariablesFeature.cs
+++ b/src/Http/Http.Features/src/IServerVariablesFeature.cs
@@ -1,18 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// This feature provides access to request server variables set.
+/// </summary>
+public interface IServerVariablesFeature
{
/// <summary>
- /// This feature provides access to request server variables set.
+ /// Gets or sets the value of a server variable for the current request.
/// </summary>
- public interface IServerVariablesFeature
- {
- /// <summary>
- /// Gets or sets the value of a server variable for the current request.
- /// </summary>
- /// <param name="variableName">The variable name</param>
- /// <returns>May return null or empty if the variable does not exist or is not set.</returns>
- string? this[string variableName] { get; set; }
- }
+ /// <param name="variableName">The variable name</param>
+ /// <returns>May return null or empty if the variable does not exist or is not set.</returns>
+ string? this[string variableName] { get; set; }
}
diff --git a/src/Http/Http.Features/src/IServiceProvidersFeature.cs b/src/Http/Http.Features/src/IServiceProvidersFeature.cs
index b53c2f9dc7..3ef71c8322 100644
--- a/src/Http/Http.Features/src/IServiceProvidersFeature.cs
+++ b/src/Http/Http.Features/src/IServiceProvidersFeature.cs
@@ -3,16 +3,15 @@
using System;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides acccess to the request-scoped <see cref="IServiceProvider"/>.
+/// </summary>
+public interface IServiceProvidersFeature
{
/// <summary>
- /// Provides acccess to the request-scoped <see cref="IServiceProvider"/>.
+ /// Gets or sets the <see cref="IServiceProvider"/> scoped to the current request.
/// </summary>
- public interface IServiceProvidersFeature
- {
- /// <summary>
- /// Gets or sets the <see cref="IServiceProvider"/> scoped to the current request.
- /// </summary>
- IServiceProvider RequestServices { get; set; }
- }
+ IServiceProvider RequestServices { get; set; }
}
diff --git a/src/Http/Http.Features/src/ISession.cs b/src/Http/Http.Features/src/ISession.cs
index c5e9a0a879..ecb30922ee 100644
--- a/src/Http/Http.Features/src/ISession.cs
+++ b/src/Http/Http.Features/src/ISession.cs
@@ -6,68 +6,67 @@ using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Stores user data while the user browses a web application. Session state uses a store maintained by the application
+/// to persist data across requests from a client. The session data is backed by a cache and considered ephemeral data.
+/// </summary>
+public interface ISession
{
/// <summary>
- /// Stores user data while the user browses a web application. Session state uses a store maintained by the application
- /// to persist data across requests from a client. The session data is backed by a cache and considered ephemeral data.
+ /// Indicates whether the current session loaded successfully. Accessing this property before the session is loaded will cause it to be loaded inline.
/// </summary>
- public interface ISession
- {
- /// <summary>
- /// Indicates whether the current session loaded successfully. Accessing this property before the session is loaded will cause it to be loaded inline.
- /// </summary>
- bool IsAvailable { get; }
+ 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>
+ /// 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>
+ /// 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>
+ /// 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>
+ /// 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>The retrieved value.</returns>
- bool TryGetValue(string key, [NotNullWhen(true)] out byte[]? value);
+ /// <summary>
+ /// Retrieve the value of the given key, if present.
+ /// </summary>
+ /// <param name="key"></param>
+ /// <param name="value"></param>
+ /// <returns>The retrieved value.</returns>
+ bool TryGetValue(string key, [NotNullWhen(true)] 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>
+ /// 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 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();
- }
+ /// <summary>
+ /// Remove all entries from the current session, if any.
+ /// The session cookie is not removed.
+ /// </summary>
+ void Clear();
}
diff --git a/src/Http/Http.Features/src/ISessionFeature.cs b/src/Http/Http.Features/src/ISessionFeature.cs
index 7867124b38..03ca5aba34 100644
--- a/src/Http/Http.Features/src/ISessionFeature.cs
+++ b/src/Http/Http.Features/src/ISessionFeature.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to the <see cref="ISession"/> for the current request.
+/// </summary>
+public interface ISessionFeature
{
/// <summary>
- /// Provides access to the <see cref="ISession"/> for the current request.
+ /// The <see cref="ISession"/> for the current request.
/// </summary>
- public interface ISessionFeature
- {
- /// <summary>
- /// The <see cref="ISession"/> for the current request.
- /// </summary>
- ISession Session { get; set; }
- }
-} \ No newline at end of file
+ ISession Session { get; set; }
+}
diff --git a/src/Http/Http.Features/src/ITlsConnectionFeature.cs b/src/Http/Http.Features/src/ITlsConnectionFeature.cs
index 05502345d5..a36ccb76b3 100644
--- a/src/Http/Http.Features/src/ITlsConnectionFeature.cs
+++ b/src/Http/Http.Features/src/ITlsConnectionFeature.cs
@@ -5,21 +5,20 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Provides access to TLS features associated with the current HTTP connection.
+/// </summary>
+public interface ITlsConnectionFeature
{
/// <summary>
- /// Provides access to TLS features associated with the current HTTP connection.
+ /// Synchronously retrieves the client certificate, if any.
/// </summary>
- public interface ITlsConnectionFeature
- {
- /// <summary>
- /// Synchronously retrieves the client certificate, if any.
- /// </summary>
- X509Certificate2? ClientCertificate { get; set; }
+ X509Certificate2? ClientCertificate { get; set; }
- /// <summary>
- /// Asynchronously retrieves the client certificate, if any.
- /// </summary>
- Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken);
- }
+ /// <summary>
+ /// Asynchronously retrieves the client certificate, if any.
+ /// </summary>
+ Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken);
}
diff --git a/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs b/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs
index 4918e5029a..1640e2f720 100644
--- a/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs
+++ b/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs
@@ -1,35 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+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>
- /// Provides information regarding TLS token binding parameters.
+ /// Gets the 'provided' token binding identifier associated with the request.
/// </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();
+ /// <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();
- }
+ /// <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
index 60cf627949..598b4832d9 100644
--- a/src/Http/Http.Features/src/ITrackingConsentFeature.cs
+++ b/src/Http/Http.Features/src/ITrackingConsentFeature.cs
@@ -1,44 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+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>
- /// Used to query, grant, and withdraw user consent regarding the storage of user
- /// information related to site activity and functionality.
+ /// Indicates if consent is required for the given request.
/// </summary>
- public interface ITrackingConsentFeature
- {
- /// <summary>
- /// Indicates if consent is required for the given request.
- /// </summary>
- bool IsConsentNeeded { get; }
+ bool IsConsentNeeded { get; }
- /// <summary>
- /// Indicates if consent was given.
- /// </summary>
- bool HasConsent { 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>
+ /// 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>
+ /// 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>
+ /// 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();
- }
+ /// <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/SameSiteMode.cs b/src/Http/Http.Features/src/SameSiteMode.cs
index 9c8b03d165..7d1efb0402 100644
--- a/src/Http/Http.Features/src/SameSiteMode.cs
+++ b/src/Http/Http.Features/src/SameSiteMode.cs
@@ -1,22 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Used to set the SameSite field on response cookies to indicate if those cookies should be included by the client on future "same-site" or "cross-site" requests.
+/// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
+/// </summary>
+// This mirrors Microsoft.Net.Http.Headers.SameSiteMode
+public enum SameSiteMode
{
- /// <summary>
- /// Used to set the SameSite field on response cookies to indicate if those cookies should be included by the client on future "same-site" or "cross-site" requests.
- /// RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.1
- /// </summary>
- // This mirrors Microsoft.Net.Http.Headers.SameSiteMode
- public enum SameSiteMode
- {
- /// <summary>No SameSite field will be set, the client should follow its default cookie policy.</summary>
- Unspecified = -1,
- /// <summary>Indicates the client should disable same-site restrictions.</summary>
- None = 0,
- /// <summary>Indicates the client should send the cookie with "same-site" requests, and with "cross-site" top-level navigations.</summary>
- Lax,
- /// <summary>Indicates the client should only send the cookie with "same-site" requests.</summary>
- Strict
- }
+ /// <summary>No SameSite field will be set, the client should follow its default cookie policy.</summary>
+ Unspecified = -1,
+ /// <summary>Indicates the client should disable same-site restrictions.</summary>
+ None = 0,
+ /// <summary>Indicates the client should send the cookie with "same-site" requests, and with "cross-site" top-level navigations.</summary>
+ Lax,
+ /// <summary>Indicates the client should only send the cookie with "same-site" requests.</summary>
+ Strict
}
diff --git a/src/Http/Http.Features/src/WebSocketAcceptContext.cs b/src/Http/Http.Features/src/WebSocketAcceptContext.cs
index 3942221f96..f9afacb0cb 100644
--- a/src/Http/Http.Features/src/WebSocketAcceptContext.cs
+++ b/src/Http/Http.Features/src/WebSocketAcceptContext.cs
@@ -4,70 +4,69 @@
using System;
using System.Net.WebSockets;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A context for negotiating a websocket upgrade.
+/// </summary>
+public class WebSocketAcceptContext
{
+ private int _serverMaxWindowBits = 15;
+
/// <summary>
- /// A context for negotiating a websocket upgrade.
+ /// Gets or sets the subprotocol being negotiated.
/// </summary>
- public class WebSocketAcceptContext
- {
- private int _serverMaxWindowBits = 15;
-
- /// <summary>
- /// Gets or sets the subprotocol being negotiated.
- /// </summary>
- public virtual string? SubProtocol { get; set; }
+ public virtual string? SubProtocol { get; set; }
- /// <summary>
- /// The interval to send pong frames. This is a heart-beat that keeps the connection alive.
- /// </summary>
- public virtual TimeSpan? KeepAliveInterval { get; set; }
+ /// <summary>
+ /// The interval to send pong frames. This is a heart-beat that keeps the connection alive.
+ /// </summary>
+ public virtual TimeSpan? KeepAliveInterval { get; set; }
- /// <summary>
- /// Enables support for the 'permessage-deflate' WebSocket extension.<para />
- /// Be aware that enabling compression over encrypted connections makes the application subject to CRIME/BREACH type attacks.
- /// It is strongly advised to turn off compression when sending data containing secrets by
- /// specifying <see cref="WebSocketMessageFlags.DisableCompression"/> when sending such messages.
- /// </summary>
- public bool DangerousEnableCompression { get; set; }
+ /// <summary>
+ /// Enables support for the 'permessage-deflate' WebSocket extension.<para />
+ /// Be aware that enabling compression over encrypted connections makes the application subject to CRIME/BREACH type attacks.
+ /// It is strongly advised to turn off compression when sending data containing secrets by
+ /// specifying <see cref="WebSocketMessageFlags.DisableCompression"/> when sending such messages.
+ /// </summary>
+ public bool DangerousEnableCompression { get; set; }
- /// <summary>
- /// Disables server context takeover when using compression.
- /// This setting reduces the memory overhead of compression at the cost of a potentially worse compression ratio.
- /// </summary>
- /// <remarks>
- /// This property does nothing when <see cref="DangerousEnableCompression"/> is false,
- /// or when the client does not use compression.
- /// </remarks>
- /// <value>
- /// false
- /// </value>
- public bool DisableServerContextTakeover { get; set; }
+ /// <summary>
+ /// Disables server context takeover when using compression.
+ /// This setting reduces the memory overhead of compression at the cost of a potentially worse compression ratio.
+ /// </summary>
+ /// <remarks>
+ /// This property does nothing when <see cref="DangerousEnableCompression"/> is false,
+ /// or when the client does not use compression.
+ /// </remarks>
+ /// <value>
+ /// false
+ /// </value>
+ public bool DisableServerContextTakeover { get; set; }
- /// <summary>
- /// Sets the maximum base-2 logarithm of the LZ77 sliding window size that can be used for compression.
- /// This setting reduces the memory overhead of compression at the cost of a potentially worse compression ratio.
- /// </summary>
- /// <remarks>
- /// This property does nothing when <see cref="DangerousEnableCompression"/> is false,
- /// or when the client does not use compression.
- /// Valid values are 9 through 15.
- /// </remarks>
- /// <value>
- /// 15
- /// </value>
- public int ServerMaxWindowBits
+ /// <summary>
+ /// Sets the maximum base-2 logarithm of the LZ77 sliding window size that can be used for compression.
+ /// This setting reduces the memory overhead of compression at the cost of a potentially worse compression ratio.
+ /// </summary>
+ /// <remarks>
+ /// This property does nothing when <see cref="DangerousEnableCompression"/> is false,
+ /// or when the client does not use compression.
+ /// Valid values are 9 through 15.
+ /// </remarks>
+ /// <value>
+ /// 15
+ /// </value>
+ public int ServerMaxWindowBits
+ {
+ get => _serverMaxWindowBits;
+ set
{
- get => _serverMaxWindowBits;
- set
+ if (value < 9 || value > 15)
{
- if (value < 9 || value > 15)
- {
- throw new ArgumentOutOfRangeException(nameof(ServerMaxWindowBits),
- "The argument must be a value from 9 to 15.");
- }
- _serverMaxWindowBits = value;
+ throw new ArgumentOutOfRangeException(nameof(ServerMaxWindowBits),
+ "The argument must be a value from 9 to 15.");
}
+ _serverMaxWindowBits = value;
}
}
}
diff --git a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
index e35d06a4fb..1958f7d402 100644
--- a/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
+++ b/src/Http/Http.Results/src/AcceptedAtRouteResult.cs
@@ -4,64 +4,63 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class AcceptedAtRouteResult : ObjectResult
{
- internal sealed class AcceptedAtRouteResult : ObjectResult
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeValues">The route data to use for generating the URL.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public AcceptedAtRouteResult(object? routeValues, object? value)
+ : this(routeName: null, routeValues: routeValues, value: value)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="routeValues">The route data to use for generating the URL.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public AcceptedAtRouteResult(object? routeValues, object? value)
- : this(routeName: null, routeValues: routeValues, value: value)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route to use for generating the URL.</param>
- /// <param name="routeValues">The route data to use for generating the URL.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public AcceptedAtRouteResult(
- string? routeName,
- object? routeValues,
- object? value)
- : base(value, StatusCodes.Status202Accepted)
- {
- RouteName = routeName;
- RouteValues = new RouteValueDictionary(routeValues);
- }
+ }
- /// <summary>
- /// Gets the name of the route to use for generating the URL.
- /// </summary>
- public string? RouteName { get; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptedAtRouteResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route to use for generating the URL.</param>
+ /// <param name="routeValues">The route data to use for generating the URL.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public AcceptedAtRouteResult(
+ string? routeName,
+ object? routeValues,
+ object? value)
+ : base(value, StatusCodes.Status202Accepted)
+ {
+ RouteName = routeName;
+ RouteValues = new RouteValueDictionary(routeValues);
+ }
- /// <summary>
- /// Gets the route data to use for generating the URL.
- /// </summary>
- public RouteValueDictionary RouteValues { get; }
+ /// <summary>
+ /// Gets the name of the route to use for generating the URL.
+ /// </summary>
+ public string? RouteName { get; }
- /// <inheritdoc />
- protected override void ConfigureResponseHeaders(HttpContext context)
- {
- var linkGenerator = context.RequestServices.GetRequiredService<LinkGenerator>();
- var url = linkGenerator.GetUriByAddress(
- context,
- RouteName,
- RouteValues,
- fragment: FragmentString.Empty);
+ /// <summary>
+ /// Gets the route data to use for generating the URL.
+ /// </summary>
+ public RouteValueDictionary RouteValues { get; }
- if (string.IsNullOrEmpty(url))
- {
- throw new InvalidOperationException("No route matches the supplied values.");
- }
+ /// <inheritdoc />
+ protected override void ConfigureResponseHeaders(HttpContext context)
+ {
+ var linkGenerator = context.RequestServices.GetRequiredService<LinkGenerator>();
+ var url = linkGenerator.GetUriByAddress(
+ context,
+ RouteName,
+ RouteValues,
+ fragment: FragmentString.Empty);
- context.Response.Headers.Location = url;
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new InvalidOperationException("No route matches the supplied values.");
}
+
+ context.Response.Headers.Location = url;
}
}
diff --git a/src/Http/Http.Results/src/AcceptedResult.cs b/src/Http/Http.Results/src/AcceptedResult.cs
index 9ef66d936c..4926e216f3 100644
--- a/src/Http/Http.Results/src/AcceptedResult.cs
+++ b/src/Http/Http.Results/src/AcceptedResult.cs
@@ -3,67 +3,66 @@
using System;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class AcceptedResult : ObjectResult
{
- internal sealed class AcceptedResult : ObjectResult
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
+ /// provided.
+ /// </summary>
+ public AcceptedResult()
+ : base(value: null, StatusCodes.Status202Accepted)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="location">The location at which the status of requested content can be monitored.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public AcceptedResult(string? location, object? value)
+ : base(value, StatusCodes.Status202Accepted)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
- /// provided.
- /// </summary>
- public AcceptedResult()
- : base(value: null, StatusCodes.Status202Accepted)
+ Location = location;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="locationUri">The location at which the status of requested content can be monitored.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public AcceptedResult(Uri locationUri, object? value)
+ : base(value, StatusCodes.Status202Accepted)
+ {
+ if (locationUri == null)
{
+ throw new ArgumentNullException(nameof(locationUri));
}
- /// <summary>
- /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="location">The location at which the status of requested content can be monitored.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public AcceptedResult(string? location, object? value)
- : base(value, StatusCodes.Status202Accepted)
+ if (locationUri.IsAbsoluteUri)
{
- Location = location;
+ Location = locationUri.AbsoluteUri;
}
-
- /// <summary>
- /// Initializes a new instance of the <see cref="AcceptedResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="locationUri">The location at which the status of requested content can be monitored.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public AcceptedResult(Uri locationUri, object? value)
- : base(value, StatusCodes.Status202Accepted)
+ else
{
- if (locationUri == null)
- {
- throw new ArgumentNullException(nameof(locationUri));
- }
-
- if (locationUri.IsAbsoluteUri)
- {
- Location = locationUri.AbsoluteUri;
- }
- else
- {
- Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
- }
+ Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
}
+ }
- /// <summary>
- /// Gets or sets the location at which the status of the requested content can be monitored.
- /// </summary>
- public string? Location { get; set; }
+ /// <summary>
+ /// Gets or sets the location at which the status of the requested content can be monitored.
+ /// </summary>
+ public string? Location { get; set; }
- /// <inheritdoc />
- protected override void ConfigureResponseHeaders(HttpContext context)
+ /// <inheritdoc />
+ protected override void ConfigureResponseHeaders(HttpContext context)
+ {
+ if (!string.IsNullOrEmpty(Location))
{
- if (!string.IsNullOrEmpty(Location))
- {
- context.Response.Headers.Location = Location;
- }
+ context.Response.Headers.Location = Location;
}
}
}
diff --git a/src/Http/Http.Results/src/BadRequestObjectResult.cs b/src/Http/Http.Results/src/BadRequestObjectResult.cs
index bdad2f4f0f..7f58b7c9d6 100644
--- a/src/Http/Http.Results/src/BadRequestObjectResult.cs
+++ b/src/Http/Http.Results/src/BadRequestObjectResult.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class BadRequestObjectResult : ObjectResult
{
- internal sealed class BadRequestObjectResult : ObjectResult
+ public BadRequestObjectResult(object? error)
+ : base(error, StatusCodes.Status400BadRequest)
{
- public BadRequestObjectResult(object? error)
- : base(error, StatusCodes.Status400BadRequest)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/ChallengeResult.cs b/src/Http/Http.Results/src/ChallengeResult.cs
index b9c15cfedd..c9948b943a 100644
--- a/src/Http/Http.Results/src/ChallengeResult.cs
+++ b/src/Http/Http.Results/src/ChallengeResult.cs
@@ -9,112 +9,111 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
+/// </summary>
+internal sealed partial class ChallengeResult : IResult
{
/// <summary>
- /// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.ChallengeAsync"/>.
+ /// Initializes a new instance of <see cref="ChallengeResult"/>.
/// </summary>
- internal sealed partial class ChallengeResult : IResult
+ public ChallengeResult()
+ : this(Array.Empty<string>())
{
- /// <summary>
- /// Initializes a new instance of <see cref="ChallengeResult"/>.
- /// </summary>
- public ChallengeResult()
- : this(Array.Empty<string>())
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ChallengeResult"/> with the
- /// specified authentication scheme.
- /// </summary>
- /// <param name="authenticationScheme">The authentication scheme to challenge.</param>
- public ChallengeResult(string authenticationScheme)
- : this(new[] { authenticationScheme })
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+ /// specified authentication scheme.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication scheme to challenge.</param>
+ public ChallengeResult(string authenticationScheme)
+ : this(new[] { authenticationScheme })
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ChallengeResult"/> with the
- /// specified authentication schemes.
- /// </summary>
- /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
- public ChallengeResult(IList<string> authenticationSchemes)
- : this(authenticationSchemes, properties: null)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+ /// specified authentication schemes.
+ /// </summary>
+ /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+ public ChallengeResult(IList<string> authenticationSchemes)
+ : this(authenticationSchemes, properties: null)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ChallengeResult"/> with the
- /// specified <paramref name="properties"/>.
- /// </summary>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- public ChallengeResult(AuthenticationProperties? properties)
- : this(Array.Empty<string>(), properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+ /// specified <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ public ChallengeResult(AuthenticationProperties? properties)
+ : this(Array.Empty<string>(), properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ChallengeResult"/> with the
- /// specified authentication scheme and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationScheme">The authentication schemes to challenge.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- public ChallengeResult(string authenticationScheme, AuthenticationProperties? properties)
- : this(new[] { authenticationScheme }, properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+ /// specified authentication scheme and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication schemes to challenge.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ public ChallengeResult(string authenticationScheme, AuthenticationProperties? properties)
+ : this(new[] { authenticationScheme }, properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ChallengeResult"/> with the
- /// specified authentication schemes and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationSchemes">The authentication scheme to challenge.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- public ChallengeResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
- {
- AuthenticationSchemes = authenticationSchemes;
- Properties = properties;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ChallengeResult"/> with the
+ /// specified authentication schemes and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationSchemes">The authentication scheme to challenge.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ public ChallengeResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
+ {
+ AuthenticationSchemes = authenticationSchemes;
+ Properties = properties;
+ }
- public IList<string> AuthenticationSchemes { get; init; } = Array.Empty<string>();
+ public IList<string> AuthenticationSchemes { get; init; } = Array.Empty<string>();
- public AuthenticationProperties? Properties { get; init; }
+ public AuthenticationProperties? Properties { get; init; }
- public async Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<ChallengeResult>>();
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<ChallengeResult>>();
- Log.ChallengeResultExecuting(logger, AuthenticationSchemes);
+ Log.ChallengeResultExecuting(logger, AuthenticationSchemes);
- if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
- {
- foreach (var scheme in AuthenticationSchemes)
- {
- await httpContext.ChallengeAsync(scheme, Properties);
- }
- }
- else
+ if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
+ {
+ foreach (var scheme in AuthenticationSchemes)
{
- await httpContext.ChallengeAsync(Properties);
+ await httpContext.ChallengeAsync(scheme, Properties);
}
}
+ else
+ {
+ await httpContext.ChallengeAsync(Properties);
+ }
+ }
- private static partial class Log
+ private static partial class Log
+ {
+ public static void ChallengeResultExecuting(ILogger logger, IList<string> authenticationSchemes)
{
- public static void ChallengeResultExecuting(ILogger logger, IList<string> authenticationSchemes)
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (logger.IsEnabled(LogLevel.Information))
- {
- ChallengeResultExecuting(logger, authenticationSchemes.ToArray());
- }
+ ChallengeResultExecuting(logger, authenticationSchemes.ToArray());
}
-
- [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
- private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
}
+
+ [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
+ private static partial void ChallengeResultExecuting(ILogger logger, string[] schemes);
}
}
diff --git a/src/Http/Http.Results/src/ConflictObjectResult.cs b/src/Http/Http.Results/src/ConflictObjectResult.cs
index 9a90eb7bc0..68308b14d2 100644
--- a/src/Http/Http.Results/src/ConflictObjectResult.cs
+++ b/src/Http/Http.Results/src/ConflictObjectResult.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class ConflictObjectResult : ObjectResult
{
- internal sealed class ConflictObjectResult : ObjectResult
+ public ConflictObjectResult(object? error) :
+ base(error, StatusCodes.Status409Conflict)
{
- public ConflictObjectResult(object? error) :
- base(error, StatusCodes.Status409Conflict)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/ContentResult.cs b/src/Http/Http.Results/src/ContentResult.cs
index 347c823d76..ebd987dfed 100644
--- a/src/Http/Http.Results/src/ContentResult.cs
+++ b/src/Http/Http.Results/src/ContentResult.cs
@@ -7,69 +7,68 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
-{
- internal sealed partial class ContentResult : IResult
- {
- private const string DefaultContentType = "text/plain; charset=utf-8";
- private static readonly Encoding DefaultEncoding = Encoding.UTF8;
+namespace Microsoft.AspNetCore.Http.Result;
- /// <summary>
- /// Gets or set the content representing the body of the response.
- /// </summary>
- public string? Content { get; init; }
+internal sealed partial class ContentResult : IResult
+{
+ private const string DefaultContentType = "text/plain; charset=utf-8";
+ private static readonly Encoding DefaultEncoding = Encoding.UTF8;
- /// <summary>
- /// Gets or sets the Content-Type header for the response.
- /// </summary>
- public string? ContentType { get; init; }
+ /// <summary>
+ /// Gets or set the content representing the body of the response.
+ /// </summary>
+ public string? Content { get; init; }
- /// <summary>
- /// Gets or sets the HTTP status code.
- /// </summary>
- public int? StatusCode { get; init; }
+ /// <summary>
+ /// Gets or sets the Content-Type header for the response.
+ /// </summary>
+ public string? ContentType { get; init; }
- /// <summary>
- /// Writes the content to the HTTP response.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
- /// <returns>A task that represents the asynchronous execute operation.</returns>
- public async Task ExecuteAsync(HttpContext httpContext)
- {
- var response = httpContext.Response;
+ /// <summary>
+ /// Gets or sets the HTTP status code.
+ /// </summary>
+ public int? StatusCode { get; init; }
- ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
- ContentType,
- response.ContentType,
- (DefaultContentType, DefaultEncoding),
- ResponseContentTypeHelper.GetEncoding,
- out var resolvedContentType,
- out var resolvedContentTypeEncoding);
+ /// <summary>
+ /// Writes the content to the HTTP response.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+ /// <returns>A task that represents the asynchronous execute operation.</returns>
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var response = httpContext.Response;
- response.ContentType = resolvedContentType;
+ ResponseContentTypeHelper.ResolveContentTypeAndEncoding(
+ ContentType,
+ response.ContentType,
+ (DefaultContentType, DefaultEncoding),
+ ResponseContentTypeHelper.GetEncoding,
+ out var resolvedContentType,
+ out var resolvedContentTypeEncoding);
- if (StatusCode != null)
- {
- response.StatusCode = StatusCode.Value;
- }
+ response.ContentType = resolvedContentType;
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<ContentResult>>();
+ if (StatusCode != null)
+ {
+ response.StatusCode = StatusCode.Value;
+ }
- Log.ContentResultExecuting(logger, resolvedContentType);
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<ContentResult>>();
- if (Content != null)
- {
- response.ContentLength = resolvedContentTypeEncoding.GetByteCount(Content);
- await response.WriteAsync(Content, resolvedContentTypeEncoding);
- }
- }
+ Log.ContentResultExecuting(logger, resolvedContentType);
- private static partial class Log
+ if (Content != null)
{
- [LoggerMessage(1, LogLevel.Information,
- "Executing ContentResult with HTTP Response ContentType of {ContentType}",
- EventName = "ContentResultExecuting")]
- internal static partial void ContentResultExecuting(ILogger logger, string contentType);
+ response.ContentLength = resolvedContentTypeEncoding.GetByteCount(Content);
+ await response.WriteAsync(Content, resolvedContentTypeEncoding);
}
}
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing ContentResult with HTTP Response ContentType of {ContentType}",
+ EventName = "ContentResultExecuting")]
+ internal static partial void ContentResultExecuting(ILogger logger, string contentType);
+ }
}
diff --git a/src/Http/Http.Results/src/CreatedAtRouteResult.cs b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
index e561e32435..4b0bc00747 100644
--- a/src/Http/Http.Results/src/CreatedAtRouteResult.cs
+++ b/src/Http/Http.Results/src/CreatedAtRouteResult.cs
@@ -5,64 +5,63 @@ using System;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class CreatedAtRouteResult : ObjectResult
{
- internal sealed class CreatedAtRouteResult : ObjectResult
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeValues">The route data to use for generating the URL.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public CreatedAtRouteResult(object? routeValues, object? value)
+ : this(routeName: null, routeValues: routeValues, value: value)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="routeValues">The route data to use for generating the URL.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public CreatedAtRouteResult(object? routeValues, object? value)
- : this(routeName: null, routeValues: routeValues, value: value)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route to use for generating the URL.</param>
- /// <param name="routeValues">The route data to use for generating the URL.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public CreatedAtRouteResult(
- string? routeName,
- object? routeValues,
- object? value)
- : base(value, StatusCodes.Status201Created)
- {
- RouteName = routeName;
- RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
- }
+ }
- /// <summary>
- /// Gets or sets the name of the route to use for generating the URL.
- /// </summary>
- public string? RouteName { get; set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route to use for generating the URL.</param>
+ /// <param name="routeValues">The route data to use for generating the URL.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public CreatedAtRouteResult(
+ string? routeName,
+ object? routeValues,
+ object? value)
+ : base(value, StatusCodes.Status201Created)
+ {
+ RouteName = routeName;
+ RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
+ }
- /// <summary>
- /// Gets or sets the route data to use for generating the URL.
- /// </summary>
- public RouteValueDictionary? RouteValues { get; set; }
+ /// <summary>
+ /// Gets or sets the name of the route to use for generating the URL.
+ /// </summary>
+ public string? RouteName { get; set; }
- /// <inheritdoc />
- protected override void ConfigureResponseHeaders(HttpContext context)
- {
- var linkGenerator = context.RequestServices.GetRequiredService<LinkGenerator>();
- var url = linkGenerator.GetUriByRouteValues(
- context,
- RouteName,
- RouteValues,
- fragment: FragmentString.Empty);
+ /// <summary>
+ /// Gets or sets the route data to use for generating the URL.
+ /// </summary>
+ public RouteValueDictionary? RouteValues { get; set; }
- if (string.IsNullOrEmpty(url))
- {
- throw new InvalidOperationException("No route matches the supplied values.");
- }
+ /// <inheritdoc />
+ protected override void ConfigureResponseHeaders(HttpContext context)
+ {
+ var linkGenerator = context.RequestServices.GetRequiredService<LinkGenerator>();
+ var url = linkGenerator.GetUriByRouteValues(
+ context,
+ RouteName,
+ RouteValues,
+ fragment: FragmentString.Empty);
- context.Response.Headers.Location = url;
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new InvalidOperationException("No route matches the supplied values.");
}
+
+ context.Response.Headers.Location = url;
}
}
diff --git a/src/Http/Http.Results/src/CreatedResult.cs b/src/Http/Http.Results/src/CreatedResult.cs
index b5b2d04bbf..6979c2160b 100644
--- a/src/Http/Http.Results/src/CreatedResult.cs
+++ b/src/Http/Http.Results/src/CreatedResult.cs
@@ -3,55 +3,54 @@
using System;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class CreatedResult : ObjectResult
{
- internal sealed class CreatedResult : ObjectResult
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="location">The location at which the content has been created.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public CreatedResult(string location, object? value)
+ : base(value, StatusCodes.Status201Created)
+ {
+ Location = location;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="location">The location at which the content has been created.</param>
+ /// <param name="value">The value to format in the entity body.</param>
+ public CreatedResult(Uri location, object? value)
+ : base(value, StatusCodes.Status201Created)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="location">The location at which the content has been created.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public CreatedResult(string location, object? value)
- : base(value, StatusCodes.Status201Created)
+ if (location == null)
{
- Location = location;
+ throw new ArgumentNullException(nameof(location));
}
- /// <summary>
- /// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="location">The location at which the content has been created.</param>
- /// <param name="value">The value to format in the entity body.</param>
- public CreatedResult(Uri location, object? value)
- : base(value, StatusCodes.Status201Created)
+ if (location.IsAbsoluteUri)
{
- if (location == null)
- {
- throw new ArgumentNullException(nameof(location));
- }
-
- if (location.IsAbsoluteUri)
- {
- Location = location.AbsoluteUri;
- }
- else
- {
- Location = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
- }
+ Location = location.AbsoluteUri;
}
-
- /// <summary>
- /// Gets or sets the location at which the content has been created.
- /// </summary>
- public string Location { get; init; }
-
- /// <inheritdoc />
- protected override void ConfigureResponseHeaders(HttpContext context)
+ else
{
- context.Response.Headers.Location = Location;
+ Location = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
}
}
+
+ /// <summary>
+ /// Gets or sets the location at which the content has been created.
+ /// </summary>
+ public string Location { get; init; }
+
+ /// <inheritdoc />
+ protected override void ConfigureResponseHeaders(HttpContext context)
+ {
+ context.Response.Headers.Location = Location;
+ }
}
diff --git a/src/Http/Http.Results/src/FileContentResult.cs b/src/Http/Http.Results/src/FileContentResult.cs
index 04c341a18b..b00da02890 100644
--- a/src/Http/Http.Results/src/FileContentResult.cs
+++ b/src/Http/Http.Results/src/FileContentResult.cs
@@ -5,68 +5,67 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed partial class FileContentResult : FileResult, IResult
{
- internal sealed partial class FileContentResult : FileResult, IResult
+ /// <summary>
+ /// Creates a new <see cref="FileContentResult"/> instance with
+ /// the provided <paramref name="fileContents"/> and the
+ /// provided <paramref name="contentType"/>.
+ /// </summary>
+ /// <param name="fileContents">The bytes that represent the file contents.</param>
+ /// <param name="contentType">The Content-Type header of the response.</param>
+ public FileContentResult(byte[] fileContents, string? contentType)
+ : base(contentType)
{
- /// <summary>
- /// Creates a new <see cref="FileContentResult"/> instance with
- /// the provided <paramref name="fileContents"/> and the
- /// provided <paramref name="contentType"/>.
- /// </summary>
- /// <param name="fileContents">The bytes that represent the file contents.</param>
- /// <param name="contentType">The Content-Type header of the response.</param>
- public FileContentResult(byte[] fileContents, string? contentType)
- : base(contentType)
- {
- FileContents = fileContents;
- }
-
- /// <summary>
- /// Gets or sets the file contents.
- /// </summary>
- public byte[] FileContents { get; init; }
+ FileContents = fileContents;
+ }
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<FileContentResult>>();
- Log.ExecutingFileResult(logger, this);
+ /// <summary>
+ /// Gets or sets the file contents.
+ /// </summary>
+ public byte[] FileContents { get; init; }
- var fileResultInfo = new FileResultInfo
- {
- ContentType = ContentType,
- EnableRangeProcessing = EnableRangeProcessing,
- EntityTag = EntityTag,
- FileDownloadName = FileDownloadName,
- LastModified = LastModified,
- };
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<FileContentResult>>();
+ Log.ExecutingFileResult(logger, this);
- var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
- httpContext,
- fileResultInfo,
- FileContents.Length,
- EnableRangeProcessing,
- LastModified,
- EntityTag,
- logger);
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ FileDownloadName = FileDownloadName,
+ LastModified = LastModified,
+ };
- if (!serveBody)
- {
- return Task.CompletedTask;
- }
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ FileContents.Length,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag,
+ logger);
- if (range != null && rangeLength == 0)
- {
- return Task.CompletedTask;
- }
+ if (!serveBody)
+ {
+ return Task.CompletedTask;
+ }
- if (range != null)
- {
- FileResultHelper.Log.WritingRangeToBody(logger);
- }
+ if (range != null && rangeLength == 0)
+ {
+ return Task.CompletedTask;
+ }
- var fileContentStream = new MemoryStream(FileContents);
- return FileResultHelper.WriteFileAsync(httpContext, fileContentStream, range, rangeLength);
+ if (range != null)
+ {
+ FileResultHelper.Log.WritingRangeToBody(logger);
}
+
+ var fileContentStream = new MemoryStream(FileContents);
+ return FileResultHelper.WriteFileAsync(httpContext, fileContentStream, range, rangeLength);
}
}
diff --git a/src/Http/Http.Results/src/FileResult.cs b/src/Http/Http.Results/src/FileResult.cs
index d6a4afea79..d3be83f96f 100644
--- a/src/Http/Http.Results/src/FileResult.cs
+++ b/src/Http/Http.Results/src/FileResult.cs
@@ -5,83 +5,82 @@ using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal abstract partial class FileResult
{
- internal abstract partial class FileResult
- {
- private string? _fileDownloadName;
+ private string? _fileDownloadName;
- /// <summary>
- /// Creates a new <see cref="FileResult"/> instance with
- /// the provided <paramref name="contentType"/>.
- /// </summary>
- /// <param name="contentType">The Content-Type header of the response.</param>
- protected FileResult(string? contentType)
- {
- ContentType = contentType ?? "application/octet-stream";
- }
+ /// <summary>
+ /// Creates a new <see cref="FileResult"/> instance with
+ /// the provided <paramref name="contentType"/>.
+ /// </summary>
+ /// <param name="contentType">The Content-Type header of the response.</param>
+ protected FileResult(string? contentType)
+ {
+ ContentType = contentType ?? "application/octet-stream";
+ }
- /// <summary>
- /// Gets the Content-Type header for the response.
- /// </summary>
- public string ContentType { get; }
+ /// <summary>
+ /// Gets the Content-Type header for the response.
+ /// </summary>
+ public string ContentType { get; }
- /// <summary>
- /// Gets the file name that will be used in the Content-Disposition header of the response.
- /// </summary>
- [AllowNull]
- public string FileDownloadName
- {
- get { return _fileDownloadName ?? string.Empty; }
- init { _fileDownloadName = value; }
- }
+ /// <summary>
+ /// Gets the file name that will be used in the Content-Disposition header of the response.
+ /// </summary>
+ [AllowNull]
+ public string FileDownloadName
+ {
+ get { return _fileDownloadName ?? string.Empty; }
+ init { _fileDownloadName = value; }
+ }
- /// <summary>
- /// Gets or sets the last modified information associated with the <see cref="FileResult"/>.
- /// </summary>
- public DateTimeOffset? LastModified { get; init; }
+ /// <summary>
+ /// Gets or sets the last modified information associated with the <see cref="FileResult"/>.
+ /// </summary>
+ public DateTimeOffset? LastModified { get; init; }
- /// <summary>
- /// Gets or sets the etag associated with the <see cref="FileResult"/>.
- /// </summary>
- public EntityTagHeaderValue? EntityTag { get; init; }
+ /// <summary>
+ /// Gets or sets the etag associated with the <see cref="FileResult"/>.
+ /// </summary>
+ public EntityTagHeaderValue? EntityTag { get; init; }
- /// <summary>
- /// Gets or sets the value that enables range processing for the <see cref="FileResult"/>.
- /// </summary>
- public bool EnableRangeProcessing { get; init; }
+ /// <summary>
+ /// Gets or sets the value that enables range processing for the <see cref="FileResult"/>.
+ /// </summary>
+ public bool EnableRangeProcessing { get; init; }
- protected static partial class Log
+ protected static partial class Log
+ {
+ public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
{
- public static void ExecutingFileResult(ILogger logger, FileResult fileResult)
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (logger.IsEnabled(LogLevel.Information))
- {
- var fileResultType = fileResult.GetType().Name;
- ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
- }
+ var fileResultType = fileResult.GetType().Name;
+ ExecutingFileResultWithNoFileName(logger, fileResultType, fileResult.FileDownloadName);
}
+ }
- public static void ExecutingFileResult(ILogger logger, FileResult fileResult, string fileName)
+ public static void ExecutingFileResult(ILogger logger, FileResult fileResult, string fileName)
+ {
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (logger.IsEnabled(LogLevel.Information))
- {
- var fileResultType = fileResult.GetType().Name;
- ExecutingFileResult(logger, fileResultType, fileName, fileResult.FileDownloadName);
- }
+ var fileResultType = fileResult.GetType().Name;
+ ExecutingFileResult(logger, fileResultType, fileName, fileResult.FileDownloadName);
}
+ }
- [LoggerMessage(1, LogLevel.Information,
- "Executing {FileResultType}, sending file with download name '{FileDownloadName}'.",
- EventName = "ExecutingFileResultWithNoFileName",
- SkipEnabledCheck = true)]
- private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing {FileResultType}, sending file with download name '{FileDownloadName}'.",
+ EventName = "ExecutingFileResultWithNoFileName",
+ SkipEnabledCheck = true)]
+ private static partial void ExecutingFileResultWithNoFileName(ILogger logger, string fileResultType, string fileDownloadName);
- [LoggerMessage(2, LogLevel.Information,
- "Executing {FileResultType}, sending file '{FileDownloadPath}' with download name '{FileDownloadName}'.",
- EventName = "ExecutingFileResult",
- SkipEnabledCheck = true)]
- private static partial void ExecutingFileResult(ILogger logger, string fileResultType, string fileDownloadPath, string fileDownloadName);
- }
+ [LoggerMessage(2, LogLevel.Information,
+ "Executing {FileResultType}, sending file '{FileDownloadPath}' with download name '{FileDownloadName}'.",
+ EventName = "ExecutingFileResult",
+ SkipEnabledCheck = true)]
+ private static partial void ExecutingFileResult(ILogger logger, string fileResultType, string fileDownloadPath, string fileDownloadName);
}
}
diff --git a/src/Http/Http.Results/src/FileStreamResult.cs b/src/Http/Http.Results/src/FileStreamResult.cs
index aa69a1db36..ed4202cb5c 100644
--- a/src/Http/Http.Results/src/FileStreamResult.cs
+++ b/src/Http/Http.Results/src/FileStreamResult.cs
@@ -5,84 +5,83 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// Represents an <see cref="FileResult"/> that when executed will
+/// write a file from a stream to the response.
+/// </summary>
+internal sealed class FileStreamResult : FileResult, IResult
{
/// <summary>
- /// Represents an <see cref="FileResult"/> that when executed will
- /// write a file from a stream to the response.
+ /// Creates a new <see cref="FileStreamResult"/> instance with
+ /// the provided <paramref name="fileStream"/> and the
+ /// provided <paramref name="contentType"/>.
/// </summary>
- internal sealed class FileStreamResult : FileResult, IResult
+ /// <param name="fileStream">The stream with the file.</param>
+ /// <param name="contentType">The Content-Type header of the response.</param>
+ public FileStreamResult(Stream fileStream, string? contentType)
+ : base(contentType)
{
- /// <summary>
- /// Creates a new <see cref="FileStreamResult"/> instance with
- /// the provided <paramref name="fileStream"/> and the
- /// provided <paramref name="contentType"/>.
- /// </summary>
- /// <param name="fileStream">The stream with the file.</param>
- /// <param name="contentType">The Content-Type header of the response.</param>
- public FileStreamResult(Stream fileStream, string? contentType)
- : base(contentType)
+ if (fileStream == null)
{
- if (fileStream == null)
- {
- throw new ArgumentNullException(nameof(fileStream));
- }
-
- FileStream = fileStream;
+ throw new ArgumentNullException(nameof(fileStream));
}
- /// <summary>
- /// Gets or sets the stream with the file that will be sent back as the response.
- /// </summary>
- public Stream FileStream { get; }
+ FileStream = fileStream;
+ }
- public async Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<FileStreamResult>>();
- await using (FileStream)
- {
- Log.ExecutingFileResult(logger, this);
+ /// <summary>
+ /// Gets or sets the stream with the file that will be sent back as the response.
+ /// </summary>
+ public Stream FileStream { get; }
- long? fileLength = null;
- if (FileStream.CanSeek)
- {
- fileLength = FileStream.Length;
- }
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<FileStreamResult>>();
+ await using (FileStream)
+ {
+ Log.ExecutingFileResult(logger, this);
- var fileResultInfo = new FileResultInfo
- {
- ContentType = ContentType,
- EnableRangeProcessing = EnableRangeProcessing,
- EntityTag = EntityTag,
- FileDownloadName = FileDownloadName,
- };
+ long? fileLength = null;
+ if (FileStream.CanSeek)
+ {
+ fileLength = FileStream.Length;
+ }
- var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
- httpContext,
- fileResultInfo,
- fileLength,
- EnableRangeProcessing,
- LastModified,
- EntityTag,
- logger);
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ FileDownloadName = FileDownloadName,
+ };
- if (!serveBody)
- {
- return;
- }
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ fileLength,
+ EnableRangeProcessing,
+ LastModified,
+ EntityTag,
+ logger);
- if (range != null && rangeLength == 0)
- {
- return;
- }
+ if (!serveBody)
+ {
+ return;
+ }
- if (range != null)
- {
- FileResultHelper.Log.WritingRangeToBody(logger);
- }
+ if (range != null && rangeLength == 0)
+ {
+ return;
+ }
- await FileResultHelper.WriteFileAsync(httpContext, FileStream, range, rangeLength);
+ if (range != null)
+ {
+ FileResultHelper.Log.WritingRangeToBody(logger);
}
+
+ await FileResultHelper.WriteFileAsync(httpContext, FileStream, range, rangeLength);
}
}
}
diff --git a/src/Http/Http.Results/src/ForbidResult.cs b/src/Http/Http.Results/src/ForbidResult.cs
index 9c87eb65a1..d0745fe9e8 100644
--- a/src/Http/Http.Results/src/ForbidResult.cs
+++ b/src/Http/Http.Results/src/ForbidResult.cs
@@ -9,117 +9,116 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed partial class ForbidResult : IResult
{
- internal sealed partial class ForbidResult : IResult
+ /// <summary>
+ /// Initializes a new instance of <see cref="ForbidResult"/>.
+ /// </summary>
+ public ForbidResult()
+ : this(Array.Empty<string>())
{
- /// <summary>
- /// Initializes a new instance of <see cref="ForbidResult"/>.
- /// </summary>
- public ForbidResult()
- : this(Array.Empty<string>())
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ForbidResult"/> with the
- /// specified authentication scheme.
- /// </summary>
- /// <param name="authenticationScheme">The authentication scheme to challenge.</param>
- public ForbidResult(string authenticationScheme)
- : this(new[] { authenticationScheme })
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ForbidResult"/> with the
+ /// specified authentication scheme.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication scheme to challenge.</param>
+ public ForbidResult(string authenticationScheme)
+ : this(new[] { authenticationScheme })
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ForbidResult"/> with the
- /// specified authentication schemes.
- /// </summary>
- /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
- public ForbidResult(IList<string> authenticationSchemes)
- : this(authenticationSchemes, properties: null)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ForbidResult"/> with the
+ /// specified authentication schemes.
+ /// </summary>
+ /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+ public ForbidResult(IList<string> authenticationSchemes)
+ : this(authenticationSchemes, properties: null)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ForbidResult"/> with the
- /// specified <paramref name="properties"/>.
- /// </summary>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- public ForbidResult(AuthenticationProperties? properties)
- : this(Array.Empty<string>(), properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ForbidResult"/> with the
+ /// specified <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ public ForbidResult(AuthenticationProperties? properties)
+ : this(Array.Empty<string>(), properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ForbidResult"/> with the
- /// specified authentication scheme and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationScheme">The authentication schemes to challenge.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- public ForbidResult(string authenticationScheme, AuthenticationProperties? properties)
- : this(new[] { authenticationScheme }, properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ForbidResult"/> with the
+ /// specified authentication scheme and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication schemes to challenge.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ public ForbidResult(string authenticationScheme, AuthenticationProperties? properties)
+ : this(new[] { authenticationScheme }, properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ForbidResult"/> with the
- /// specified authentication schemes and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationSchemes">The authentication scheme to challenge.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- public ForbidResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
- {
- AuthenticationSchemes = authenticationSchemes;
- Properties = properties;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ForbidResult"/> with the
+ /// specified authentication schemes and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationSchemes">The authentication scheme to challenge.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ public ForbidResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
+ {
+ AuthenticationSchemes = authenticationSchemes;
+ Properties = properties;
+ }
- /// <summary>
- /// Gets or sets the authentication schemes that are challenged.
- /// </summary>
- public IList<string> AuthenticationSchemes { get; init; }
+ /// <summary>
+ /// Gets or sets the authentication schemes that are challenged.
+ /// </summary>
+ public IList<string> AuthenticationSchemes { get; init; }
- /// <summary>
- /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the authentication challenge.
- /// </summary>
- public AuthenticationProperties? Properties { get; init; }
+ /// <summary>
+ /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the authentication challenge.
+ /// </summary>
+ public AuthenticationProperties? Properties { get; init; }
- /// <inheritdoc />
- public async Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<ForbidResult>>();
+ /// <inheritdoc />
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<ForbidResult>>();
- Log.ForbidResultExecuting(logger, AuthenticationSchemes);
+ Log.ForbidResultExecuting(logger, AuthenticationSchemes);
- if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
- {
- for (var i = 0; i < AuthenticationSchemes.Count; i++)
- {
- await httpContext.ForbidAsync(AuthenticationSchemes[i], Properties);
- }
- }
- else
+ if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0)
+ {
+ for (var i = 0; i < AuthenticationSchemes.Count; i++)
{
- await httpContext.ForbidAsync(Properties);
+ await httpContext.ForbidAsync(AuthenticationSchemes[i], Properties);
}
}
+ else
+ {
+ await httpContext.ForbidAsync(Properties);
+ }
+ }
- private static partial class Log
+ private static partial class Log
+ {
+ public static void ForbidResultExecuting(ILogger logger, IList<string> authenticationSchemes)
{
- public static void ForbidResultExecuting(ILogger logger, IList<string> authenticationSchemes)
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (logger.IsEnabled(LogLevel.Information))
- {
- ForbidResultExecuting(logger, authenticationSchemes.ToArray());
- }
+ ForbidResultExecuting(logger, authenticationSchemes.ToArray());
}
-
- [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
- private static partial void ForbidResultExecuting(ILogger logger, string[] schemes);
}
+ [LoggerMessage(1, LogLevel.Information, "Executing ChallengeResult with authentication schemes ({Schemes}).", EventName = "ChallengeResultExecuting", SkipEnabledCheck = true)]
+ private static partial void ForbidResultExecuting(ILogger logger, string[] schemes);
}
+
}
diff --git a/src/Http/Http.Results/src/IResultExtensions.cs b/src/Http/Http.Results/src/IResultExtensions.cs
index 1905055d0f..eaddb35180 100644
--- a/src/Http/Http.Results/src/IResultExtensions.cs
+++ b/src/Http/Http.Results/src/IResultExtensions.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
-{
- /// <summary>
- /// Provides an interface to registering external methods that provide
- /// custom IResult instances.
- /// </summary>
- public interface IResultExtensions { }
-} \ No newline at end of file
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides an interface to registering external methods that provide
+/// custom IResult instances.
+/// </summary>
+public interface IResultExtensions { }
diff --git a/src/Http/Http.Results/src/JsonResult.cs b/src/Http/Http.Results/src/JsonResult.cs
index 59e7f9cba4..797f62f8e9 100644
--- a/src/Http/Http.Results/src/JsonResult.cs
+++ b/src/Http/Http.Results/src/JsonResult.cs
@@ -6,73 +6,72 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// An action result which formats the given object as JSON.
+/// </summary>
+internal sealed partial class JsonResult : IResult
{
/// <summary>
- /// An action result which formats the given object as JSON.
+ /// Gets or sets the <see cref="Net.Http.Headers.MediaTypeHeaderValue"/> representing the Content-Type header of the response.
/// </summary>
- internal sealed partial class JsonResult : IResult
- {
- /// <summary>
- /// Gets or sets the <see cref="Net.Http.Headers.MediaTypeHeaderValue"/> representing the Content-Type header of the response.
- /// </summary>
- public string? ContentType { get; init; }
-
- /// <summary>
- /// Gets or sets the serializer settings.
- /// <para>
- /// When using <c>System.Text.Json</c>, this should be an instance of <see cref="JsonSerializerOptions" />
- /// </para>
- /// <para>
- /// When using <c>Newtonsoft.Json</c>, this should be an instance of <c>JsonSerializerSettings</c>.
- /// </para>
- /// </summary>
- public JsonSerializerOptions? JsonSerializerOptions { get; init; }
+ public string? ContentType { get; init; }
- /// <summary>
- /// Gets or sets the HTTP status code.
- /// </summary>
- public int? StatusCode { get; init; }
+ /// <summary>
+ /// Gets or sets the serializer settings.
+ /// <para>
+ /// When using <c>System.Text.Json</c>, this should be an instance of <see cref="JsonSerializerOptions" />
+ /// </para>
+ /// <para>
+ /// When using <c>Newtonsoft.Json</c>, this should be an instance of <c>JsonSerializerSettings</c>.
+ /// </para>
+ /// </summary>
+ public JsonSerializerOptions? JsonSerializerOptions { get; init; }
- /// <summary>
- /// Gets or sets the value to be formatted.
- /// </summary>
- public object? Value { get; init; }
+ /// <summary>
+ /// Gets or sets the HTTP status code.
+ /// </summary>
+ public int? StatusCode { get; init; }
- /// <summary>
- /// Write the result as JSON to the HTTP response.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
- /// <returns>A task that represents the asynchronous execute operation.</returns>
- Task IResult.ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<JsonResult>>();
- Log.JsonResultExecuting(logger, Value);
+ /// <summary>
+ /// Gets or sets the value to be formatted.
+ /// </summary>
+ public object? Value { get; init; }
- if (StatusCode is int statusCode)
- {
- httpContext.Response.StatusCode = statusCode;
- }
+ /// <summary>
+ /// Write the result as JSON to the HTTP response.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+ /// <returns>A task that represents the asynchronous execute operation.</returns>
+ Task IResult.ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<JsonResult>>();
+ Log.JsonResultExecuting(logger, Value);
- return httpContext.Response.WriteAsJsonAsync(Value, JsonSerializerOptions, ContentType);
+ if (StatusCode is int statusCode)
+ {
+ httpContext.Response.StatusCode = statusCode;
}
- private static partial class Log
+ return httpContext.Response.WriteAsJsonAsync(Value, JsonSerializerOptions, ContentType);
+ }
+
+ private static partial class Log
+ {
+ public static void JsonResultExecuting(ILogger logger, object? value)
{
- public static void JsonResultExecuting(ILogger logger, object? value)
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (logger.IsEnabled(LogLevel.Information))
- {
- var type = value == null ? "null" : value.GetType().FullName!;
- JsonResultExecuting(logger, type);
- }
+ var type = value == null ? "null" : value.GetType().FullName!;
+ JsonResultExecuting(logger, type);
}
-
- [LoggerMessage(1, LogLevel.Information,
- "Executing JsonResult, writing value of type '{Type}'.",
- EventName = "JsonResultExecuting",
- SkipEnabledCheck = true)]
- private static partial void JsonResultExecuting(ILogger logger, string type);
}
+
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing JsonResult, writing value of type '{Type}'.",
+ EventName = "JsonResultExecuting",
+ SkipEnabledCheck = true)]
+ private static partial void JsonResultExecuting(ILogger logger, string type);
}
}
diff --git a/src/Http/Http.Results/src/LocalRedirectResult.cs b/src/Http/Http.Results/src/LocalRedirectResult.cs
index d7a2efb9ec..c7ad5b8d8b 100644
--- a/src/Http/Http.Results/src/LocalRedirectResult.cs
+++ b/src/Http/Http.Results/src/LocalRedirectResult.cs
@@ -7,105 +7,104 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
+/// or Permanent Redirect (308) response with a Location header to the supplied local URL.
+/// </summary>
+internal sealed partial class LocalRedirectResult : IResult
{
/// <summary>
- /// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
- /// or Permanent Redirect (308) response with a Location header to the supplied local URL.
+ /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
+ /// provided.
/// </summary>
- internal sealed partial class LocalRedirectResult : IResult
+ /// <param name="localUrl">The local URL to redirect to.</param>
+ public LocalRedirectResult(string localUrl)
+ : this(localUrl, permanent: false)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="localUrl">The local URL to redirect to.</param>
- public LocalRedirectResult(string localUrl)
- : this(localUrl, permanent: false)
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="localUrl">The local URL to redirect to.</param>
- /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
- public LocalRedirectResult(string localUrl, bool permanent)
- : this(localUrl, permanent, preserveMethod: false)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="localUrl">The local URL to redirect to.</param>
+ /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+ public LocalRedirectResult(string localUrl, bool permanent)
+ : this(localUrl, permanent, preserveMethod: false)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="localUrl">The local URL to redirect to.</param>
+ /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request's method.</param>
+ public LocalRedirectResult(string localUrl, bool permanent, bool preserveMethod)
+ {
+ if (string.IsNullOrEmpty(localUrl))
{
+ throw new ArgumentException("Argument cannot be null or empty", nameof(localUrl));
}
- /// <summary>
- /// Initializes a new instance of the <see cref="LocalRedirectResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="localUrl">The local URL to redirect to.</param>
- /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request's method.</param>
- public LocalRedirectResult(string localUrl, bool permanent, bool preserveMethod)
+ Permanent = permanent;
+ PreserveMethod = preserveMethod;
+ Url = localUrl;
+ }
+
+ /// <summary>
+ /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
+ /// </summary>
+ public bool Permanent { get; }
+
+ /// <summary>
+ /// Gets or sets an indication that the redirect preserves the initial request method.
+ /// </summary>
+ public bool PreserveMethod { get; }
+
+ /// <summary>
+ /// Gets or sets the local URL to redirect to.
+ /// </summary>
+ public string Url { get; }
+
+ /// <inheritdoc />
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ if (!SharedUrlHelper.IsLocalUrl(Url))
{
- if (string.IsNullOrEmpty(localUrl))
- {
- throw new ArgumentException("Argument cannot be null or empty", nameof(localUrl));
- }
-
- Permanent = permanent;
- PreserveMethod = preserveMethod;
- Url = localUrl;
+ throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.");
}
- /// <summary>
- /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
- /// </summary>
- public bool Permanent { get; }
+ var destinationUrl = SharedUrlHelper.Content(httpContext, Url);
- /// <summary>
- /// Gets or sets an indication that the redirect preserves the initial request method.
- /// </summary>
- public bool PreserveMethod { get; }
+ // IsLocalUrl is called to handle URLs starting with '~/'.
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<LocalRedirectResult>>();
- /// <summary>
- /// Gets or sets the local URL to redirect to.
- /// </summary>
- public string Url { get; }
+ Log.LocalRedirectResultExecuting(logger, destinationUrl);
- /// <inheritdoc />
- public Task ExecuteAsync(HttpContext httpContext)
+ if (PreserveMethod)
{
- if (!SharedUrlHelper.IsLocalUrl(Url))
- {
- throw new InvalidOperationException("The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.");
- }
-
- var destinationUrl = SharedUrlHelper.Content(httpContext, Url);
-
- // IsLocalUrl is called to handle URLs starting with '~/'.
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<LocalRedirectResult>>();
-
- Log.LocalRedirectResultExecuting(logger, destinationUrl);
-
- if (PreserveMethod)
- {
- httpContext.Response.StatusCode = Permanent
- ? StatusCodes.Status308PermanentRedirect
- : StatusCodes.Status307TemporaryRedirect;
- httpContext.Response.Headers.Location = destinationUrl;
- }
- else
- {
- httpContext.Response.Redirect(destinationUrl, Permanent);
- }
-
- return Task.CompletedTask;
+ httpContext.Response.StatusCode = Permanent
+ ? StatusCodes.Status308PermanentRedirect
+ : StatusCodes.Status307TemporaryRedirect;
+ httpContext.Response.Headers.Location = destinationUrl;
}
-
- private static partial class Log
+ else
{
- [LoggerMessage(1, LogLevel.Information,
- "Executing LocalRedirectResult, redirecting to {Destination}.",
- EventName = "LocalRedirectResultExecuting")]
- public static partial void LocalRedirectResultExecuting(ILogger logger, string destination);
+ httpContext.Response.Redirect(destinationUrl, Permanent);
}
+
+ return Task.CompletedTask;
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing LocalRedirectResult, redirecting to {Destination}.",
+ EventName = "LocalRedirectResultExecuting")]
+ public static partial void LocalRedirectResultExecuting(ILogger logger, string destination);
}
}
diff --git a/src/Http/Http.Results/src/NoContentResult.cs b/src/Http/Http.Results/src/NoContentResult.cs
index 7b143e8578..582484c406 100644
--- a/src/Http/Http.Results/src/NoContentResult.cs
+++ b/src/Http/Http.Results/src/NoContentResult.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal class NoContentResult : StatusCodeResult
{
- internal class NoContentResult : StatusCodeResult
+ public NoContentResult() : base(StatusCodes.Status204NoContent)
{
- public NoContentResult() : base(StatusCodes.Status204NoContent)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/NotFoundObjectResult.cs b/src/Http/Http.Results/src/NotFoundObjectResult.cs
index 2588567c54..5ce0e6083b 100644
--- a/src/Http/Http.Results/src/NotFoundObjectResult.cs
+++ b/src/Http/Http.Results/src/NotFoundObjectResult.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class NotFoundObjectResult : ObjectResult
{
- internal sealed class NotFoundObjectResult : ObjectResult
+ public NotFoundObjectResult(object? value)
+ : base(value, StatusCodes.Status404NotFound)
{
- public NotFoundObjectResult(object? value)
- : base(value, StatusCodes.Status404NotFound)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/ObjectResult.cs b/src/Http/Http.Results/src/ObjectResult.cs
index 1b57682474..3204d866b5 100644
--- a/src/Http/Http.Results/src/ObjectResult.cs
+++ b/src/Http/Http.Results/src/ObjectResult.cs
@@ -6,131 +6,130 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal partial class ObjectResult : IResult
{
- internal partial class ObjectResult : IResult
+ /// <summary>
+ /// Creates a new <see cref="ObjectResult"/> instance with the provided <paramref name="value"/>.
+ /// </summary>
+ public ObjectResult(object? value)
+ {
+ Value = value;
+ }
+
+ /// <summary>
+ /// Creates a new <see cref="ObjectResult"/> instance with the provided <paramref name="value"/>.
+ /// </summary>
+ public ObjectResult(object? value, int? statusCode)
{
- /// <summary>
- /// Creates a new <see cref="ObjectResult"/> instance with the provided <paramref name="value"/>.
- /// </summary>
- public ObjectResult(object? value)
+ Value = value;
+ StatusCode = statusCode;
+ }
+
+ /// <summary>
+ /// The object result.
+ /// </summary>
+ public object? Value { get; }
+
+ /// <summary>
+ /// Gets the HTTP status code.
+ /// </summary>
+ public int? StatusCode { get; set; }
+
+ /// <summary>
+ /// Gets the value for the <c>Content-Type</c> header.
+ /// </summary>
+ public string? ContentType { get; set; }
+
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
+ var logger = loggerFactory.CreateLogger(GetType());
+ Log.ObjectResultExecuting(logger, Value, StatusCode);
+
+ if (Value is ProblemDetails problemDetails)
{
- Value = value;
+ ApplyProblemDetailsDefaults(problemDetails);
}
- /// <summary>
- /// Creates a new <see cref="ObjectResult"/> instance with the provided <paramref name="value"/>.
- /// </summary>
- public ObjectResult(object? value, int? statusCode)
+ if (StatusCode is { } statusCode)
{
- Value = value;
- StatusCode = statusCode;
+ httpContext.Response.StatusCode = statusCode;
}
- /// <summary>
- /// The object result.
- /// </summary>
- public object? Value { get; }
+ ConfigureResponseHeaders(httpContext);
- /// <summary>
- /// Gets the HTTP status code.
- /// </summary>
- public int? StatusCode { get; set; }
+ if (Value is null)
+ {
+ return Task.CompletedTask;
+ }
- /// <summary>
- /// Gets the value for the <c>Content-Type</c> header.
- /// </summary>
- public string? ContentType { get; set; }
+ OnFormatting(httpContext);
+ return httpContext.Response.WriteAsJsonAsync(Value, Value.GetType(), options: null, contentType: ContentType);
+ }
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var loggerFactory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
- var logger = loggerFactory.CreateLogger(GetType());
- Log.ObjectResultExecuting(logger, Value, StatusCode);
+ protected virtual void OnFormatting(HttpContext httpContext)
+ {
+ }
- if (Value is ProblemDetails problemDetails)
- {
- ApplyProblemDetailsDefaults(problemDetails);
- }
+ protected virtual void ConfigureResponseHeaders(HttpContext httpContext)
+ {
+ }
- if (StatusCode is { } statusCode)
+ private void ApplyProblemDetailsDefaults(ProblemDetails problemDetails)
+ {
+ // We allow StatusCode to be specified either on ProblemDetails or on the ObjectResult and use it to configure the other.
+ // This lets users write <c>return Conflict(new Problem("some description"))</c>
+ // or <c>return Problem("some-problem", 422)</c> and have the response have consistent fields.
+ if (problemDetails.Status is null)
+ {
+ if (StatusCode is not null)
{
- httpContext.Response.StatusCode = statusCode;
+ problemDetails.Status = StatusCode;
}
-
- ConfigureResponseHeaders(httpContext);
-
- if (Value is null)
+ else
{
- return Task.CompletedTask;
+ problemDetails.Status = problemDetails is HttpValidationProblemDetails ?
+ StatusCodes.Status400BadRequest :
+ StatusCodes.Status500InternalServerError;
}
-
- OnFormatting(httpContext);
- return httpContext.Response.WriteAsJsonAsync(Value, Value.GetType(), options: null, contentType: ContentType);
}
- protected virtual void OnFormatting(HttpContext httpContext)
+ if (StatusCode is null)
{
+ StatusCode = problemDetails.Status;
}
- protected virtual void ConfigureResponseHeaders(HttpContext httpContext)
+ if (ProblemDetailsDefaults.Defaults.TryGetValue(problemDetails.Status.Value, out var defaults))
{
+ problemDetails.Title ??= defaults.Title;
+ problemDetails.Type ??= defaults.Type;
}
+ }
- private void ApplyProblemDetailsDefaults(ProblemDetails problemDetails)
+ private static partial class Log
+ {
+ public static void ObjectResultExecuting(ILogger logger, object? value, int? statusCode)
{
- // We allow StatusCode to be specified either on ProblemDetails or on the ObjectResult and use it to configure the other.
- // This lets users write <c>return Conflict(new Problem("some description"))</c>
- // or <c>return Problem("some-problem", 422)</c> and have the response have consistent fields.
- if (problemDetails.Status is null)
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (StatusCode is not null)
+ if (value is null)
{
- problemDetails.Status = StatusCode;
+ ObjectResultExecutingWithoutValue(logger, statusCode ?? StatusCodes.Status200OK);
}
else
{
- problemDetails.Status = problemDetails is HttpValidationProblemDetails ?
- StatusCodes.Status400BadRequest :
- StatusCodes.Status500InternalServerError;
+ var valueType = value.GetType().FullName!;
+ ObjectResultExecuting(logger, valueType, statusCode ?? StatusCodes.Status200OK);
}
}
-
- if (StatusCode is null)
- {
- StatusCode = problemDetails.Status;
- }
-
- if (ProblemDetailsDefaults.Defaults.TryGetValue(problemDetails.Status.Value, out var defaults))
- {
- problemDetails.Title ??= defaults.Title;
- problemDetails.Type ??= defaults.Type;
- }
}
- private static partial class Log
- {
- public static void ObjectResultExecuting(ILogger logger, object? value, int? statusCode)
- {
- if (logger.IsEnabled(LogLevel.Information))
- {
- if (value is null)
- {
- ObjectResultExecutingWithoutValue(logger, statusCode ?? StatusCodes.Status200OK);
- }
- else
- {
- var valueType = value.GetType().FullName!;
- ObjectResultExecuting(logger, valueType, statusCode ?? StatusCodes.Status200OK);
- }
- }
- }
-
- [LoggerMessage(1, LogLevel.Information, "Writing value of type '{Type}' with status code '{StatusCode}'.", EventName = "ObjectResultExecuting", SkipEnabledCheck = true)]
- private static partial void ObjectResultExecuting(ILogger logger, string type, int statusCode);
+ [LoggerMessage(1, LogLevel.Information, "Writing value of type '{Type}' with status code '{StatusCode}'.", EventName = "ObjectResultExecuting", SkipEnabledCheck = true)]
+ private static partial void ObjectResultExecuting(ILogger logger, string type, int statusCode);
- [LoggerMessage(2, LogLevel.Information, "Executing result with status code '{StatusCode}'.", EventName = "ObjectResultExecutingWithoutValue", SkipEnabledCheck = true)]
- private static partial void ObjectResultExecutingWithoutValue(ILogger logger, int statusCode);
- }
+ [LoggerMessage(2, LogLevel.Information, "Executing result with status code '{StatusCode}'.", EventName = "ObjectResultExecutingWithoutValue", SkipEnabledCheck = true)]
+ private static partial void ObjectResultExecutingWithoutValue(ILogger logger, int statusCode);
}
}
diff --git a/src/Http/Http.Results/src/OkObjectResult.cs b/src/Http/Http.Results/src/OkObjectResult.cs
index 830beadac2..70013671de 100644
--- a/src/Http/Http.Results/src/OkObjectResult.cs
+++ b/src/Http/Http.Results/src/OkObjectResult.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class OkObjectResult : ObjectResult
{
- internal sealed class OkObjectResult : ObjectResult
+ public OkObjectResult(object? value)
+ : base(value, StatusCodes.Status200OK)
{
- public OkObjectResult(object? value)
- : base(value, StatusCodes.Status200OK)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/PhysicalFileResult.cs b/src/Http/Http.Results/src/PhysicalFileResult.cs
index af01f4bd43..8c02e03270 100644
--- a/src/Http/Http.Results/src/PhysicalFileResult.cs
+++ b/src/Http/Http.Results/src/PhysicalFileResult.cs
@@ -5,116 +5,115 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// A <see cref="PhysicalFileResult"/> on execution will write a file from disk to the response
+/// using mechanisms provided by the host.
+/// </summary>
+internal sealed partial class PhysicalFileResult : FileResult, IResult
{
/// <summary>
- /// A <see cref="PhysicalFileResult"/> on execution will write a file from disk to the response
- /// using mechanisms provided by the host.
+ /// Creates a new <see cref="PhysicalFileResult"/> instance with
+ /// the provided <paramref name="fileName"/> and the provided <paramref name="contentType"/>.
+ /// </summary>
+ /// <param name="fileName">The path to the file. The path must be an absolute path.</param>
+ /// <param name="contentType">The Content-Type header of the response.</param>
+ public PhysicalFileResult(string fileName, string? contentType)
+ : base(contentType)
+ {
+ FileName = fileName;
+ }
+
+ /// <summary>
+ /// Gets or sets the path to the file that will be sent back as the response.
/// </summary>
- internal sealed partial class PhysicalFileResult : FileResult, IResult
+ public string FileName { get; }
+
+ // For testing
+ public Func<string, FileInfoWrapper> GetFileInfoWrapper { get; init; } =
+ static path => new FileInfoWrapper(path);
+
+ public Task ExecuteAsync(HttpContext httpContext)
{
- /// <summary>
- /// Creates a new <see cref="PhysicalFileResult"/> instance with
- /// the provided <paramref name="fileName"/> and the provided <paramref name="contentType"/>.
- /// </summary>
- /// <param name="fileName">The path to the file. The path must be an absolute path.</param>
- /// <param name="contentType">The Content-Type header of the response.</param>
- public PhysicalFileResult(string fileName, string? contentType)
- : base(contentType)
+ var fileInfo = GetFileInfoWrapper(FileName);
+ if (!fileInfo.Exists)
{
- FileName = fileName;
+ throw new FileNotFoundException($"Could not find file: {FileName}", FileName);
}
- /// <summary>
- /// Gets or sets the path to the file that will be sent back as the response.
- /// </summary>
- public string FileName { get; }
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<PhysicalFileResult>>();
+
+ Log.ExecutingFileResult(logger, this, FileName);
+
+ var lastModified = LastModified ?? fileInfo.LastWriteTimeUtc;
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ FileDownloadName = FileDownloadName,
+ LastModified = lastModified,
+ };
+
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ fileInfo.Length,
+ EnableRangeProcessing,
+ lastModified,
+ EntityTag,
+ logger);
+
+ if (!serveBody)
+ {
+ return Task.CompletedTask;
+ }
- // For testing
- public Func<string, FileInfoWrapper> GetFileInfoWrapper { get; init; } =
- static path => new FileInfoWrapper(path);
+ if (range != null && rangeLength == 0)
+ {
+ return Task.CompletedTask;
+ }
- public Task ExecuteAsync(HttpContext httpContext)
+ var response = httpContext.Response;
+ if (!Path.IsPathRooted(FileName))
{
- var fileInfo = GetFileInfoWrapper(FileName);
- if (!fileInfo.Exists)
- {
- throw new FileNotFoundException($"Could not find file: {FileName}", FileName);
- }
-
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<PhysicalFileResult>>();
-
- Log.ExecutingFileResult(logger, this, FileName);
-
- var lastModified = LastModified ?? fileInfo.LastWriteTimeUtc;
- var fileResultInfo = new FileResultInfo
- {
- ContentType = ContentType,
- EnableRangeProcessing = EnableRangeProcessing,
- EntityTag = EntityTag,
- FileDownloadName = FileDownloadName,
- LastModified = lastModified,
- };
-
- var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
- httpContext,
- fileResultInfo,
- fileInfo.Length,
- EnableRangeProcessing,
- lastModified,
- EntityTag,
- logger);
-
- if (!serveBody)
- {
- return Task.CompletedTask;
- }
-
- if (range != null && rangeLength == 0)
- {
- return Task.CompletedTask;
- }
-
- var response = httpContext.Response;
- if (!Path.IsPathRooted(FileName))
- {
- throw new NotSupportedException($"Path '{FileName}' was not rooted.");
- }
-
- if (range != null)
- {
- FileResultHelper.Log.WritingRangeToBody(logger);
- }
-
- var offset = 0L;
- var count = (long?)null;
- if (range != null)
- {
- offset = range.From ?? 0L;
- count = rangeLength;
- }
-
- return response.SendFileAsync(
- FileName,
- offset: offset,
- count: count);
+ throw new NotSupportedException($"Path '{FileName}' was not rooted.");
}
- internal readonly struct FileInfoWrapper
+ if (range != null)
{
- public FileInfoWrapper(string path)
- {
- var fileInfo = new FileInfo(path);
- Exists = fileInfo.Exists;
- Length = fileInfo.Length;
- LastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
- }
+ FileResultHelper.Log.WritingRangeToBody(logger);
+ }
- public bool Exists { get; init; }
+ var offset = 0L;
+ var count = (long?)null;
+ if (range != null)
+ {
+ offset = range.From ?? 0L;
+ count = rangeLength;
+ }
- public long Length { get; init; }
+ return response.SendFileAsync(
+ FileName,
+ offset: offset,
+ count: count);
+ }
- public DateTimeOffset LastWriteTimeUtc { get; init; }
+ internal readonly struct FileInfoWrapper
+ {
+ public FileInfoWrapper(string path)
+ {
+ var fileInfo = new FileInfo(path);
+ Exists = fileInfo.Exists;
+ Length = fileInfo.Length;
+ LastWriteTimeUtc = fileInfo.LastWriteTimeUtc;
}
+
+ public bool Exists { get; init; }
+
+ public long Length { get; init; }
+
+ public DateTimeOffset LastWriteTimeUtc { get; init; }
}
}
diff --git a/src/Http/Http.Results/src/RedirectResult.cs b/src/Http/Http.Results/src/RedirectResult.cs
index ac4c3ea2dd..f8c89e5821 100644
--- a/src/Http/Http.Results/src/RedirectResult.cs
+++ b/src/Http/Http.Results/src/RedirectResult.cs
@@ -7,80 +7,79 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed partial class RedirectResult : IResult
{
- internal sealed partial class RedirectResult : IResult
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectResult"/> class with the values
+ /// provided.
+ /// </summary>
+ /// <param name="url">The URL to redirect to.</param>
+ /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+ public RedirectResult(string url, bool permanent, bool preserveMethod)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectResult"/> class with the values
- /// provided.
- /// </summary>
- /// <param name="url">The URL to redirect to.</param>
- /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
- public RedirectResult(string url, bool permanent, bool preserveMethod)
+ if (url == null)
{
- if (url == null)
- {
- throw new ArgumentNullException(nameof(url));
- }
-
- if (string.IsNullOrEmpty(url))
- {
- throw new ArgumentException("Argument cannot be null or empty", nameof(url));
- }
+ throw new ArgumentNullException(nameof(url));
+ }
- Permanent = permanent;
- PreserveMethod = preserveMethod;
- Url = url;
+ if (string.IsNullOrEmpty(url))
+ {
+ throw new ArgumentException("Argument cannot be null or empty", nameof(url));
}
- /// <summary>
- /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
- /// </summary>
- public bool Permanent { get; }
+ Permanent = permanent;
+ PreserveMethod = preserveMethod;
+ Url = url;
+ }
- /// <summary>
- /// Gets or sets an indication that the redirect preserves the initial request method.
- /// </summary>
- public bool PreserveMethod { get; }
+ /// <summary>
+ /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false.
+ /// </summary>
+ public bool Permanent { get; }
- /// <summary>
- /// Gets or sets the URL to redirect to.
- /// </summary>
- public string Url { get; }
+ /// <summary>
+ /// Gets or sets an indication that the redirect preserves the initial request method.
+ /// </summary>
+ public bool PreserveMethod { get; }
- /// <inheritdoc />
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<RedirectResult>>();
+ /// <summary>
+ /// Gets or sets the URL to redirect to.
+ /// </summary>
+ public string Url { get; }
- // IsLocalUrl is called to handle URLs starting with '~/'.
- var destinationUrl = SharedUrlHelper.IsLocalUrl(Url) ? SharedUrlHelper.Content(httpContext, Url) : Url;
+ /// <inheritdoc />
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<RedirectResult>>();
- Log.RedirectResultExecuting(logger, destinationUrl);
+ // IsLocalUrl is called to handle URLs starting with '~/'.
+ var destinationUrl = SharedUrlHelper.IsLocalUrl(Url) ? SharedUrlHelper.Content(httpContext, Url) : Url;
- if (PreserveMethod)
- {
- httpContext.Response.StatusCode = Permanent
- ? StatusCodes.Status308PermanentRedirect
- : StatusCodes.Status307TemporaryRedirect;
- httpContext.Response.Headers.Location = destinationUrl;
- }
- else
- {
- httpContext.Response.Redirect(destinationUrl, Permanent);
- }
+ Log.RedirectResultExecuting(logger, destinationUrl);
- return Task.CompletedTask;
+ if (PreserveMethod)
+ {
+ httpContext.Response.StatusCode = Permanent
+ ? StatusCodes.Status308PermanentRedirect
+ : StatusCodes.Status307TemporaryRedirect;
+ httpContext.Response.Headers.Location = destinationUrl;
}
-
- private static partial class Log
+ else
{
- [LoggerMessage(1, LogLevel.Information,
- "Executing RedirectResult, redirecting to {Destination}.",
- EventName = "RedirectResultExecuting")]
- public static partial void RedirectResultExecuting(ILogger logger, string destination);
+ httpContext.Response.Redirect(destinationUrl, Permanent);
}
+
+ return Task.CompletedTask;
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing RedirectResult, redirecting to {Destination}.",
+ EventName = "RedirectResultExecuting")]
+ public static partial void RedirectResultExecuting(ILogger logger, string destination);
}
}
diff --git a/src/Http/Http.Results/src/RedirectToRouteResult.cs b/src/Http/Http.Results/src/RedirectToRouteResult.cs
index e531a11837..b45ca52995 100644
--- a/src/Http/Http.Results/src/RedirectToRouteResult.cs
+++ b/src/Http/Http.Results/src/RedirectToRouteResult.cs
@@ -7,188 +7,187 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
+/// or Permanent Redirect (308) response with a Location header.
+/// Targets a registered route.
+/// </summary>
+internal sealed partial class RedirectToRouteResult : IResult
{
/// <summary>
- /// An <see cref="IResult"/> that returns a Found (302), Moved Permanently (301), Temporary Redirect (307),
- /// or Permanent Redirect (308) response with a Location header.
- /// Targets a registered route.
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
/// </summary>
- internal sealed partial class RedirectToRouteResult : IResult
+ /// <param name="routeValues">The parameters for the route.</param>
+ public RedirectToRouteResult(object? routeValues)
+ : this(routeName: null, routeValues: routeValues)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeValues">The parameters for the route.</param>
- public RedirectToRouteResult(object? routeValues)
- : this(routeName: null, routeValues: routeValues)
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for the route.</param>
- public RedirectToRouteResult(
- string? routeName,
- object? routeValues)
- : this(routeName, routeValues, permanent: false)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for the route.</param>
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues)
+ : this(routeName, routeValues, permanent: false)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for the route.</param>
- /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
- public RedirectToRouteResult(
- string? routeName,
- object? routeValues,
- bool permanent)
- : this(routeName, routeValues, permanent, fragment: null)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for the route.</param>
+ /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ bool permanent)
+ : this(routeName, routeValues, permanent, fragment: null)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for the route.</param>
- /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
- public RedirectToRouteResult(
- string? routeName,
- object? routeValues,
- bool permanent,
- bool preserveMethod)
- : this(routeName, routeValues, permanent, preserveMethod, fragment: null)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for the route.</param>
+ /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ bool permanent,
+ bool preserveMethod)
+ : this(routeName, routeValues, permanent, preserveMethod, fragment: null)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for the route.</param>
- /// <param name="fragment">The fragment to add to the URL.</param>
- public RedirectToRouteResult(
- string? routeName,
- object? routeValues,
- string? fragment)
- : this(routeName, routeValues, permanent: false, fragment: fragment)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for the route.</param>
+ /// <param name="fragment">The fragment to add to the URL.</param>
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ string? fragment)
+ : this(routeName, routeValues, permanent: false, fragment: fragment)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for the route.</param>
- /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
- /// <param name="fragment">The fragment to add to the URL.</param>
- public RedirectToRouteResult(
- string? routeName,
- object? routeValues,
- bool permanent,
- string? fragment)
- : this(routeName, routeValues, permanent, preserveMethod: false, fragment: fragment)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for the route.</param>
+ /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+ /// <param name="fragment">The fragment to add to the URL.</param>
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ bool permanent,
+ string? fragment)
+ : this(routeName, routeValues, permanent, preserveMethod: false, fragment: fragment)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
+ /// provided.
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for the route.</param>
+ /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+ /// <param name="fragment">The fragment to add to the URL.</param>
+ public RedirectToRouteResult(
+ string? routeName,
+ object? routeValues,
+ bool permanent,
+ bool preserveMethod,
+ string? fragment)
+ {
+ RouteName = routeName;
+ RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
+ PreserveMethod = preserveMethod;
+ Permanent = permanent;
+ Fragment = fragment;
+ }
+
+ /// <summary>
+ /// Gets or sets the name of the route to use for generating the URL.
+ /// </summary>
+ public string? RouteName { get; }
+
+ /// <summary>
+ /// Gets or sets the route data to use for generating the URL.
+ /// </summary>
+ public RouteValueDictionary? RouteValues { get; }
+
+ /// <summary>
+ /// Gets or sets an indication that the redirect is permanent.
+ /// </summary>
+ public bool Permanent { get; }
+
+ /// <summary>
+ /// Gets or sets an indication that the redirect preserves the initial request method.
+ /// </summary>
+ public bool PreserveMethod { get; }
+
+ /// <summary>
+ /// Gets or sets the fragment to add to the URL.
+ /// </summary>
+ public string? Fragment { get; }
- /// <summary>
- /// Initializes a new instance of the <see cref="RedirectToRouteResult"/> with the values
- /// provided.
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for the route.</param>
- /// <param name="permanent">If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
- /// <param name="fragment">The fragment to add to the URL.</param>
- public RedirectToRouteResult(
- string? routeName,
- object? routeValues,
- bool permanent,
- bool preserveMethod,
- string? fragment)
+ /// <inheritdoc />
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
+
+ var destinationUrl = linkGenerator.GetUriByRouteValues(
+ httpContext,
+ RouteName,
+ RouteValues,
+ fragment: Fragment == null ? FragmentString.Empty : new FragmentString("#" + Fragment));
+ if (string.IsNullOrEmpty(destinationUrl))
{
- RouteName = routeName;
- RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues);
- PreserveMethod = preserveMethod;
- Permanent = permanent;
- Fragment = fragment;
+ throw new InvalidOperationException("No route matches the supplied values.");
}
- /// <summary>
- /// Gets or sets the name of the route to use for generating the URL.
- /// </summary>
- public string? RouteName { get; }
-
- /// <summary>
- /// Gets or sets the route data to use for generating the URL.
- /// </summary>
- public RouteValueDictionary? RouteValues { get; }
-
- /// <summary>
- /// Gets or sets an indication that the redirect is permanent.
- /// </summary>
- public bool Permanent { get; }
-
- /// <summary>
- /// Gets or sets an indication that the redirect preserves the initial request method.
- /// </summary>
- public bool PreserveMethod { get; }
-
- /// <summary>
- /// Gets or sets the fragment to add to the URL.
- /// </summary>
- public string? Fragment { get; }
-
- /// <inheritdoc />
- public Task ExecuteAsync(HttpContext httpContext)
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<RedirectToRouteResult>>();
+ Log.RedirectToRouteResultExecuting(logger, destinationUrl, RouteName);
+
+ if (PreserveMethod)
{
- var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
-
- var destinationUrl = linkGenerator.GetUriByRouteValues(
- httpContext,
- RouteName,
- RouteValues,
- fragment: Fragment == null ? FragmentString.Empty : new FragmentString("#" + Fragment));
- if (string.IsNullOrEmpty(destinationUrl))
- {
- throw new InvalidOperationException("No route matches the supplied values.");
- }
-
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<RedirectToRouteResult>>();
- Log.RedirectToRouteResultExecuting(logger, destinationUrl, RouteName);
-
- if (PreserveMethod)
- {
- httpContext.Response.StatusCode = Permanent ?
- StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect;
- httpContext.Response.Headers.Location = destinationUrl;
- }
- else
- {
- httpContext.Response.Redirect(destinationUrl, Permanent);
- }
-
- return Task.CompletedTask;
+ httpContext.Response.StatusCode = Permanent ?
+ StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect;
+ httpContext.Response.Headers.Location = destinationUrl;
}
-
- private static partial class Log
+ else
{
- [LoggerMessage(1, LogLevel.Information,
- "Executing RedirectToRouteResult, redirecting to {Destination} from route {RouteName}.",
- EventName = "RedirectToRouteResultExecuting")]
- public static partial void RedirectToRouteResultExecuting(ILogger logger, string destination, string? routeName);
+ httpContext.Response.Redirect(destinationUrl, Permanent);
}
+
+ return Task.CompletedTask;
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing RedirectToRouteResult, redirecting to {Destination} from route {RouteName}.",
+ EventName = "RedirectToRouteResultExecuting")]
+ public static partial void RedirectToRouteResultExecuting(ILogger logger, string destination, string? routeName);
}
}
diff --git a/src/Http/Http.Results/src/ResultExtensions.cs b/src/Http/Http.Results/src/ResultExtensions.cs
index cab72d4ff2..d10a3bd59a 100644
--- a/src/Http/Http.Results/src/ResultExtensions.cs
+++ b/src/Http/Http.Results/src/ResultExtensions.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
-{
- /// <summary>
- /// Implements an interface for registering external methods that provide
- /// custom IResult instances.
- /// </summary>
- internal class ResultExtensions : IResultExtensions { }
-} \ No newline at end of file
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Implements an interface for registering external methods that provide
+/// custom IResult instances.
+/// </summary>
+internal class ResultExtensions : IResultExtensions { }
diff --git a/src/Http/Http.Results/src/Results.cs b/src/Http/Http.Results/src/Results.cs
index 9632ac0a3b..0b7f0a228d 100644
--- a/src/Http/Http.Results/src/Results.cs
+++ b/src/Http/Http.Results/src/Results.cs
@@ -11,622 +11,621 @@ using Microsoft.AspNetCore.Http.Result;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A factory for <see cref="IResult"/>.
+/// </summary>
+public static class Results
{
/// <summary>
- /// A factory for <see cref="IResult"/>.
+ /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext, string?, AuthenticationProperties?)" />.
+ /// <para>
+ /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
+ /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
+ /// are among likely status results.
+ /// </para>
+ /// </summary>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Challenge(
+ AuthenticationProperties? properties = null,
+ IList<string>? authenticationSchemes = null)
+ => new ChallengeResult { AuthenticationSchemes = authenticationSchemes ?? Array.Empty<string>(), Properties = properties };
+
+ /// <summary>
+ /// Creates a <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ForbidAsync(HttpContext, string?, AuthenticationProperties?)"/>.
+ /// <para>
+ /// By default, executing this result returns a <see cref="StatusCodes.Status403Forbidden"/>. Some authentication schemes, such as cookies,
+ /// will convert <see cref="StatusCodes.Status403Forbidden"/> to a redirect to show a login page.
+ /// </para>
+ /// </summary>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
+ /// challenge.</param>
+ /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ /// <remarks>
+ /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
+ /// a redirect to show a login page.
+ /// </remarks>
+ public static IResult Forbid(AuthenticationProperties? properties = null, IList<string>? authenticationSchemes = null)
+ => new ForbidResult { Properties = properties, AuthenticationSchemes = authenticationSchemes ?? Array.Empty<string>(), };
+
+ /// <summary>
+ /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignInAsync(HttpContext, string?, ClaimsPrincipal, AuthenticationProperties?)" />.
+ /// </summary>
+ /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+ /// <param name="authenticationScheme">The authentication scheme to use for the sign-in operation.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult SignIn(
+ ClaimsPrincipal principal,
+ AuthenticationProperties? properties = null,
+ string? authenticationScheme = null)
+ => new SignInResult(authenticationScheme, principal, properties);
+
+ /// <summary>
+ /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignOutAsync(HttpContext, string?, AuthenticationProperties?)" />.
+ /// </summary>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+ /// <param name="authenticationSchemes">The authentication scheme to use for the sign-out operation.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult SignOut(AuthenticationProperties? properties = null, IList<string>? authenticationSchemes = null)
+ => new SignOutResult(authenticationSchemes ?? Array.Empty<string>(), properties);
+
+ /// <summary>
+ /// Writes the <paramref name="content"/> string to the HTTP response.
+ /// <para>
+ /// This is an alias for <see cref="Text(string, string?, Encoding?)"/>.
+ /// </para>
+ /// </summary>
+ /// <param name="content">The content to write to the response.</param>
+ /// <param name="contentType">The content type (MIME type).</param>
+ /// <param name="contentEncoding">The content encoding.</param>
+ /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+ /// <remarks>
+ /// If encoding is provided by both the 'charset' and the <paramref name="contentEncoding"/> parameters, then
+ /// the <paramref name="contentEncoding"/> parameter is chosen as the final encoding.
+ /// </remarks>
+ public static IResult Content(string content, string? contentType = null, Encoding? contentEncoding = null)
+ => Text(content, contentType, contentEncoding);
+
+ /// <summary>
+ /// Writes the <paramref name="content"/> string to the HTTP response.
+ /// <para>
+ /// This is an alias for <see cref="Content(string, string?, Encoding?)"/>.
+ /// </para>
/// </summary>
- public static class Results
+ /// <param name="content">The content to write to the response.</param>
+ /// <param name="contentType">The content type (MIME type).</param>
+ /// <param name="contentEncoding">The content encoding.</param>
+ /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+ /// <remarks>
+ /// If encoding is provided by both the 'charset' and the <paramref name="contentEncoding"/> parameters, then
+ /// the <paramref name="contentEncoding"/> parameter is chosen as the final encoding.
+ /// </remarks>
+ public static IResult Text(string content, string? contentType = null, Encoding? contentEncoding = null)
{
- /// <summary>
- /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ChallengeAsync(HttpContext, string?, AuthenticationProperties?)" />.
- /// <para>
- /// The behavior of this method depends on the <see cref="IAuthenticationService"/> in use.
- /// <see cref="StatusCodes.Status401Unauthorized"/> and <see cref="StatusCodes.Status403Forbidden"/>
- /// are among likely status results.
- /// </para>
- /// </summary>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Challenge(
- AuthenticationProperties? properties = null,
- IList<string>? authenticationSchemes = null)
- => new ChallengeResult { AuthenticationSchemes = authenticationSchemes ?? Array.Empty<string>(), Properties = properties };
-
- /// <summary>
- /// Creates a <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.ForbidAsync(HttpContext, string?, AuthenticationProperties?)"/>.
- /// <para>
- /// By default, executing this result returns a <see cref="StatusCodes.Status403Forbidden"/>. Some authentication schemes, such as cookies,
- /// will convert <see cref="StatusCodes.Status403Forbidden"/> to a redirect to show a login page.
- /// </para>
- /// </summary>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the authentication
- /// challenge.</param>
- /// <param name="authenticationSchemes">The authentication schemes to challenge.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- /// <remarks>
- /// Some authentication schemes, such as cookies, will convert <see cref="StatusCodes.Status403Forbidden"/> to
- /// a redirect to show a login page.
- /// </remarks>
- public static IResult Forbid(AuthenticationProperties? properties = null, IList<string>? authenticationSchemes = null)
- => new ForbidResult { Properties = properties, AuthenticationSchemes = authenticationSchemes ?? Array.Empty<string>(), };
-
- /// <summary>
- /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignInAsync(HttpContext, string?, ClaimsPrincipal, AuthenticationProperties?)" />.
- /// </summary>
- /// <param name="principal">The <see cref="ClaimsPrincipal"/> containing the user claims.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
- /// <param name="authenticationScheme">The authentication scheme to use for the sign-in operation.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult SignIn(
- ClaimsPrincipal principal,
- AuthenticationProperties? properties = null,
- string? authenticationScheme = null)
- => new SignInResult(authenticationScheme, principal, properties);
-
- /// <summary>
- /// Creates an <see cref="IResult"/> that on execution invokes <see cref="AuthenticationHttpContextExtensions.SignOutAsync(HttpContext, string?, AuthenticationProperties?)" />.
- /// </summary>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
- /// <param name="authenticationSchemes">The authentication scheme to use for the sign-out operation.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult SignOut(AuthenticationProperties? properties = null, IList<string>? authenticationSchemes = null)
- => new SignOutResult(authenticationSchemes ?? Array.Empty<string>(), properties);
-
- /// <summary>
- /// Writes the <paramref name="content"/> string to the HTTP response.
- /// <para>
- /// This is an alias for <see cref="Text(string, string?, Encoding?)"/>.
- /// </para>
- /// </summary>
- /// <param name="content">The content to write to the response.</param>
- /// <param name="contentType">The content type (MIME type).</param>
- /// <param name="contentEncoding">The content encoding.</param>
- /// <returns>The created <see cref="IResult"/> object for the response.</returns>
- /// <remarks>
- /// If encoding is provided by both the 'charset' and the <paramref name="contentEncoding"/> parameters, then
- /// the <paramref name="contentEncoding"/> parameter is chosen as the final encoding.
- /// </remarks>
- public static IResult Content(string content, string? contentType = null, Encoding? contentEncoding = null)
- => Text(content, contentType, contentEncoding);
-
- /// <summary>
- /// Writes the <paramref name="content"/> string to the HTTP response.
- /// <para>
- /// This is an alias for <see cref="Content(string, string?, Encoding?)"/>.
- /// </para>
- /// </summary>
- /// <param name="content">The content to write to the response.</param>
- /// <param name="contentType">The content type (MIME type).</param>
- /// <param name="contentEncoding">The content encoding.</param>
- /// <returns>The created <see cref="IResult"/> object for the response.</returns>
- /// <remarks>
- /// If encoding is provided by both the 'charset' and the <paramref name="contentEncoding"/> parameters, then
- /// the <paramref name="contentEncoding"/> parameter is chosen as the final encoding.
- /// </remarks>
- public static IResult Text(string content, string? contentType = null, Encoding? contentEncoding = null)
+ MediaTypeHeaderValue? mediaTypeHeaderValue = null;
+ if (contentType is not null)
{
- MediaTypeHeaderValue? mediaTypeHeaderValue = null;
- if (contentType is not null)
- {
- mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType);
- mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding;
- }
-
- return new ContentResult
- {
- Content = content,
- ContentType = mediaTypeHeaderValue?.ToString()
- };
+ mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType);
+ mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding;
}
- /// <summary>
- /// Writes the <paramref name="content"/> string to the HTTP response.
- /// </summary>
- /// <param name="content">The content to write to the response.</param>
- /// <param name="contentType">The content type (MIME type).</param>
- /// <returns>The created <see cref="IResult"/> object for the response.</returns>
- public static IResult Content(string content, MediaTypeHeaderValue contentType)
- => new ContentResult
- {
- Content = content,
- ContentType = contentType.ToString()
- };
+ return new ContentResult
+ {
+ Content = content,
+ ContentType = mediaTypeHeaderValue?.ToString()
+ };
+ }
- /// <summary>
- /// Creates a <see cref="IResult"/> that serializes the specified <paramref name="data"/> object to JSON.
- /// </summary>
- /// <param name="data">The object to write as JSON.</param>
- /// <param name="options">The serializer options use when serializing the value.</param>
- /// <param name="contentType">The content-type to set on the response.</param>
- /// <param name="statusCode">The status code to set on the response.</param>
- /// <returns>The created <see cref="JsonResult"/> that serializes the specified <paramref name="data"/>
- /// as JSON format for the response.</returns>
- /// <remarks>Callers should cache an instance of serializer settings to avoid
- /// recreating cached data with each call.</remarks>
- public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)
- => new JsonResult
- {
- Value = data,
- JsonSerializerOptions = options,
- ContentType = contentType,
- StatusCode = statusCode,
- };
+ /// <summary>
+ /// Writes the <paramref name="content"/> string to the HTTP response.
+ /// </summary>
+ /// <param name="content">The content to write to the response.</param>
+ /// <param name="contentType">The content type (MIME type).</param>
+ /// <returns>The created <see cref="IResult"/> object for the response.</returns>
+ public static IResult Content(string content, MediaTypeHeaderValue contentType)
+ => new ContentResult
+ {
+ Content = content,
+ ContentType = contentType.ToString()
+ };
- /// <summary>
- /// Writes the byte-array content to the response.
- /// <para>
- /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
- /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
- /// </para>
- /// <para>
- /// This API is an alias for <see cref="Bytes(byte[], string, string?, bool, DateTimeOffset?, EntityTagHeaderValue?)"/>.</para>
- /// </summary>
- /// <param name="fileContents">The file contents.</param>
- /// <param name="contentType">The Content-Type of the file.</param>
- /// <param name="fileDownloadName">The suggested file name.</param>
- /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
- /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
- /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ /// <summary>
+ /// Creates a <see cref="IResult"/> that serializes the specified <paramref name="data"/> object to JSON.
+ /// </summary>
+ /// <param name="data">The object to write as JSON.</param>
+ /// <param name="options">The serializer options use when serializing the value.</param>
+ /// <param name="contentType">The content-type to set on the response.</param>
+ /// <param name="statusCode">The status code to set on the response.</param>
+ /// <returns>The created <see cref="JsonResult"/> that serializes the specified <paramref name="data"/>
+ /// as JSON format for the response.</returns>
+ /// <remarks>Callers should cache an instance of serializer settings to avoid
+ /// recreating cached data with each call.</remarks>
+ public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)
+ => new JsonResult
+ {
+ Value = data,
+ JsonSerializerOptions = options,
+ ContentType = contentType,
+ StatusCode = statusCode,
+ };
+
+ /// <summary>
+ /// Writes the byte-array content to the response.
+ /// <para>
+ /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+ /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+ /// </para>
+ /// <para>
+ /// This API is an alias for <see cref="Bytes(byte[], string, string?, bool, DateTimeOffset?, EntityTagHeaderValue?)"/>.</para>
+ /// </summary>
+ /// <param name="fileContents">The file contents.</param>
+ /// <param name="contentType">The Content-Type of the file.</param>
+ /// <param name="fileDownloadName">The suggested file name.</param>
+ /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+ /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+ /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
- public static IResult File(
+ public static IResult File(
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
byte[] fileContents,
- string? contentType = null,
- string? fileDownloadName = null,
- bool enableRangeProcessing = false,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue? entityTag = null)
- => new FileContentResult(fileContents, contentType)
- {
- FileDownloadName = fileDownloadName,
- EnableRangeProcessing = enableRangeProcessing,
- LastModified = lastModified,
- EntityTag = entityTag,
- };
+ string? contentType = null,
+ string? fileDownloadName = null,
+ bool enableRangeProcessing = false,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ => new FileContentResult(fileContents, contentType)
+ {
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
- /// <summary>
- /// Writes the byte-array content to the response.
- /// <para>
- /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
- /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
- /// </para>
- /// <para>
- /// This API is an alias for <see cref="File(byte[], string, string?, bool, DateTimeOffset?, EntityTagHeaderValue?)"/>.</para>
- /// </summary>
- /// <param name="contents">The file contents.</param>
- /// <param name="contentType">The Content-Type of the file.</param>
- /// <param name="fileDownloadName">The suggested file name.</param>
- /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
- /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
- /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Bytes(
- byte[] contents,
- string? contentType = null,
- string? fileDownloadName = null,
- bool enableRangeProcessing = false,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue? entityTag = null)
- => new FileContentResult(contents, contentType)
- {
- FileDownloadName = fileDownloadName,
- EnableRangeProcessing = enableRangeProcessing,
- LastModified = lastModified,
- EntityTag = entityTag,
- };
+ /// <summary>
+ /// Writes the byte-array content to the response.
+ /// <para>
+ /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+ /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+ /// </para>
+ /// <para>
+ /// This API is an alias for <see cref="File(byte[], string, string?, bool, DateTimeOffset?, EntityTagHeaderValue?)"/>.</para>
+ /// </summary>
+ /// <param name="contents">The file contents.</param>
+ /// <param name="contentType">The Content-Type of the file.</param>
+ /// <param name="fileDownloadName">The suggested file name.</param>
+ /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+ /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+ /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Bytes(
+ byte[] contents,
+ string? contentType = null,
+ string? fileDownloadName = null,
+ bool enableRangeProcessing = false,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null)
+ => new FileContentResult(contents, contentType)
+ {
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
- /// <summary>
- /// Writes the specified <see cref="Stream"/> to the response.
- /// <para>
- /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
- /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
- /// </para>
- /// <para>
- /// This API is an alias for <see cref="Stream(Stream, string, string?, DateTimeOffset?, EntityTagHeaderValue?, bool)"/>.
- /// </para>
- /// </summary>
- /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
- /// <param name="contentType">The Content-Type of the file.</param>
- /// <param name="fileDownloadName">The the file name to be used in the <c>Content-Disposition</c> header.</param>
- /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.
- /// Used to configure the <c>Last-Modified</c> response header and perform conditional range requests.</param>
- /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> to be configure the <c>ETag</c> response header
- /// and perform conditional requests.</param>
- /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- /// <remarks>
- /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
- /// </remarks>
+ /// <summary>
+ /// Writes the specified <see cref="Stream"/> to the response.
+ /// <para>
+ /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+ /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+ /// </para>
+ /// <para>
+ /// This API is an alias for <see cref="Stream(Stream, string, string?, DateTimeOffset?, EntityTagHeaderValue?, bool)"/>.
+ /// </para>
+ /// </summary>
+ /// <param name="fileStream">The <see cref="Stream"/> with the contents of the file.</param>
+ /// <param name="contentType">The Content-Type of the file.</param>
+ /// <param name="fileDownloadName">The the file name to be used in the <c>Content-Disposition</c> header.</param>
+ /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.
+ /// Used to configure the <c>Last-Modified</c> response header and perform conditional range requests.</param>
+ /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> to be configure the <c>ETag</c> response header
+ /// and perform conditional requests.</param>
+ /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ /// <remarks>
+ /// The <paramref name="fileStream" /> parameter is disposed after the response is sent.
+ /// </remarks>
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
- public static IResult File(
+ public static IResult File(
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
Stream fileStream,
- string? contentType = null,
- string? fileDownloadName = null,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue? entityTag = null,
- bool enableRangeProcessing = false)
+ string? contentType = null,
+ string? fileDownloadName = null,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null,
+ bool enableRangeProcessing = false)
+ {
+ return new FileStreamResult(fileStream, contentType)
{
- return new FileStreamResult(fileStream, contentType)
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+
+ /// <summary>
+ /// Writes the specified <see cref="Stream"/> to the response.
+ /// <para>
+ /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+ /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+ /// </para>
+ /// <para>
+ /// This API is an alias for <see cref="File(Stream, string, string?, DateTimeOffset?, EntityTagHeaderValue?, bool)"/>.
+ /// </para>
+ /// </summary>
+ /// <param name="stream">The <see cref="Stream"/> to write to the response.</param>
+ /// <param name="contentType">The <c>Content-Type</c> of the response. Defaults to <c>application/octet-stream</c>.</param>
+ /// <param name="fileDownloadName">The the file name to be used in the <c>Content-Disposition</c> header.</param>
+ /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.
+ /// Used to configure the <c>Last-Modified</c> response header and perform conditional range requests.</param>
+ /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> to be configure the <c>ETag</c> response header
+ /// and perform conditional requests.</param>
+ /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ /// <remarks>
+ /// The <paramref name="stream" /> parameter is disposed after the response is sent.
+ /// </remarks>
+ public static IResult Stream(
+ Stream stream,
+ string? contentType = null,
+ string? fileDownloadName = null,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null,
+ bool enableRangeProcessing = false)
+ {
+ return new FileStreamResult(stream, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ FileDownloadName = fileDownloadName,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
+ }
+
+ /// <summary>
+ /// Writes the file at the specified <paramref name="path"/> to the response.
+ /// <para>
+ /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
+ /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
+ /// </para>
+ /// </summary>
+ /// <param name="path">The path to the file. When not rooted, resolves the path relative to <see cref="IWebHostEnvironment.WebRootFileProvider"/>.</param>
+ /// <param name="contentType">The Content-Type of the file.</param>
+ /// <param name="fileDownloadName">The suggested file name.</param>
+ /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
+ /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
+ /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
+ public static IResult File(
+#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
+ string path,
+ string? contentType = null,
+ string? fileDownloadName = null,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue? entityTag = null,
+ bool enableRangeProcessing = false)
+ {
+ if (Path.IsPathRooted(path))
+ {
+ return new PhysicalFileResult(path, contentType)
{
+ FileDownloadName = fileDownloadName,
LastModified = lastModified,
EntityTag = entityTag,
- FileDownloadName = fileDownloadName,
EnableRangeProcessing = enableRangeProcessing,
};
}
-
- /// <summary>
- /// Writes the specified <see cref="Stream"/> to the response.
- /// <para>
- /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
- /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
- /// </para>
- /// <para>
- /// This API is an alias for <see cref="File(Stream, string, string?, DateTimeOffset?, EntityTagHeaderValue?, bool)"/>.
- /// </para>
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to write to the response.</param>
- /// <param name="contentType">The <c>Content-Type</c> of the response. Defaults to <c>application/octet-stream</c>.</param>
- /// <param name="fileDownloadName">The the file name to be used in the <c>Content-Disposition</c> header.</param>
- /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.
- /// Used to configure the <c>Last-Modified</c> response header and perform conditional range requests.</param>
- /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> to be configure the <c>ETag</c> response header
- /// and perform conditional requests.</param>
- /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- /// <remarks>
- /// The <paramref name="stream" /> parameter is disposed after the response is sent.
- /// </remarks>
- public static IResult Stream(
- Stream stream,
- string? contentType = null,
- string? fileDownloadName = null,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue? entityTag = null,
- bool enableRangeProcessing = false)
+ else
{
- return new FileStreamResult(stream, contentType)
+ return new VirtualFileResult(path, contentType)
{
+ FileDownloadName = fileDownloadName,
LastModified = lastModified,
EntityTag = entityTag,
- FileDownloadName = fileDownloadName,
EnableRangeProcessing = enableRangeProcessing,
};
}
+ }
- /// <summary>
- /// Writes the file at the specified <paramref name="path"/> to the response.
- /// <para>
- /// This supports range requests (<see cref="StatusCodes.Status206PartialContent"/> or
- /// <see cref="StatusCodes.Status416RangeNotSatisfiable"/> if the range is not satisfiable).
- /// </para>
- /// </summary>
- /// <param name="path">The path to the file. When not rooted, resolves the path relative to <see cref="IWebHostEnvironment.WebRootFileProvider"/>.</param>
- /// <param name="contentType">The Content-Type of the file.</param>
- /// <param name="fileDownloadName">The suggested file name.</param>
- /// <param name="lastModified">The <see cref="DateTimeOffset"/> of when the file was last modified.</param>
- /// <param name="entityTag">The <see cref="EntityTagHeaderValue"/> associated with the file.</param>
- /// <param name="enableRangeProcessing">Set to <c>true</c> to enable range requests processing.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
-#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
- public static IResult File(
-#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
- string path,
- string? contentType = null,
- string? fileDownloadName = null,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue? entityTag = null,
- bool enableRangeProcessing = false)
+ /// <summary>
+ /// Redirects to the specified <paramref name="url"/>.
+ /// <list type="bullet">
+ /// <item>When <paramref name="permanent"/> and <paramref name="preserveMethod"/> are set, sets the <see cref="StatusCodes.Status308PermanentRedirect"/> status code.</item>
+ /// <item>When <paramref name="preserveMethod"/> is set, sets the <see cref="StatusCodes.Status307TemporaryRedirect"/> status code.</item>
+ /// <item>When <paramref name="permanent"/> is set, sets the <see cref="StatusCodes.Status301MovedPermanently"/> status code.</item>
+ /// <item>Otherwise, configures <see cref="StatusCodes.Status302Found"/>.</item>
+ /// </list>
+ /// </summary>
+ /// <param name="url">The URL to redirect to.</param>
+ /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Redirect(string url, bool permanent = false, bool preserveMethod = false)
+ => new RedirectResult(url, permanent, preserveMethod);
+
+ /// <summary>
+ /// Redirects to the specified <paramref name="localUrl"/>.
+ /// <list type="bullet">
+ /// <item>When <paramref name="permanent"/> and <paramref name="preserveMethod"/> are set, sets the <see cref="StatusCodes.Status308PermanentRedirect"/> status code.</item>
+ /// <item>When <paramref name="preserveMethod"/> is set, sets the <see cref="StatusCodes.Status307TemporaryRedirect"/> status code.</item>
+ /// <item>When <paramref name="permanent"/> is set, sets the <see cref="StatusCodes.Status301MovedPermanently"/> status code.</item>
+ /// <item>Otherwise, configures <see cref="StatusCodes.Status302Found"/>.</item>
+ /// </list>
+ /// </summary>
+ /// <param name="localUrl">The local URL to redirect to.</param>
+ /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult LocalRedirect(string localUrl, bool permanent = false, bool preserveMethod = false)
+ => new LocalRedirectResult(localUrl, permanent, preserveMethod);
+
+ /// <summary>
+ /// Redirects to the specified route.
+ /// <list type="bullet">
+ /// <item>When <paramref name="permanent"/> and <paramref name="preserveMethod"/> are set, sets the <see cref="StatusCodes.Status308PermanentRedirect"/> status code.</item>
+ /// <item>When <paramref name="preserveMethod"/> is set, sets the <see cref="StatusCodes.Status307TemporaryRedirect"/> status code.</item>
+ /// <item>When <paramref name="permanent"/> is set, sets the <see cref="StatusCodes.Status301MovedPermanently"/> status code.</item>
+ /// <item>Otherwise, configures <see cref="StatusCodes.Status302Found"/>.</item>
+ /// </list>
+ /// </summary>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeValues">The parameters for a route.</param>
+ /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
+ /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
+ /// <param name="fragment">The fragment to add to the URL.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult RedirectToRoute(string? routeName = null, object? routeValues = null, bool permanent = false, bool preserveMethod = false, string? fragment = null)
+ => new RedirectToRouteResult(
+ routeName: routeName,
+ routeValues: routeValues,
+ permanent: permanent,
+ preserveMethod: preserveMethod,
+ fragment: fragment);
+
+ /// <summary>
+ /// Creates a <see cref="StatusCodeResult"/> object by specifying a <paramref name="statusCode"/>.
+ /// </summary>
+ /// <param name="statusCode">The status code to set on the response.</param>
+ /// <returns>The created <see cref="StatusCodeResult"/> object for the response.</returns>
+ public static IResult StatusCode(int statusCode)
+ => new StatusCodeResult(statusCode);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status404NotFound"/> response.
+ /// </summary>
+ /// <param name="value">The value to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult NotFound(object? value = null)
+ => new NotFoundObjectResult(value);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status401Unauthorized"/> response.
+ /// </summary>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Unauthorized()
+ => new UnauthorizedResult();
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response.
+ /// </summary>
+ /// <param name="error">An error object to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult BadRequest(object? error = null)
+ => new BadRequestObjectResult(error);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status409Conflict"/> response.
+ /// </summary>
+ /// <param name="error">An error object to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Conflict(object? error = null)
+ => new ConflictObjectResult(error);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status204NoContent"/> response.
+ /// </summary>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult NoContent()
+ => new NoContentResult();
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status200OK"/> response.
+ /// </summary>
+ /// <param name="value">The value to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Ok(object? value = null)
+ => new OkObjectResult(value);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
+ /// </summary>
+ /// <param name="error">An error object to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult UnprocessableEntity(object? error = null)
+ => new UnprocessableEntityObjectResult(error);
+
+ /// <summary>
+ /// Produces a <see cref="ProblemDetails"/> response.
+ /// </summary>
+ /// <param name="statusCode">The value for <see cref="ProblemDetails.Status" />.</param>
+ /// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param>
+ /// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param>
+ /// <param name="title">The value for <see cref="ProblemDetails.Title" />.</param>
+ /// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param>
+ /// <param name="extensions">The value for <see cref="ProblemDetails.Extensions" />.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Problem(
+ string? detail = null,
+ string? instance = null,
+ int? statusCode = null,
+ string? title = null,
+ string? type = null,
+ IDictionary<string, object?>? extensions = null)
+ {
+ var problemDetails = new ProblemDetails
{
- if (Path.IsPathRooted(path))
- {
- return new PhysicalFileResult(path, contentType)
- {
- FileDownloadName = fileDownloadName,
- LastModified = lastModified,
- EntityTag = entityTag,
- EnableRangeProcessing = enableRangeProcessing,
- };
- }
- else
+ Detail = detail,
+ Instance = instance,
+ Status = statusCode,
+ Title = title,
+ Type = type,
+ };
+
+ if (extensions is not null)
+ {
+ foreach (var extension in extensions)
{
- return new VirtualFileResult(path, contentType)
- {
- FileDownloadName = fileDownloadName,
- LastModified = lastModified,
- EntityTag = entityTag,
- EnableRangeProcessing = enableRangeProcessing,
- };
+ problemDetails.Extensions.Add(extension);
}
}
- /// <summary>
- /// Redirects to the specified <paramref name="url"/>.
- /// <list type="bullet">
- /// <item>When <paramref name="permanent"/> and <paramref name="preserveMethod"/> are set, sets the <see cref="StatusCodes.Status308PermanentRedirect"/> status code.</item>
- /// <item>When <paramref name="preserveMethod"/> is set, sets the <see cref="StatusCodes.Status307TemporaryRedirect"/> status code.</item>
- /// <item>When <paramref name="permanent"/> is set, sets the <see cref="StatusCodes.Status301MovedPermanently"/> status code.</item>
- /// <item>Otherwise, configures <see cref="StatusCodes.Status302Found"/>.</item>
- /// </list>
- /// </summary>
- /// <param name="url">The URL to redirect to.</param>
- /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Redirect(string url, bool permanent = false, bool preserveMethod = false)
- => new RedirectResult(url, permanent, preserveMethod);
-
- /// <summary>
- /// Redirects to the specified <paramref name="localUrl"/>.
- /// <list type="bullet">
- /// <item>When <paramref name="permanent"/> and <paramref name="preserveMethod"/> are set, sets the <see cref="StatusCodes.Status308PermanentRedirect"/> status code.</item>
- /// <item>When <paramref name="preserveMethod"/> is set, sets the <see cref="StatusCodes.Status307TemporaryRedirect"/> status code.</item>
- /// <item>When <paramref name="permanent"/> is set, sets the <see cref="StatusCodes.Status301MovedPermanently"/> status code.</item>
- /// <item>Otherwise, configures <see cref="StatusCodes.Status302Found"/>.</item>
- /// </list>
- /// </summary>
- /// <param name="localUrl">The local URL to redirect to.</param>
- /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult LocalRedirect(string localUrl, bool permanent = false, bool preserveMethod = false)
- => new LocalRedirectResult(localUrl, permanent, preserveMethod);
-
- /// <summary>
- /// Redirects to the specified route.
- /// <list type="bullet">
- /// <item>When <paramref name="permanent"/> and <paramref name="preserveMethod"/> are set, sets the <see cref="StatusCodes.Status308PermanentRedirect"/> status code.</item>
- /// <item>When <paramref name="preserveMethod"/> is set, sets the <see cref="StatusCodes.Status307TemporaryRedirect"/> status code.</item>
- /// <item>When <paramref name="permanent"/> is set, sets the <see cref="StatusCodes.Status301MovedPermanently"/> status code.</item>
- /// <item>Otherwise, configures <see cref="StatusCodes.Status302Found"/>.</item>
- /// </list>
- /// </summary>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeValues">The parameters for a route.</param>
- /// <param name="permanent">Specifies whether the redirect should be permanent (301) or temporary (302).</param>
- /// <param name="preserveMethod">If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method.</param>
- /// <param name="fragment">The fragment to add to the URL.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult RedirectToRoute(string? routeName = null, object? routeValues = null, bool permanent = false, bool preserveMethod = false, string? fragment = null)
- => new RedirectToRouteResult(
- routeName: routeName,
- routeValues: routeValues,
- permanent: permanent,
- preserveMethod: preserveMethod,
- fragment: fragment);
-
- /// <summary>
- /// Creates a <see cref="StatusCodeResult"/> object by specifying a <paramref name="statusCode"/>.
- /// </summary>
- /// <param name="statusCode">The status code to set on the response.</param>
- /// <returns>The created <see cref="StatusCodeResult"/> object for the response.</returns>
- public static IResult StatusCode(int statusCode)
- => new StatusCodeResult(statusCode);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status404NotFound"/> response.
- /// </summary>
- /// <param name="value">The value to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult NotFound(object? value = null)
- => new NotFoundObjectResult(value);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status401Unauthorized"/> response.
- /// </summary>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Unauthorized()
- => new UnauthorizedResult();
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response.
- /// </summary>
- /// <param name="error">An error object to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult BadRequest(object? error = null)
- => new BadRequestObjectResult(error);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status409Conflict"/> response.
- /// </summary>
- /// <param name="error">An error object to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Conflict(object? error = null)
- => new ConflictObjectResult(error);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status204NoContent"/> response.
- /// </summary>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult NoContent()
- => new NoContentResult();
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status200OK"/> response.
- /// </summary>
- /// <param name="value">The value to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Ok(object? value = null)
- => new OkObjectResult(value);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status422UnprocessableEntity"/> response.
- /// </summary>
- /// <param name="error">An error object to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult UnprocessableEntity(object? error = null)
- => new UnprocessableEntityObjectResult(error);
-
- /// <summary>
- /// Produces a <see cref="ProblemDetails"/> response.
- /// </summary>
- /// <param name="statusCode">The value for <see cref="ProblemDetails.Status" />.</param>
- /// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param>
- /// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param>
- /// <param name="title">The value for <see cref="ProblemDetails.Title" />.</param>
- /// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param>
- /// <param name="extensions">The value for <see cref="ProblemDetails.Extensions" />.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Problem(
- string? detail = null,
- string? instance = null,
- int? statusCode = null,
- string? title = null,
- string? type = null,
- IDictionary<string, object?>? extensions = null)
+ return new ObjectResult(problemDetails)
{
- var problemDetails = new ProblemDetails
- {
- Detail = detail,
- Instance = instance,
- Status = statusCode,
- Title = title,
- Type = type,
- };
-
- if (extensions is not null)
- {
- foreach (var extension in extensions)
- {
- problemDetails.Extensions.Add(extension);
- }
- }
-
- return new ObjectResult(problemDetails)
- {
- ContentType = "application/problem+json",
- };
- }
+ ContentType = "application/problem+json",
+ };
+ }
- /// <summary>
- /// Produces a <see cref="ProblemDetails"/> response.
- /// </summary>
- /// <param name="problemDetails">The <see cref="ProblemDetails"/> object to produce a response from.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Problem(ProblemDetails problemDetails)
+ /// <summary>
+ /// Produces a <see cref="ProblemDetails"/> response.
+ /// </summary>
+ /// <param name="problemDetails">The <see cref="ProblemDetails"/> object to produce a response from.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Problem(ProblemDetails problemDetails)
+ {
+ return new ObjectResult(problemDetails)
{
- return new ObjectResult(problemDetails)
- {
- ContentType = "application/problem+json",
- };
- }
+ ContentType = "application/problem+json",
+ };
+ }
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response
- /// with a <see cref="HttpValidationProblemDetails"/> value.
- /// </summary>
- /// <param name="errors">One or more validation errors.</param>
- /// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param>
- /// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param>
- /// <param name="statusCode">The status code.</param>
- /// <param name="title">The value for <see cref="ProblemDetails.Title" />. Defaults to "One or more validation errors occurred."</param>
- /// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param>
- /// <param name="extensions">The value for <see cref="ProblemDetails.Extensions" />.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult ValidationProblem(
- IDictionary<string, string[]> errors,
- string? detail = null,
- string? instance = null,
- int? statusCode = null,
- string? title = null,
- string? type = null,
- IDictionary<string, object?>? extensions = null)
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status400BadRequest"/> response
+ /// with a <see cref="HttpValidationProblemDetails"/> value.
+ /// </summary>
+ /// <param name="errors">One or more validation errors.</param>
+ /// <param name="detail">The value for <see cref="ProblemDetails.Detail" />.</param>
+ /// <param name="instance">The value for <see cref="ProblemDetails.Instance" />.</param>
+ /// <param name="statusCode">The status code.</param>
+ /// <param name="title">The value for <see cref="ProblemDetails.Title" />. Defaults to "One or more validation errors occurred."</param>
+ /// <param name="type">The value for <see cref="ProblemDetails.Type" />.</param>
+ /// <param name="extensions">The value for <see cref="ProblemDetails.Extensions" />.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult ValidationProblem(
+ IDictionary<string, string[]> errors,
+ string? detail = null,
+ string? instance = null,
+ int? statusCode = null,
+ string? title = null,
+ string? type = null,
+ IDictionary<string, object?>? extensions = null)
+ {
+ var problemDetails = new HttpValidationProblemDetails(errors)
{
- var problemDetails = new HttpValidationProblemDetails(errors)
- {
- Detail = detail,
- Instance = instance,
- Type = type,
- Status = statusCode,
- };
-
- problemDetails.Title = title ?? problemDetails.Title;
+ Detail = detail,
+ Instance = instance,
+ Type = type,
+ Status = statusCode,
+ };
- if (extensions is not null)
- {
- foreach (var extension in extensions)
- {
- problemDetails.Extensions.Add(extension);
- }
- }
+ problemDetails.Title = title ?? problemDetails.Title;
- return new ObjectResult(problemDetails)
+ if (extensions is not null)
+ {
+ foreach (var extension in extensions)
{
- ContentType = "application/problem+json",
- };
+ problemDetails.Extensions.Add(extension);
+ }
}
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status201Created"/> response.
- /// </summary>
- /// <param name="uri">The URI at which the content has been created.</param>
- /// <param name="value">The value to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Created(string uri, object? value)
+ return new ObjectResult(problemDetails)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ ContentType = "application/problem+json",
+ };
+ }
- return new CreatedResult(uri, value);
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status201Created"/> response.
+ /// </summary>
+ /// <param name="uri">The URI at which the content has been created.</param>
+ /// <param name="value">The value to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Created(string uri, object? value)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
}
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status201Created"/> response.
- /// </summary>
- /// <param name="uri">The URI at which the content has been created.</param>
- /// <param name="value">The value to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Created(Uri uri, object? value)
- {
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ return new CreatedResult(uri, value);
+ }
- return new CreatedResult(uri, value);
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status201Created"/> response.
+ /// </summary>
+ /// <param name="uri">The URI at which the content has been created.</param>
+ /// <param name="value">The value to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Created(Uri uri, object? value)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
}
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status201Created"/> response.
- /// </summary>
- /// <param name="routeName">The name of the route to use for generating the URL.</param>
- /// <param name="routeValues">The route data to use for generating the URL.</param>
- /// <param name="value">The value to be included in the HTTP response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
- => new CreatedAtRouteResult(routeName, routeValues, value);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
- /// </summary>
- /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
- /// <param name="value">The optional content value to format in the response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult Accepted(string? uri = null, object? value = null)
- => new AcceptedResult(uri, value);
-
- /// <summary>
- /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
- /// </summary>
- /// <param name="routeName">The name of the route to use for generating the URL.</param>
- /// <param name="routeValues">The route data to use for generating the URL.</param>
- /// <param name="value">The optional content value to format in the response body.</param>
- /// <returns>The created <see cref="IResult"/> for the response.</returns>
- public static IResult AcceptedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
- => new AcceptedAtRouteResult(routeName, routeValues, value);
-
- /// <summary>
- /// Provides a container for external libraries to extend
- /// the default `Results` set with their own samples.
- /// </summary>
- public static IResultExtensions Extensions { get; } = new ResultExtensions();
+ return new CreatedResult(uri, value);
}
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status201Created"/> response.
+ /// </summary>
+ /// <param name="routeName">The name of the route to use for generating the URL.</param>
+ /// <param name="routeValues">The route data to use for generating the URL.</param>
+ /// <param name="value">The value to be included in the HTTP response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult CreatedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
+ => new CreatedAtRouteResult(routeName, routeValues, value);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
+ /// </summary>
+ /// <param name="uri">The URI with the location at which the status of requested content can be monitored.</param>
+ /// <param name="value">The optional content value to format in the response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult Accepted(string? uri = null, object? value = null)
+ => new AcceptedResult(uri, value);
+
+ /// <summary>
+ /// Produces a <see cref="StatusCodes.Status202Accepted"/> response.
+ /// </summary>
+ /// <param name="routeName">The name of the route to use for generating the URL.</param>
+ /// <param name="routeValues">The route data to use for generating the URL.</param>
+ /// <param name="value">The optional content value to format in the response body.</param>
+ /// <returns>The created <see cref="IResult"/> for the response.</returns>
+ public static IResult AcceptedAtRoute(string? routeName = null, object? routeValues = null, object? value = null)
+ => new AcceptedAtRouteResult(routeName, routeValues, value);
+
+ /// <summary>
+ /// Provides a container for external libraries to extend
+ /// the default `Results` set with their own samples.
+ /// </summary>
+ public static IResultExtensions Extensions { get; } = new ResultExtensions();
}
diff --git a/src/Http/Http.Results/src/SignInResult.cs b/src/Http/Http.Results/src/SignInResult.cs
index 3efa8919d5..9ce2b632d4 100644
--- a/src/Http/Http.Results/src/SignInResult.cs
+++ b/src/Http/Http.Results/src/SignInResult.cs
@@ -8,90 +8,89 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.SignInAsync"/>.
+/// </summary>
+internal sealed partial class SignInResult : IResult
{
/// <summary>
- /// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.SignInAsync"/>.
+ /// Initializes a new instance of <see cref="SignInResult"/> with the
+ /// default authentication scheme.
/// </summary>
- internal sealed partial class SignInResult : IResult
+ /// <param name="principal">The claims principal containing the user claims.</param>
+ public SignInResult(ClaimsPrincipal principal)
+ : this(authenticationScheme: null, principal, properties: null)
{
- /// <summary>
- /// Initializes a new instance of <see cref="SignInResult"/> with the
- /// default authentication scheme.
- /// </summary>
- /// <param name="principal">The claims principal containing the user claims.</param>
- public SignInResult(ClaimsPrincipal principal)
- : this(authenticationScheme: null, principal, properties: null)
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignInResult"/> with the
- /// specified authentication scheme.
- /// </summary>
- /// <param name="authenticationScheme">The authentication scheme to use when signing in the user.</param>
- /// <param name="principal">The claims principal containing the user claims.</param>
- public SignInResult(string? authenticationScheme, ClaimsPrincipal principal)
- : this(authenticationScheme, principal, properties: null)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignInResult"/> with the
+ /// specified authentication scheme.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication scheme to use when signing in the user.</param>
+ /// <param name="principal">The claims principal containing the user claims.</param>
+ public SignInResult(string? authenticationScheme, ClaimsPrincipal principal)
+ : this(authenticationScheme, principal, properties: null)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignInResult"/> with the
- /// default authentication scheme and <paramref name="properties"/>.
- /// </summary>
- /// <param name="principal">The claims principal containing the user claims.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
- public SignInResult(ClaimsPrincipal principal, AuthenticationProperties? properties)
- : this(authenticationScheme: null, principal, properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignInResult"/> with the
+ /// default authentication scheme and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="principal">The claims principal containing the user claims.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+ public SignInResult(ClaimsPrincipal principal, AuthenticationProperties? properties)
+ : this(authenticationScheme: null, principal, properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignInResult"/> with the
- /// specified authentication scheme and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationScheme">The authentication schemes to use when signing in the user.</param>
- /// <param name="principal">The claims principal containing the user claims.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
- public SignInResult(string? authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties? properties)
- {
- Principal = principal ?? throw new ArgumentNullException(nameof(principal));
- AuthenticationScheme = authenticationScheme;
- Properties = properties;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignInResult"/> with the
+ /// specified authentication scheme and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication schemes to use when signing in the user.</param>
+ /// <param name="principal">The claims principal containing the user claims.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-in operation.</param>
+ public SignInResult(string? authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties? properties)
+ {
+ Principal = principal ?? throw new ArgumentNullException(nameof(principal));
+ AuthenticationScheme = authenticationScheme;
+ Properties = properties;
+ }
- /// <summary>
- /// Gets or sets the authentication scheme that is used to perform the sign-in operation.
- /// </summary>
- public string? AuthenticationScheme { get; set; }
+ /// <summary>
+ /// Gets or sets the authentication scheme that is used to perform the sign-in operation.
+ /// </summary>
+ public string? AuthenticationScheme { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="ClaimsPrincipal"/> containing the user claims.
- /// </summary>
- public ClaimsPrincipal Principal { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="ClaimsPrincipal"/> containing the user claims.
+ /// </summary>
+ public ClaimsPrincipal Principal { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the sign-in operation.
- /// </summary>
- public AuthenticationProperties? Properties { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the sign-in operation.
+ /// </summary>
+ public AuthenticationProperties? Properties { get; set; }
- /// <inheritdoc />
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<SignInResult>>();
+ /// <inheritdoc />
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<SignInResult>>();
- Log.SignInResultExecuting(logger, AuthenticationScheme, Principal);
+ Log.SignInResultExecuting(logger, AuthenticationScheme, Principal);
- return httpContext.SignInAsync(AuthenticationScheme, Principal, Properties);
- }
+ return httpContext.SignInAsync(AuthenticationScheme, Principal, Properties);
+ }
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Information,
- "Executing SignInResult with authentication scheme ({Scheme}) and the following principal: {Principal}.",
- EventName = "SignInResultExecuting")]
- public static partial void SignInResultExecuting(ILogger logger, string? scheme, ClaimsPrincipal principal);
- }
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing SignInResult with authentication scheme ({Scheme}) and the following principal: {Principal}.",
+ EventName = "SignInResultExecuting")]
+ public static partial void SignInResultExecuting(ILogger logger, string? scheme, ClaimsPrincipal principal);
}
}
diff --git a/src/Http/Http.Results/src/SignOutResult.cs b/src/Http/Http.Results/src/SignOutResult.cs
index 6698aa71e4..7b99b5a1fa 100644
--- a/src/Http/Http.Results/src/SignOutResult.cs
+++ b/src/Http/Http.Results/src/SignOutResult.cs
@@ -9,119 +9,118 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.SignOutAsync"/>.
+/// </summary>
+internal sealed partial class SignOutResult : IResult
{
/// <summary>
- /// An <see cref="IResult"/> that on execution invokes <see cref="M:HttpContext.SignOutAsync"/>.
+ /// Initializes a new instance of <see cref="SignOutResult"/> with the default sign out scheme.
/// </summary>
- internal sealed partial class SignOutResult : IResult
+ public SignOutResult()
+ : this(Array.Empty<string>())
{
- /// <summary>
- /// Initializes a new instance of <see cref="SignOutResult"/> with the default sign out scheme.
- /// </summary>
- public SignOutResult()
- : this(Array.Empty<string>())
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignOutResult"/> with the default sign out scheme.
- /// specified authentication scheme and <paramref name="properties"/>.
- /// </summary>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
- public SignOutResult(AuthenticationProperties properties)
- : this(Array.Empty<string>(), properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignOutResult"/> with the default sign out scheme.
+ /// specified authentication scheme and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+ public SignOutResult(AuthenticationProperties properties)
+ : this(Array.Empty<string>(), properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignOutResult"/> with the
- /// specified authentication scheme.
- /// </summary>
- /// <param name="authenticationScheme">The authentication scheme to use when signing out the user.</param>
- public SignOutResult(string authenticationScheme)
- : this(new[] { authenticationScheme })
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignOutResult"/> with the
+ /// specified authentication scheme.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication scheme to use when signing out the user.</param>
+ public SignOutResult(string authenticationScheme)
+ : this(new[] { authenticationScheme })
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignOutResult"/> with the
- /// specified authentication schemes.
- /// </summary>
- /// <param name="authenticationSchemes">The authentication schemes to use when signing out the user.</param>
- public SignOutResult(IList<string> authenticationSchemes)
- : this(authenticationSchemes, properties: null)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignOutResult"/> with the
+ /// specified authentication schemes.
+ /// </summary>
+ /// <param name="authenticationSchemes">The authentication schemes to use when signing out the user.</param>
+ public SignOutResult(IList<string> authenticationSchemes)
+ : this(authenticationSchemes, properties: null)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignOutResult"/> with the
- /// specified authentication scheme and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationScheme">The authentication schemes to use when signing out the user.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
- public SignOutResult(string authenticationScheme, AuthenticationProperties? properties)
- : this(new[] { authenticationScheme }, properties)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignOutResult"/> with the
+ /// specified authentication scheme and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationScheme">The authentication schemes to use when signing out the user.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+ public SignOutResult(string authenticationScheme, AuthenticationProperties? properties)
+ : this(new[] { authenticationScheme }, properties)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="SignOutResult"/> with the
- /// specified authentication schemes and <paramref name="properties"/>.
- /// </summary>
- /// <param name="authenticationSchemes">The authentication scheme to use when signing out the user.</param>
- /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
- public SignOutResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
- {
- AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
- Properties = properties;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="SignOutResult"/> with the
+ /// specified authentication schemes and <paramref name="properties"/>.
+ /// </summary>
+ /// <param name="authenticationSchemes">The authentication scheme to use when signing out the user.</param>
+ /// <param name="properties"><see cref="AuthenticationProperties"/> used to perform the sign-out operation.</param>
+ public SignOutResult(IList<string> authenticationSchemes, AuthenticationProperties? properties)
+ {
+ AuthenticationSchemes = authenticationSchemes ?? throw new ArgumentNullException(nameof(authenticationSchemes));
+ Properties = properties;
+ }
- /// <summary>
- /// Gets or sets the authentication schemes that are challenged.
- /// </summary>
- public IList<string> AuthenticationSchemes { get; init; }
+ /// <summary>
+ /// Gets or sets the authentication schemes that are challenged.
+ /// </summary>
+ public IList<string> AuthenticationSchemes { get; init; }
- /// <summary>
- /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the sign-out operation.
- /// </summary>
- public AuthenticationProperties? Properties { get; init; }
+ /// <summary>
+ /// Gets or sets the <see cref="AuthenticationProperties"/> used to perform the sign-out operation.
+ /// </summary>
+ public AuthenticationProperties? Properties { get; init; }
- /// <inheritdoc />
- public async Task ExecuteAsync(HttpContext httpContext)
- {
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<SignOutResult>>();
+ /// <inheritdoc />
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<SignOutResult>>();
- Log.SignOutResultExecuting(logger, AuthenticationSchemes);
+ Log.SignOutResultExecuting(logger, AuthenticationSchemes);
- if (AuthenticationSchemes.Count == 0)
- {
- await httpContext.SignOutAsync(Properties);
- }
- else
+ if (AuthenticationSchemes.Count == 0)
+ {
+ await httpContext.SignOutAsync(Properties);
+ }
+ else
+ {
+ for (var i = 0; i < AuthenticationSchemes.Count; i++)
{
- for (var i = 0; i < AuthenticationSchemes.Count; i++)
- {
- await httpContext.SignOutAsync(AuthenticationSchemes[i], Properties);
- }
+ await httpContext.SignOutAsync(AuthenticationSchemes[i], Properties);
}
}
+ }
- private static partial class Log
+ private static partial class Log
+ {
+ public static void SignOutResultExecuting(ILogger logger, IList<string> authenticationSchemes)
{
- public static void SignOutResultExecuting(ILogger logger, IList<string> authenticationSchemes)
+ if (logger.IsEnabled(LogLevel.Information))
{
- if (logger.IsEnabled(LogLevel.Information))
- {
- SignOutResultExecuting(logger, authenticationSchemes.ToArray());
- }
+ SignOutResultExecuting(logger, authenticationSchemes.ToArray());
}
-
- [LoggerMessage(1, LogLevel.Information,
- "Executing SignOutResult with authentication schemes ({Schemes}).",
- EventName = "SignOutResultExecuting",
- SkipEnabledCheck = true)]
- private static partial void SignOutResultExecuting(ILogger logger, string[] schemes);
}
+
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing SignOutResult with authentication schemes ({Schemes}).",
+ EventName = "SignOutResultExecuting",
+ SkipEnabledCheck = true)]
+ private static partial void SignOutResultExecuting(ILogger logger, string[] schemes);
}
}
diff --git a/src/Http/Http.Results/src/StatusCodeResult.cs b/src/Http/Http.Results/src/StatusCodeResult.cs
index 0a7c171f51..58d1461329 100644
--- a/src/Http/Http.Results/src/StatusCodeResult.cs
+++ b/src/Http/Http.Results/src/StatusCodeResult.cs
@@ -5,47 +5,46 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal partial class StatusCodeResult : IResult
{
- internal partial class StatusCodeResult : IResult
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StatusCodeResult"/> class
+ /// with the given <paramref name="statusCode"/>.
+ /// </summary>
+ /// <param name="statusCode">The HTTP status code of the response.</param>
+ public StatusCodeResult(int statusCode)
+ {
+ StatusCode = statusCode;
+ }
+
+ /// <summary>
+ /// Gets the HTTP status code.
+ /// </summary>
+ public int StatusCode { get; }
+
+ /// <summary>
+ /// Sets the status code on the HTTP response.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
+ /// <returns>A task that represents the asynchronous execute operation.</returns>
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var factory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
+ var logger = factory.CreateLogger(GetType());
+
+ Log.StatusCodeResultExecuting(logger, StatusCode);
+
+ httpContext.Response.StatusCode = StatusCode;
+ return Task.CompletedTask;
+ }
+
+ private static partial class Log
{
- /// <summary>
- /// Initializes a new instance of the <see cref="StatusCodeResult"/> class
- /// with the given <paramref name="statusCode"/>.
- /// </summary>
- /// <param name="statusCode">The HTTP status code of the response.</param>
- public StatusCodeResult(int statusCode)
- {
- StatusCode = statusCode;
- }
-
- /// <summary>
- /// Gets the HTTP status code.
- /// </summary>
- public int StatusCode { get; }
-
- /// <summary>
- /// Sets the status code on the HTTP response.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> for the current request.</param>
- /// <returns>A task that represents the asynchronous execute operation.</returns>
- public Task ExecuteAsync(HttpContext httpContext)
- {
- var factory = httpContext.RequestServices.GetRequiredService<ILoggerFactory>();
- var logger = factory.CreateLogger(GetType());
-
- Log.StatusCodeResultExecuting(logger, StatusCode);
-
- httpContext.Response.StatusCode = StatusCode;
- return Task.CompletedTask;
- }
-
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Information,
- "Executing StatusCodeResult, setting HTTP status code {StatusCode}.",
- EventName = "StatusCodeResultExecuting")]
- public static partial void StatusCodeResultExecuting(ILogger logger, int statusCode);
- }
+ [LoggerMessage(1, LogLevel.Information,
+ "Executing StatusCodeResult, setting HTTP status code {StatusCode}.",
+ EventName = "StatusCodeResultExecuting")]
+ public static partial void StatusCodeResultExecuting(ILogger logger, int statusCode);
}
}
diff --git a/src/Http/Http.Results/src/UnauthorizedResult.cs b/src/Http/Http.Results/src/UnauthorizedResult.cs
index fd2135362e..28ec97eaf4 100644
--- a/src/Http/Http.Results/src/UnauthorizedResult.cs
+++ b/src/Http/Http.Results/src/UnauthorizedResult.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class UnauthorizedResult : StatusCodeResult
{
- internal sealed class UnauthorizedResult : StatusCodeResult
+ public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized)
{
- public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
index 3be9fa3608..fd7b456eb8 100644
--- a/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
+++ b/src/Http/Http.Results/src/UnprocessableEntityObjectResult.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class UnprocessableEntityObjectResult : ObjectResult
{
- internal sealed class UnprocessableEntityObjectResult : ObjectResult
+ public UnprocessableEntityObjectResult(object? error)
+ : base(error, StatusCodes.Status422UnprocessableEntity)
{
- public UnprocessableEntityObjectResult(object? error)
- : base(error, StatusCodes.Status422UnprocessableEntity)
- {
- }
}
}
diff --git a/src/Http/Http.Results/src/VirtualFileResult.cs b/src/Http/Http.Results/src/VirtualFileResult.cs
index b5cb0bc9e4..2e83b13625 100644
--- a/src/Http/Http.Results/src/VirtualFileResult.cs
+++ b/src/Http/Http.Results/src/VirtualFileResult.cs
@@ -8,106 +8,105 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+/// <summary>
+/// A <see cref="FileResult" /> that on execution writes the file specified using a virtual path to the response
+/// using mechanisms provided by the host.
+/// </summary>
+internal sealed class VirtualFileResult : FileResult, IResult
{
+ private string _fileName;
+
/// <summary>
- /// A <see cref="FileResult" /> that on execution writes the file specified using a virtual path to the response
- /// using mechanisms provided by the host.
+ /// Creates a new <see cref="VirtualFileResult"/> instance with the provided <paramref name="fileName"/>
+ /// and the provided <paramref name="contentType"/>.
/// </summary>
- internal sealed class VirtualFileResult : FileResult, IResult
+ /// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
+ /// <param name="contentType">The Content-Type header of the response.</param>
+ public VirtualFileResult(string fileName, string? contentType)
+ : base(contentType)
{
- private string _fileName;
-
- /// <summary>
- /// Creates a new <see cref="VirtualFileResult"/> instance with the provided <paramref name="fileName"/>
- /// and the provided <paramref name="contentType"/>.
- /// </summary>
- /// <param name="fileName">The path to the file. The path must be relative/virtual.</param>
- /// <param name="contentType">The Content-Type header of the response.</param>
- public VirtualFileResult(string fileName, string? contentType)
- : base(contentType)
+ FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
+ }
+
+ /// <summary>
+ /// Gets or sets the path to the file that will be sent back as the response.
+ /// </summary>
+ public string FileName
+ {
+ get => _fileName;
+ [MemberNotNull(nameof(_fileName))]
+ set => _fileName = value ?? throw new ArgumentNullException(nameof(value));
+ }
+
+ /// <inheritdoc/>
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var hostingEnvironment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
+ var logger = httpContext.RequestServices.GetRequiredService<ILogger<VirtualFileResult>>();
+
+ var fileInfo = GetFileInformation(hostingEnvironment.WebRootFileProvider);
+ if (!fileInfo.Exists)
{
- FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
+ throw new FileNotFoundException($"Could not find file: {FileName}.", FileName);
}
- /// <summary>
- /// Gets or sets the path to the file that will be sent back as the response.
- /// </summary>
- public string FileName
+ Log.ExecutingFileResult(logger, this);
+
+ var lastModified = LastModified ?? fileInfo.LastModified;
+ var fileResultInfo = new FileResultInfo
+ {
+ ContentType = ContentType,
+ FileDownloadName = FileDownloadName,
+ EnableRangeProcessing = EnableRangeProcessing,
+ EntityTag = EntityTag,
+ LastModified = lastModified,
+ };
+
+ var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
+ httpContext,
+ fileResultInfo,
+ fileInfo.Length,
+ EnableRangeProcessing,
+ lastModified,
+ EntityTag,
+ logger);
+
+ if (!serveBody)
{
- get => _fileName;
- [MemberNotNull(nameof(_fileName))]
- set => _fileName = value ?? throw new ArgumentNullException(nameof(value));
+ return Task.CompletedTask;
}
- /// <inheritdoc/>
- public Task ExecuteAsync(HttpContext httpContext)
+ if (range != null)
{
- var hostingEnvironment = httpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
- var logger = httpContext.RequestServices.GetRequiredService<ILogger<VirtualFileResult>>();
-
- var fileInfo = GetFileInformation(hostingEnvironment.WebRootFileProvider);
- if (!fileInfo.Exists)
- {
- throw new FileNotFoundException($"Could not find file: {FileName}.", FileName);
- }
-
- Log.ExecutingFileResult(logger, this);
-
- var lastModified = LastModified ?? fileInfo.LastModified;
- var fileResultInfo = new FileResultInfo
- {
- ContentType = ContentType,
- FileDownloadName = FileDownloadName,
- EnableRangeProcessing = EnableRangeProcessing,
- EntityTag = EntityTag,
- LastModified = lastModified,
- };
-
- var (range, rangeLength, serveBody) = FileResultHelper.SetHeadersAndLog(
- httpContext,
- fileResultInfo,
- fileInfo.Length,
- EnableRangeProcessing,
- lastModified,
- EntityTag,
- logger);
-
- if (!serveBody)
- {
- return Task.CompletedTask;
- }
-
- if (range != null)
- {
- FileResultHelper.Log.WritingRangeToBody(logger);
- }
-
- var response = httpContext.Response;
- var offset = 0L;
- var count = (long?)null;
- if (range != null)
- {
- offset = range.From ?? 0L;
- count = rangeLength;
- }
-
- return response.SendFileAsync(
- fileInfo,
- offset,
- count);
+ FileResultHelper.Log.WritingRangeToBody(logger);
}
- internal IFileInfo GetFileInformation(IFileProvider fileProvider)
+ var response = httpContext.Response;
+ var offset = 0L;
+ var count = (long?)null;
+ if (range != null)
{
- var normalizedPath = FileName;
- if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
- {
- normalizedPath = normalizedPath.Substring(1);
- }
-
- var fileInfo = fileProvider.GetFileInfo(normalizedPath);
- return fileInfo;
+ offset = range.From ?? 0L;
+ count = rangeLength;
}
+
+ return response.SendFileAsync(
+ fileInfo,
+ offset,
+ count);
+ }
+
+ internal IFileInfo GetFileInformation(IFileProvider fileProvider)
+ {
+ var normalizedPath = FileName;
+ if (normalizedPath.StartsWith("~", StringComparison.Ordinal))
+ {
+ normalizedPath = normalizedPath.Substring(1);
+ }
+
+ var fileInfo = fileProvider.GetFileInfo(normalizedPath);
+ return fileInfo;
}
}
diff --git a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
index c2f1e31339..f9626d7df5 100644
--- a/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
+++ b/src/Http/Http.Results/test/AcceptedAtRouteResultTests.cs
@@ -12,43 +12,43 @@ using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class AcceptedAtRouteResultTests
{
- public class AcceptedAtRouteResultTests
+ [Fact]
+ public async Task ExecuteResultAsync_FormatsData()
{
- [Fact]
- public async Task ExecuteResultAsync_FormatsData()
- {
- // Arrange
- var url = "testAction";
- var linkGenerator = new TestLinkGenerator { Url = url };
- var httpContext = GetHttpContext(linkGenerator);
- var stream = new MemoryStream();
- httpContext.Response.Body = stream;
+ // Arrange
+ var url = "testAction";
+ var linkGenerator = new TestLinkGenerator { Url = url };
+ var httpContext = GetHttpContext(linkGenerator);
+ var stream = new MemoryStream();
+ httpContext.Response.Body = stream;
- var routeValues = new RouteValueDictionary(new Dictionary<string, string>()
+ var routeValues = new RouteValueDictionary(new Dictionary<string, string>()
{
{ "test", "case" },
{ "sample", "route" }
});
- // Act
- var result = new AcceptedAtRouteResult(
- routeName: "sample",
- routeValues: routeValues,
- value: "Hello world");
- await result.ExecuteAsync(httpContext);
+ // Act
+ var result = new AcceptedAtRouteResult(
+ routeName: "sample",
+ routeValues: routeValues,
+ value: "Hello world");
+ await result.ExecuteAsync(httpContext);
- // Assert
- var response = Encoding.UTF8.GetString(stream.ToArray());
- Assert.Equal("\"Hello world\"", response);
- }
+ // Assert
+ var response = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal("\"Hello world\"", response);
+ }
- public static TheoryData<object> AcceptedAtRouteData
+ public static TheoryData<object> AcceptedAtRouteData
+ {
+ get
{
- get
- {
- return new TheoryData<object>
+ return new TheoryData<object>
{
null,
new Dictionary<string, string>()
@@ -62,59 +62,58 @@ namespace Microsoft.AspNetCore.Http.Result
{ "sample", "route" }
}),
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(AcceptedAtRouteData))]
- public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader(object values)
- {
- // Arrange
- var expectedUrl = "testAction";
- var linkGenerator = new TestLinkGenerator { Url = expectedUrl };
- var httpContext = GetHttpContext(linkGenerator);
+ [Theory]
+ [MemberData(nameof(AcceptedAtRouteData))]
+ public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader(object values)
+ {
+ // Arrange
+ var expectedUrl = "testAction";
+ var linkGenerator = new TestLinkGenerator { Url = expectedUrl };
+ var httpContext = GetHttpContext(linkGenerator);
- // Act
- var result = new AcceptedAtRouteResult(routeValues: values, value: null);
- await result.ExecuteAsync(httpContext);
+ // Act
+ var result = new AcceptedAtRouteResult(routeValues: values, value: null);
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
- Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
- [Fact]
- public async Task ExecuteResultAsync_ThrowsIfRouteUrlIsNull()
- {
- // Arrange
- var linkGenerator = new TestLinkGenerator();
- var httpContext = GetHttpContext(linkGenerator);
+ [Fact]
+ public async Task ExecuteResultAsync_ThrowsIfRouteUrlIsNull()
+ {
+ // Arrange
+ var linkGenerator = new TestLinkGenerator();
+ var httpContext = GetHttpContext(linkGenerator);
- // Act
- var result = new AcceptedAtRouteResult(
- routeName: null,
- routeValues: new Dictionary<string, object>(),
- value: null);
+ // Act
+ var result = new AcceptedAtRouteResult(
+ routeName: null,
+ routeValues: new Dictionary<string, object>(),
+ value: null);
- // Assert
- await ExceptionAssert.ThrowsAsync<InvalidOperationException>(() =>
- result.ExecuteAsync(httpContext),
- "No route matches the supplied values.");
- }
+ // Assert
+ await ExceptionAssert.ThrowsAsync<InvalidOperationException>(() =>
+ result.ExecuteAsync(httpContext),
+ "No route matches the supplied values.");
+ }
- private static HttpContext GetHttpContext(LinkGenerator linkGenerator)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices(linkGenerator);
- return httpContext;
- }
+ private static HttpContext GetHttpContext(LinkGenerator linkGenerator)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices(linkGenerator);
+ return httpContext;
+ }
- private static IServiceProvider CreateServices(LinkGenerator linkGenerator)
- {
- var services = new ServiceCollection();
- services.AddLogging();
- services.AddSingleton(linkGenerator);
- return services.BuildServiceProvider();
- }
+ private static IServiceProvider CreateServices(LinkGenerator linkGenerator)
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddSingleton(linkGenerator);
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/AcceptedResultTests.cs b/src/Http/Http.Results/test/AcceptedResultTests.cs
index bb54726153..3074a1c50f 100644
--- a/src/Http/Http.Results/test/AcceptedResultTests.cs
+++ b/src/Http/Http.Results/test/AcceptedResultTests.cs
@@ -8,54 +8,53 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class AcceptedResultTests
{
- public class AcceptedResultTests
+ [Fact]
+ public async Task ExecuteResultAsync_FormatsData()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var stream = new MemoryStream();
+ httpContext.Response.Body = stream;
+ // Act
+ var result = new AcceptedResult("my-location", value: "Hello world");
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ var response = Encoding.UTF8.GetString(stream.ToArray());
+ Assert.Equal("\"Hello world\"", response);
+ }
+
+ [Fact]
+ public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
+ {
+ // Arrange
+ var expectedUrl = "testAction";
+ var httpContext = GetHttpContext();
+
+ // Act
+ var result = new AcceptedResult(expectedUrl, value: "some-value");
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
+
+ private static HttpContext GetHttpContext()
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices();
+ return httpContext;
+ }
+
+ private static IServiceProvider CreateServices()
{
- [Fact]
- public async Task ExecuteResultAsync_FormatsData()
- {
- // Arrange
- var httpContext = GetHttpContext();
- var stream = new MemoryStream();
- httpContext.Response.Body = stream;
- // Act
- var result = new AcceptedResult("my-location", value: "Hello world");
- await result.ExecuteAsync(httpContext);
-
- // Assert
- var response = Encoding.UTF8.GetString(stream.ToArray());
- Assert.Equal("\"Hello world\"", response);
- }
-
- [Fact]
- public async Task ExecuteResultAsync_SetsStatusCodeAndLocationHeader()
- {
- // Arrange
- var expectedUrl = "testAction";
- var httpContext = GetHttpContext();
-
- // Act
- var result = new AcceptedResult(expectedUrl, value: "some-value");
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(StatusCodes.Status202Accepted, httpContext.Response.StatusCode);
- Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
- }
-
- private static HttpContext GetHttpContext()
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices();
- return httpContext;
- }
-
- private static IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddLogging();
- return services.BuildServiceProvider();
- }
+ var services = new ServiceCollection();
+ services.AddLogging();
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/BadRequestObjectResultTests.cs b/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
index 7c3c57be31..ccec17c255 100644
--- a/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
+++ b/src/Http/Http.Results/test/BadRequestObjectResultTests.cs
@@ -3,20 +3,19 @@
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class BadRequestObjectResultTests
{
- public class BadRequestObjectResultTests
+ [Fact]
+ public void BadRequestObjectResult_SetsStatusCodeAndValue()
{
- [Fact]
- public void BadRequestObjectResult_SetsStatusCodeAndValue()
- {
- // Arrange & Act
- var obj = new object();
- var badRequestObjectResult = new BadRequestObjectResult(obj);
+ // Arrange & Act
+ var obj = new object();
+ var badRequestObjectResult = new BadRequestObjectResult(obj);
- // Assert
- Assert.Equal(StatusCodes.Status400BadRequest, badRequestObjectResult.StatusCode);
- Assert.Equal(obj, badRequestObjectResult.Value);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status400BadRequest, badRequestObjectResult.StatusCode);
+ Assert.Equal(obj, badRequestObjectResult.Value);
}
}
diff --git a/src/Http/Http.Results/test/ChallengeResultTest.cs b/src/Http/Http.Results/test/ChallengeResultTest.cs
index 672d3b34ae..3bed9c0378 100644
--- a/src/Http/Http.Results/test/ChallengeResultTest.cs
+++ b/src/Http/Http.Results/test/ChallengeResultTest.cs
@@ -9,54 +9,53 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class ChallengeResultTest
{
- public class ChallengeResultTest
+ [Fact]
+ public async Task ChallengeResult_ExecuteAsync()
+ {
+ // Arrange
+ var result = new ChallengeResult("", null);
+ var auth = new Mock<IAuthenticationService>();
+ var httpContext = GetHttpContext(auth);
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ auth.Verify(c => c.ChallengeAsync(httpContext, "", null), Times.Exactly(1));
+ }
+
+ [Fact]
+ public async Task ChallengeResult_ExecuteAsync_NoSchemes()
+ {
+ // Arrange
+ var result = new ChallengeResult(new string[] { }, null);
+ var auth = new Mock<IAuthenticationService>();
+ var httpContext = GetHttpContext(auth);
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ auth.Verify(c => c.ChallengeAsync(httpContext, null, null), Times.Exactly(1));
+ }
+
+ private static DefaultHttpContext GetHttpContext(Mock<IAuthenticationService> auth)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices()
+ .AddSingleton(auth.Object)
+ .BuildServiceProvider();
+ return httpContext;
+ }
+
+ private static IServiceCollection CreateServices()
{
- [Fact]
- public async Task ChallengeResult_ExecuteAsync()
- {
- // Arrange
- var result = new ChallengeResult("", null);
- var auth = new Mock<IAuthenticationService>();
- var httpContext = GetHttpContext(auth);
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- auth.Verify(c => c.ChallengeAsync(httpContext, "", null), Times.Exactly(1));
- }
-
- [Fact]
- public async Task ChallengeResult_ExecuteAsync_NoSchemes()
- {
- // Arrange
- var result = new ChallengeResult(new string[] { }, null);
- var auth = new Mock<IAuthenticationService>();
- var httpContext = GetHttpContext(auth);
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- auth.Verify(c => c.ChallengeAsync(httpContext, null, null), Times.Exactly(1));
- }
-
- private static DefaultHttpContext GetHttpContext(Mock<IAuthenticationService> auth)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices()
- .AddSingleton(auth.Object)
- .BuildServiceProvider();
- return httpContext;
- }
-
- private static IServiceCollection CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
- return services;
- }
+ var services = new ServiceCollection();
+ services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ return services;
}
}
diff --git a/src/Http/Http.Results/test/ConflictObjectResultTest.cs b/src/Http/Http.Results/test/ConflictObjectResultTest.cs
index fba55eecce..1b2c573102 100644
--- a/src/Http/Http.Results/test/ConflictObjectResultTest.cs
+++ b/src/Http/Http.Results/test/ConflictObjectResultTest.cs
@@ -3,20 +3,19 @@
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class ConflictObjectResultTest
{
- public class ConflictObjectResultTest
+ [Fact]
+ public void ConflictObjectResult_SetsStatusCodeAndValue()
{
- [Fact]
- public void ConflictObjectResult_SetsStatusCodeAndValue()
- {
- // Arrange & Act
- var obj = new object();
- var conflictObjectResult = new ConflictObjectResult(obj);
+ // Arrange & Act
+ var obj = new object();
+ var conflictObjectResult = new ConflictObjectResult(obj);
- // Assert
- Assert.Equal(StatusCodes.Status409Conflict, conflictObjectResult.StatusCode);
- Assert.Equal(obj, conflictObjectResult.Value);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status409Conflict, conflictObjectResult.StatusCode);
+ Assert.Equal(obj, conflictObjectResult.Value);
}
}
diff --git a/src/Http/Http.Results/test/ContentResultTest.cs b/src/Http/Http.Results/test/ContentResultTest.cs
index 1f50e0001a..09e0ada15d 100644
--- a/src/Http/Http.Results/test/ContentResultTest.cs
+++ b/src/Http/Http.Results/test/ContentResultTest.cs
@@ -10,37 +10,37 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Net.Http.Headers;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class ContentResultTest
{
- public class ContentResultTest
+ [Fact]
+ public async Task ContentResult_ExecuteAsync_Response_NullContent_SetsContentTypeAndEncoding()
{
- [Fact]
- public async Task ContentResult_ExecuteAsync_Response_NullContent_SetsContentTypeAndEncoding()
+ // Arrange
+ var contentResult = new ContentResult
{
- // Arrange
- var contentResult = new ContentResult
+ Content = null,
+ ContentType = new MediaTypeHeaderValue("text/plain")
{
- Content = null,
- ContentType = new MediaTypeHeaderValue("text/plain")
- {
- Encoding = Encoding.Unicode
- }.ToString()
- };
- var httpContext = GetHttpContext();
+ Encoding = Encoding.Unicode
+ }.ToString()
+ };
+ var httpContext = GetHttpContext();
- // Act
- await contentResult.ExecuteAsync(httpContext);
+ // Act
+ await contentResult.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
- }
+ // Assert
+ Assert.Equal("text/plain; charset=utf-16", httpContext.Response.ContentType);
+ }
- public static TheoryData<MediaTypeHeaderValue, string, string, string, byte[]> ContentResultContentTypeData
+ public static TheoryData<MediaTypeHeaderValue, string, string, string, byte[]> ContentResultContentTypeData
+ {
+ get
{
- get
- {
- // contentType, content, responseContentType, expectedContentType, expectedData
- return new TheoryData<MediaTypeHeaderValue, string, string, string, byte[]>
+ // contentType, content, responseContentType, expectedContentType, expectedData
+ return new TheoryData<MediaTypeHeaderValue, string, string, string, byte[]>
{
{
null,
@@ -99,54 +99,53 @@ namespace Microsoft.AspNetCore.Http.Result
new byte[] { 97, 98, 99, 100 }
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(ContentResultContentTypeData))]
- public async Task ContentResult_ExecuteAsync_SetContentTypeAndEncoding_OnResponse(
- MediaTypeHeaderValue contentType,
- string content,
- string responseContentType,
- string expectedContentType,
- byte[] expectedContentData)
+ [Theory]
+ [MemberData(nameof(ContentResultContentTypeData))]
+ public async Task ContentResult_ExecuteAsync_SetContentTypeAndEncoding_OnResponse(
+ MediaTypeHeaderValue contentType,
+ string content,
+ string responseContentType,
+ string expectedContentType,
+ byte[] expectedContentData)
+ {
+ // Arrange
+ var contentResult = new ContentResult
{
- // Arrange
- var contentResult = new ContentResult
- {
- Content = content,
- ContentType = contentType?.ToString()
- };
- var httpContext = GetHttpContext();
- var memoryStream = new MemoryStream();
- httpContext.Response.Body = memoryStream;
- httpContext.Response.ContentType = responseContentType;
+ Content = content,
+ ContentType = contentType?.ToString()
+ };
+ var httpContext = GetHttpContext();
+ var memoryStream = new MemoryStream();
+ httpContext.Response.Body = memoryStream;
+ httpContext.Response.ContentType = responseContentType;
- // Act
- await contentResult.ExecuteAsync(httpContext);
+ // Act
+ await contentResult.ExecuteAsync(httpContext);
- // Assert
- var finalResponseContentType = httpContext.Response.ContentType;
- Assert.Equal(expectedContentType, finalResponseContentType);
- Assert.Equal(expectedContentData, memoryStream.ToArray());
- Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
- }
+ // Assert
+ var finalResponseContentType = httpContext.Response.ContentType;
+ Assert.Equal(expectedContentType, finalResponseContentType);
+ Assert.Equal(expectedContentData, memoryStream.ToArray());
+ Assert.Equal(expectedContentData.Length, httpContext.Response.ContentLength);
+ }
- private static IServiceCollection CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
- return services;
- }
+ private static IServiceCollection CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ return services;
+ }
- private static HttpContext GetHttpContext()
- {
- var services = CreateServices();
+ private static HttpContext GetHttpContext()
+ {
+ var services = CreateServices();
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = services.BuildServiceProvider();
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = services.BuildServiceProvider();
- return httpContext;
- }
+ return httpContext;
}
}
diff --git a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
index 7f4cd28dd9..7fdedb717f 100644
--- a/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
+++ b/src/Http/Http.Results/test/CreatedAtRouteResultTests.cs
@@ -12,82 +12,81 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public partial class CreatedAtRouteResultTests
{
- public partial class CreatedAtRouteResultTests
+ public static IEnumerable<object[]> CreatedAtRouteData
{
- public static IEnumerable<object[]> CreatedAtRouteData
+ get
{
- get
- {
- yield return new object[] { null };
- yield return
- new object[] {
+ yield return new object[] { null };
+ yield return
+ new object[] {
new Dictionary<string, string>() { { "hello", "world" } }
- };
- yield return
- new object[] {
+ };
+ yield return
+ new object[] {
new RouteValueDictionary(new Dictionary<string, string>() {
{ "test", "case" },
{ "sample", "route" }
})
- };
- }
+ };
}
+ }
- [Theory]
- [MemberData(nameof(CreatedAtRouteData))]
- public async Task CreatedAtRouteResult_ReturnsStatusCode_SetsLocationHeader(object values)
- {
- // Arrange
- var expectedUrl = "testAction";
- var httpContext = GetHttpContext(expectedUrl);
+ [Theory]
+ [MemberData(nameof(CreatedAtRouteData))]
+ public async Task CreatedAtRouteResult_ReturnsStatusCode_SetsLocationHeader(object values)
+ {
+ // Arrange
+ var expectedUrl = "testAction";
+ var httpContext = GetHttpContext(expectedUrl);
- // Act
- var result = new CreatedAtRouteResult(routeName: null, routeValues: values, value: null);
- await result.ExecuteAsync(httpContext);
+ // Act
+ var result = new CreatedAtRouteResult(routeName: null, routeValues: values, value: null);
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
- Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
- [Fact]
- public async Task CreatedAtRouteResult_ThrowsOnNullUrl()
- {
- // Arrange
- var httpContext = GetHttpContext(expectedUrl: null);
+ [Fact]
+ public async Task CreatedAtRouteResult_ThrowsOnNullUrl()
+ {
+ // Arrange
+ var httpContext = GetHttpContext(expectedUrl: null);
- var result = new CreatedAtRouteResult(
- routeName: null,
- routeValues: new Dictionary<string, object>(),
- value: null);
+ var result = new CreatedAtRouteResult(
+ routeName: null,
+ routeValues: new Dictionary<string, object>(),
+ value: null);
- // Act & Assert
- await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
- async () => await result.ExecuteAsync(httpContext),
- "No route matches the supplied values.");
- }
+ // Act & Assert
+ await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
+ async () => await result.ExecuteAsync(httpContext),
+ "No route matches the supplied values.");
+ }
- private static HttpContext GetHttpContext(string expectedUrl)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.PathBase = new PathString("");
- httpContext.Response.Body = new MemoryStream();
- httpContext.RequestServices = CreateServices(expectedUrl);
- return httpContext;
- }
+ private static HttpContext GetHttpContext(string expectedUrl)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.PathBase = new PathString("");
+ httpContext.Response.Body = new MemoryStream();
+ httpContext.RequestServices = CreateServices(expectedUrl);
+ return httpContext;
+ }
- private static IServiceProvider CreateServices(string expectedUrl)
+ private static IServiceProvider CreateServices(string expectedUrl)
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+ services.AddSingleton<LinkGenerator>(new TestLinkGenerator
{
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
- services.AddSingleton<LinkGenerator>(new TestLinkGenerator
- {
- Url = expectedUrl
- });
+ Url = expectedUrl
+ });
- return services.BuildServiceProvider();
- }
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/CreatedResultTest.cs b/src/Http/Http.Results/test/CreatedResultTest.cs
index 28f4868e8f..06bc459d36 100644
--- a/src/Http/Http.Results/test/CreatedResultTest.cs
+++ b/src/Http/Http.Results/test/CreatedResultTest.cs
@@ -9,71 +9,70 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class CreatedResultTests
{
- public class CreatedResultTests
+ [Fact]
+ public void CreatedResult_SetsLocation()
{
- [Fact]
- public void CreatedResult_SetsLocation()
- {
- // Arrange
- var location = "http://test/location";
+ // Arrange
+ var location = "http://test/location";
- // Act
- var result = new CreatedResult(location, "testInput");
+ // Act
+ var result = new CreatedResult(location, "testInput");
- // Assert
- Assert.Same(location, result.Location);
- }
+ // Assert
+ Assert.Same(location, result.Location);
+ }
- [Fact]
- public async Task CreatedResult_ReturnsStatusCode_SetsLocationHeader()
- {
- // Arrange
- var location = "/test/";
- var httpContext = GetHttpContext();
- var result = new CreatedResult(location, "testInput");
+ [Fact]
+ public async Task CreatedResult_ReturnsStatusCode_SetsLocationHeader()
+ {
+ // Arrange
+ var location = "/test/";
+ var httpContext = GetHttpContext();
+ var result = new CreatedResult(location, "testInput");
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
- Assert.Equal(location, httpContext.Response.Headers["Location"]);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
+ Assert.Equal(location, httpContext.Response.Headers["Location"]);
+ }
- [Fact]
- public async Task CreatedResult_OverwritesLocationHeader()
- {
- // Arrange
- var location = "/test/";
- var httpContext = GetHttpContext();
- httpContext.Response.Headers["Location"] = "/different/location/";
- var result = new CreatedResult(location, "testInput");
+ [Fact]
+ public async Task CreatedResult_OverwritesLocationHeader()
+ {
+ // Arrange
+ var location = "/test/";
+ var httpContext = GetHttpContext();
+ httpContext.Response.Headers["Location"] = "/different/location/";
+ var result = new CreatedResult(location, "testInput");
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
- Assert.Equal(location, httpContext.Response.Headers["Location"]);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status201Created, httpContext.Response.StatusCode);
+ Assert.Equal(location, httpContext.Response.Headers["Location"]);
+ }
- private static HttpContext GetHttpContext()
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.PathBase = new PathString("");
- httpContext.Response.Body = new MemoryStream();
- httpContext.RequestServices = CreateServices();
- return httpContext;
- }
+ private static HttpContext GetHttpContext()
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.PathBase = new PathString("");
+ httpContext.Response.Body = new MemoryStream();
+ httpContext.RequestServices = CreateServices();
+ return httpContext;
+ }
- private static IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
- return services.BuildServiceProvider();
- }
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/FileContentResultTest.cs b/src/Http/Http.Results/test/FileContentResultTest.cs
index 6a2205c2b2..02ab23f267 100644
--- a/src/Http/Http.Results/test/FileContentResultTest.cs
+++ b/src/Http/Http.Results/test/FileContentResultTest.cs
@@ -9,30 +9,29 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class FileContentResultTest : FileContentResultTestBase
{
- public class FileContentResultTest : FileContentResultTestBase
+ protected override Task ExecuteAsync(
+ HttpContext httpContext,
+ byte[] buffer,
+ string contentType,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue entityTag = null,
+ bool enableRangeProcessing = false)
{
- protected override Task ExecuteAsync(
- HttpContext httpContext,
- byte[] buffer,
- string contentType,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue entityTag = null,
- bool enableRangeProcessing = false)
+ var result = new FileContentResult(buffer, contentType)
{
- var result = new FileContentResult(buffer, contentType)
- {
- EntityTag = entityTag,
- LastModified = lastModified,
- EnableRangeProcessing = enableRangeProcessing,
- };
+ EntityTag = entityTag,
+ LastModified = lastModified,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
- httpContext.RequestServices = new ServiceCollection()
- .AddSingleton(typeof(ILogger<>), typeof(NullLogger<>))
- .BuildServiceProvider();
+ httpContext.RequestServices = new ServiceCollection()
+ .AddSingleton(typeof(ILogger<>), typeof(NullLogger<>))
+ .BuildServiceProvider();
- return result.ExecuteAsync(httpContext);
- }
+ return result.ExecuteAsync(httpContext);
}
}
diff --git a/src/Http/Http.Results/test/FileStreamResultTest.cs b/src/Http/Http.Results/test/FileStreamResultTest.cs
index fca9982ac3..6fc5cf6209 100644
--- a/src/Http/Http.Results/test/FileStreamResultTest.cs
+++ b/src/Http/Http.Results/test/FileStreamResultTest.cs
@@ -8,79 +8,78 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.Net.Http.Headers;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class FileStreamResultTest : FileStreamResultTestBase
{
- public class FileStreamResultTest : FileStreamResultTestBase
+ protected override Task ExecuteAsync(
+ HttpContext httpContext,
+ Stream stream,
+ string contentType,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue entityTag = null,
+ bool enableRangeProcessing = false)
{
- protected override Task ExecuteAsync(
- HttpContext httpContext,
- Stream stream,
- string contentType,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue entityTag = null,
- bool enableRangeProcessing = false)
+ var fileStreamResult = new FileStreamResult(stream, contentType)
{
- var fileStreamResult = new FileStreamResult(stream, contentType)
- {
- LastModified = lastModified,
- EntityTag = entityTag,
- EnableRangeProcessing = enableRangeProcessing
- };
-
- return fileStreamResult.ExecuteAsync(httpContext);
- }
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing
+ };
- [Fact]
- public void Constructor_SetsFileName()
- {
- // Arrange
- var stream = Stream.Null;
+ return fileStreamResult.ExecuteAsync(httpContext);
+ }
- // Act
- var result = new FileStreamResult(stream, "text/plain");
+ [Fact]
+ public void Constructor_SetsFileName()
+ {
+ // Arrange
+ var stream = Stream.Null;
- // Assert
- Assert.Equal(stream, result.FileStream);
- }
+ // Act
+ var result = new FileStreamResult(stream, "text/plain");
- [Fact]
- public void Constructor_SetsContentTypeAndParameters()
- {
- // Arrange
- var stream = Stream.Null;
- var contentType = "text/plain; charset=us-ascii; p1=p1-value";
- var expectedMediaType = contentType;
+ // Assert
+ Assert.Equal(stream, result.FileStream);
+ }
- // Act
- var result = new FileStreamResult(stream, contentType);
+ [Fact]
+ public void Constructor_SetsContentTypeAndParameters()
+ {
+ // Arrange
+ var stream = Stream.Null;
+ var contentType = "text/plain; charset=us-ascii; p1=p1-value";
+ var expectedMediaType = contentType;
- // Assert
- Assert.Equal(stream, result.FileStream);
- Assert.Equal(expectedMediaType, result.ContentType);
- }
+ // Act
+ var result = new FileStreamResult(stream, contentType);
- [Fact]
- public void Constructor_SetsLastModifiedAndEtag()
- {
- // Arrange
- var stream = Stream.Null;
- var contentType = "text/plain";
- var expectedMediaType = contentType;
- var lastModified = new DateTimeOffset();
- var entityTag = new EntityTagHeaderValue("\"Etag\"");
+ // Assert
+ Assert.Equal(stream, result.FileStream);
+ Assert.Equal(expectedMediaType, result.ContentType);
+ }
- // Act
- var result = new FileStreamResult(stream, contentType)
- {
- LastModified = lastModified,
- EntityTag = entityTag,
- };
+ [Fact]
+ public void Constructor_SetsLastModifiedAndEtag()
+ {
+ // Arrange
+ var stream = Stream.Null;
+ var contentType = "text/plain";
+ var expectedMediaType = contentType;
+ var lastModified = new DateTimeOffset();
+ var entityTag = new EntityTagHeaderValue("\"Etag\"");
- // Assert
- Assert.Equal(lastModified, result.LastModified);
- Assert.Equal(entityTag, result.EntityTag);
- Assert.Equal(expectedMediaType, result.ContentType);
- }
+ // Act
+ var result = new FileStreamResult(stream, contentType)
+ {
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ };
+ // Assert
+ Assert.Equal(lastModified, result.LastModified);
+ Assert.Equal(entityTag, result.EntityTag);
+ Assert.Equal(expectedMediaType, result.ContentType);
}
+
}
diff --git a/src/Http/Http.Results/test/ForbidResultTest.cs b/src/Http/Http.Results/test/ForbidResultTest.cs
index f123d38469..42236c0066 100644
--- a/src/Http/Http.Results/test/ForbidResultTest.cs
+++ b/src/Http/Http.Results/test/ForbidResultTest.cs
@@ -10,120 +10,119 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class ForbidResultTest
{
- public class ForbidResultTest
+ [Fact]
+ public async Task ExecuteResultAsync_InvokesForbidAsyncOnAuthenticationService()
{
- [Fact]
- public async Task ExecuteResultAsync_InvokesForbidAsyncOnAuthenticationService()
- {
- // Arrange
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "", null))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new ForbidResult("", null);
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- auth.Verify();
- }
-
- [Fact]
- public async Task ExecuteResultAsync_InvokesForbidAsyncOnAllConfiguredSchemes()
+ // Arrange
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "", null))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new ForbidResult("", null);
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ auth.Verify();
+ }
+
+ [Fact]
+ public async Task ExecuteResultAsync_InvokesForbidAsyncOnAllConfiguredSchemes()
+ {
+ // Arrange
+ var authProperties = new AuthenticationProperties();
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "Scheme1", authProperties))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ auth
+ .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "Scheme2", authProperties))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new ForbidResult(new[] { "Scheme1", "Scheme2" }, authProperties);
+ var routeData = new RouteData();
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ auth.Verify();
+ }
+
+ public static TheoryData ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData =>
+ new TheoryData<AuthenticationProperties>
{
- // Arrange
- var authProperties = new AuthenticationProperties();
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "Scheme1", authProperties))
- .Returns(Task.CompletedTask)
- .Verifiable();
- auth
- .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), "Scheme2", authProperties))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new ForbidResult(new[] { "Scheme1", "Scheme2" }, authProperties);
- var routeData = new RouteData();
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- auth.Verify();
- }
-
- public static TheoryData ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData =>
- new TheoryData<AuthenticationProperties>
- {
null,
new AuthenticationProperties()
- };
+ };
- [Theory]
- [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))]
- public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties(AuthenticationProperties expected)
- {
- // Arrange
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), null, expected))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var result = new ForbidResult(expected);
- var httpContext = GetHttpContext(auth.Object);
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- auth.Verify();
- }
-
- [Theory]
- [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))]
- public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties_WhenAuthenticationSchemesIsEmpty(
- AuthenticationProperties expected)
- {
- // Arrange
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), null, expected))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new ForbidResult(expected)
- {
- AuthenticationSchemes = new string[0]
- };
- var routeData = new RouteData();
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- auth.Verify();
- }
-
- private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices()
- .AddSingleton(auth)
- .BuildServiceProvider();
- return httpContext;
- }
-
- private static IServiceCollection CreateServices()
+ [Theory]
+ [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))]
+ public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties(AuthenticationProperties expected)
+ {
+ // Arrange
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), null, expected))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var result = new ForbidResult(expected);
+ var httpContext = GetHttpContext(auth.Object);
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ auth.Verify();
+ }
+
+ [Theory]
+ [MemberData(nameof(ExecuteResultAsync_InvokesForbidAsyncWithAuthPropertiesData))]
+ public async Task ExecuteResultAsync_InvokesForbidAsyncWithAuthProperties_WhenAuthenticationSchemesIsEmpty(
+ AuthenticationProperties expected)
+ {
+ // Arrange
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.ForbidAsync(It.IsAny<HttpContext>(), null, expected))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new ForbidResult(expected)
{
- var services = new ServiceCollection();
- services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
- return services;
- }
+ AuthenticationSchemes = new string[0]
+ };
+ var routeData = new RouteData();
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ auth.Verify();
+ }
+
+ private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices()
+ .AddSingleton(auth)
+ .BuildServiceProvider();
+ return httpContext;
+ }
+
+ private static IServiceCollection CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ return services;
}
}
diff --git a/src/Http/Http.Results/test/LocalRedirectResultTest.cs b/src/Http/Http.Results/test/LocalRedirectResultTest.cs
index d059f50540..c641cfcb06 100644
--- a/src/Http/Http.Results/test/LocalRedirectResultTest.cs
+++ b/src/Http/Http.Results/test/LocalRedirectResultTest.cs
@@ -8,131 +8,130 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class LocalRedirectResultTest
{
- public class LocalRedirectResultTest
+ [Fact]
+ public void Constructor_WithParameterUrl_SetsResultUrlAndNotPermanentOrPreserveMethod()
+ {
+ // Arrange
+ var url = "/test/url";
+
+ // Act
+ var result = new LocalRedirectResult(url);
+
+ // Assert
+ Assert.False(result.PreserveMethod);
+ Assert.False(result.Permanent);
+ Assert.Same(url, result.Url);
+ }
+
+ [Fact]
+ public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlAndPermanentNotPreserveMethod()
+ {
+ // Arrange
+ var url = "/test/url";
+
+ // Act
+ var result = new LocalRedirectResult(url, permanent: true);
+
+ // Assert
+ Assert.False(result.PreserveMethod);
+ Assert.True(result.Permanent);
+ Assert.Same(url, result.Url);
+ }
+
+ [Fact]
+ public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlPermanentAndPreserveMethod()
+ {
+ // Arrange
+ var url = "/test/url";
+
+ // Act
+ var result = new LocalRedirectResult(url, permanent: true, preserveMethod: true);
+
+ // Assert
+ Assert.True(result.PreserveMethod);
+ Assert.True(result.Permanent);
+ Assert.Same(url, result.Url);
+ }
+
+ [Fact]
+ public async Task Execute_ReturnsExpectedValues()
+ {
+ // Arrange
+ var appRoot = "/";
+ var contentPath = "~/Home/About";
+ var expectedPath = "/Home/About";
+
+ var httpContext = GetHttpContext(appRoot);
+ var result = new LocalRedirectResult(contentPath);
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(expectedPath, httpContext.Response.Headers.Location.ToString());
+ Assert.Equal(StatusCodes.Status302Found, httpContext.Response.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("", "//")]
+ [InlineData("", "/\\")]
+ [InlineData("", "//foo")]
+ [InlineData("", "/\\foo")]
+ [InlineData("", "Home/About")]
+ [InlineData("/myapproot", "http://www.example.com")]
+ public async Task Execute_Throws_ForNonLocalUrl(
+ string appRoot,
+ string contentPath)
+ {
+ // Arrange
+ var httpContext = GetHttpContext(appRoot);
+ var result = new LocalRedirectResult(contentPath);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => result.ExecuteAsync(httpContext));
+ Assert.Equal(
+ "The supplied URL is not local. A URL with an absolute path is considered local if it does not " +
+ "have a host/authority part. URLs using virtual paths ('~/') are also local.",
+ exception.Message);
+ }
+
+ [Theory]
+ [InlineData("", "~//")]
+ [InlineData("", "~/\\")]
+ [InlineData("", "~//foo")]
+ [InlineData("", "~/\\foo")]
+ public async Task Execute_Throws_ForNonLocalUrlTilde(
+ string appRoot,
+ string contentPath)
+ {
+ // Arrange
+ var httpContext = GetHttpContext(appRoot);
+ var result = new LocalRedirectResult(contentPath);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => result.ExecuteAsync(httpContext));
+ Assert.Equal(
+ "The supplied URL is not local. A URL with an absolute path is considered local if it does not " +
+ "have a host/authority part. URLs using virtual paths ('~/') are also local.",
+ exception.Message);
+ }
+
+ private static IServiceProvider GetServiceProvider()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddTransient(typeof(ILogger<>), typeof(NullLogger<>));
+ return serviceCollection.BuildServiceProvider();
+ }
+
+ private static HttpContext GetHttpContext(string appRoot)
{
- [Fact]
- public void Constructor_WithParameterUrl_SetsResultUrlAndNotPermanentOrPreserveMethod()
- {
- // Arrange
- var url = "/test/url";
-
- // Act
- var result = new LocalRedirectResult(url);
-
- // Assert
- Assert.False(result.PreserveMethod);
- Assert.False(result.Permanent);
- Assert.Same(url, result.Url);
- }
-
- [Fact]
- public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlAndPermanentNotPreserveMethod()
- {
- // Arrange
- var url = "/test/url";
-
- // Act
- var result = new LocalRedirectResult(url, permanent: true);
-
- // Assert
- Assert.False(result.PreserveMethod);
- Assert.True(result.Permanent);
- Assert.Same(url, result.Url);
- }
-
- [Fact]
- public void Constructor_WithParameterUrlAndPermanent_SetsResultUrlPermanentAndPreserveMethod()
- {
- // Arrange
- var url = "/test/url";
-
- // Act
- var result = new LocalRedirectResult(url, permanent: true, preserveMethod: true);
-
- // Assert
- Assert.True(result.PreserveMethod);
- Assert.True(result.Permanent);
- Assert.Same(url, result.Url);
- }
-
- [Fact]
- public async Task Execute_ReturnsExpectedValues()
- {
- // Arrange
- var appRoot = "/";
- var contentPath = "~/Home/About";
- var expectedPath = "/Home/About";
-
- var httpContext = GetHttpContext(appRoot);
- var result = new LocalRedirectResult(contentPath);
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(expectedPath, httpContext.Response.Headers.Location.ToString());
- Assert.Equal(StatusCodes.Status302Found, httpContext.Response.StatusCode);
- }
-
- [Theory]
- [InlineData("", "//")]
- [InlineData("", "/\\")]
- [InlineData("", "//foo")]
- [InlineData("", "/\\foo")]
- [InlineData("", "Home/About")]
- [InlineData("/myapproot", "http://www.example.com")]
- public async Task Execute_Throws_ForNonLocalUrl(
- string appRoot,
- string contentPath)
- {
- // Arrange
- var httpContext = GetHttpContext(appRoot);
- var result = new LocalRedirectResult(contentPath);
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => result.ExecuteAsync(httpContext));
- Assert.Equal(
- "The supplied URL is not local. A URL with an absolute path is considered local if it does not " +
- "have a host/authority part. URLs using virtual paths ('~/') are also local.",
- exception.Message);
- }
-
- [Theory]
- [InlineData("", "~//")]
- [InlineData("", "~/\\")]
- [InlineData("", "~//foo")]
- [InlineData("", "~/\\foo")]
- public async Task Execute_Throws_ForNonLocalUrlTilde(
- string appRoot,
- string contentPath)
- {
- // Arrange
- var httpContext = GetHttpContext(appRoot);
- var result = new LocalRedirectResult(contentPath);
-
- // Act & Assert
- var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => result.ExecuteAsync(httpContext));
- Assert.Equal(
- "The supplied URL is not local. A URL with an absolute path is considered local if it does not " +
- "have a host/authority part. URLs using virtual paths ('~/') are also local.",
- exception.Message);
- }
-
- private static IServiceProvider GetServiceProvider()
- {
- var serviceCollection = new ServiceCollection();
- serviceCollection.AddTransient(typeof(ILogger<>), typeof(NullLogger<>));
- return serviceCollection.BuildServiceProvider();
- }
-
- private static HttpContext GetHttpContext(string appRoot)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = GetServiceProvider();
- httpContext.Request.PathBase = new PathString(appRoot);
- return httpContext;
- }
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = GetServiceProvider();
+ httpContext.Request.PathBase = new PathString(appRoot);
+ return httpContext;
}
}
diff --git a/src/Http/Http.Results/test/NotFoundObjectResultTest.cs b/src/Http/Http.Results/test/NotFoundObjectResultTest.cs
index af6387f9fa..59604dc062 100644
--- a/src/Http/Http.Results/test/NotFoundObjectResultTest.cs
+++ b/src/Http/Http.Results/test/NotFoundObjectResultTest.cs
@@ -9,59 +9,58 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class NotFoundObjectResultTest
{
- public class NotFoundObjectResultTest
+ [Fact]
+ public void NotFoundObjectResult_InitializesStatusCode()
{
- [Fact]
- public void NotFoundObjectResult_InitializesStatusCode()
- {
- // Arrange & act
- var notFound = new NotFoundObjectResult(null);
+ // Arrange & act
+ var notFound = new NotFoundObjectResult(null);
- // Assert
- Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+ }
- [Fact]
- public void NotFoundObjectResult_InitializesStatusCodeAndResponseContent()
- {
- // Arrange & act
- var notFound = new NotFoundObjectResult("Test Content");
+ [Fact]
+ public void NotFoundObjectResult_InitializesStatusCodeAndResponseContent()
+ {
+ // Arrange & act
+ var notFound = new NotFoundObjectResult("Test Content");
- // Assert
- Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
- Assert.Equal("Test Content", notFound.Value);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status404NotFound, notFound.StatusCode);
+ Assert.Equal("Test Content", notFound.Value);
+ }
- [Fact]
- public async Task NotFoundObjectResult_ExecuteSuccessful()
- {
- // Arrange
- var httpContext = GetHttpContext();
- var result = new NotFoundObjectResult("Test Content");
+ [Fact]
+ public async Task NotFoundObjectResult_ExecuteSuccessful()
+ {
+ // Arrange
+ var httpContext = GetHttpContext();
+ var result = new NotFoundObjectResult("Test Content");
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
+ }
- private static HttpContext GetHttpContext()
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.PathBase = new PathString("");
- httpContext.Response.Body = new MemoryStream();
- httpContext.RequestServices = CreateServices();
- return httpContext;
- }
+ private static HttpContext GetHttpContext()
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.PathBase = new PathString("");
+ httpContext.Response.Body = new MemoryStream();
+ httpContext.RequestServices = CreateServices();
+ return httpContext;
+ }
- private static IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
- return services.BuildServiceProvider();
- }
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/ObjectResultTests.cs b/src/Http/Http.Results/test/ObjectResultTests.cs
index 849708dc33..59160a9dcb 100644
--- a/src/Http/Http.Results/test/ObjectResultTests.cs
+++ b/src/Http/Http.Results/test/ObjectResultTests.cs
@@ -10,195 +10,194 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class ObjectResultTests
{
- public class ObjectResultTests
+ [Fact]
+ public async Task ObjectResult_ExecuteAsync_WithNullValue_Works()
{
- [Fact]
- public async Task ObjectResult_ExecuteAsync_WithNullValue_Works()
+ // Arrange
+ var result = new ObjectResult(value: null, 411);
+
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var result = new ObjectResult(value: null, 411);
+ RequestServices = CreateServices(),
+ };
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- };
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Act
- await result.ExecuteAsync(httpContext);
+ // Assert
+ Assert.Equal(411, httpContext.Response.StatusCode);
+ }
- // Assert
- Assert.Equal(411, httpContext.Response.StatusCode);
- }
+ [Fact]
+ public async Task ObjectResult_ExecuteAsync_SetsStatusCode()
+ {
+ // Arrange
+ var result = new ObjectResult("Hello", 407);
- [Fact]
- public async Task ObjectResult_ExecuteAsync_SetsStatusCode()
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var result = new ObjectResult("Hello", 407);
+ RequestServices = CreateServices(),
+ };
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- };
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(407, httpContext.Response.StatusCode);
- }
+ // Assert
+ Assert.Equal(407, httpContext.Response.StatusCode);
+ }
- [Fact]
- public async Task ObjectResult_ExecuteAsync_JsonSerializesBody()
+ [Fact]
+ public async Task ObjectResult_ExecuteAsync_JsonSerializesBody()
+ {
+ // Arrange
+ var result = new ObjectResult("Hello", 407);
+ var stream = new MemoryStream();
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var result = new ObjectResult("Hello", 407);
- var stream = new MemoryStream();
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- Response =
+ RequestServices = CreateServices(),
+ Response =
{
Body = stream,
},
- };
+ };
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal("\"Hello\"", Encoding.UTF8.GetString(stream.ToArray()));
- }
+ // Assert
+ Assert.Equal("\"Hello\"", Encoding.UTF8.GetString(stream.ToArray()));
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_UsesDefaults_ForProblemDetails()
+ {
+ // Arrange
+ var details = new ProblemDetails();
- [Fact]
- public async Task ExecuteAsync_UsesDefaults_ForProblemDetails()
+ var result = new ObjectResult(details);
+ var stream = new MemoryStream();
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var details = new ProblemDetails();
-
- var result = new ObjectResult(details);
- var stream = new MemoryStream();
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- Response =
+ RequestServices = CreateServices(),
+ Response =
{
Body = stream,
},
- };
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(StatusCodes.Status500InternalServerError, httpContext.Response.StatusCode);
- stream.Position = 0;
- var responseDetails = JsonSerializer.Deserialize<ProblemDetails>(stream);
- Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.6.1", responseDetails.Type);
- Assert.Equal("An error occurred while processing your request.", responseDetails.Title);
- Assert.Equal(StatusCodes.Status500InternalServerError, responseDetails.Status);
- }
-
- [Fact]
- public async Task ExecuteAsync_UsesDefaults_ForValidationProblemDetails()
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status500InternalServerError, httpContext.Response.StatusCode);
+ stream.Position = 0;
+ var responseDetails = JsonSerializer.Deserialize<ProblemDetails>(stream);
+ Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.6.1", responseDetails.Type);
+ Assert.Equal("An error occurred while processing your request.", responseDetails.Title);
+ Assert.Equal(StatusCodes.Status500InternalServerError, responseDetails.Status);
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_UsesDefaults_ForValidationProblemDetails()
+ {
+ // Arrange
+ var details = new HttpValidationProblemDetails();
+
+ var result = new ObjectResult(details);
+ var stream = new MemoryStream();
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var details = new HttpValidationProblemDetails();
-
- var result = new ObjectResult(details);
- var stream = new MemoryStream();
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- Response =
+ RequestServices = CreateServices(),
+ Response =
{
Body = stream,
},
- };
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
- stream.Position = 0;
- var responseDetails = JsonSerializer.Deserialize<HttpValidationProblemDetails>(stream);
- Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.5.1", responseDetails.Type);
- Assert.Equal("One or more validation errors occurred.", responseDetails.Title);
- Assert.Equal(StatusCodes.Status400BadRequest, responseDetails.Status);
- }
-
- [Fact]
- public async Task ExecuteAsync_SetsProblemDetailsStatus_ForValidationProblemDetails()
- {
- // Arrange
- var details = new HttpValidationProblemDetails();
-
- var result = new ObjectResult(details, StatusCodes.Status422UnprocessableEntity);
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- };
-
- // Act
- await result.ExecuteAsync(httpContext);
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
+ stream.Position = 0;
+ var responseDetails = JsonSerializer.Deserialize<HttpValidationProblemDetails>(stream);
+ Assert.Equal("https://tools.ietf.org/html/rfc7231#section-6.5.1", responseDetails.Type);
+ Assert.Equal("One or more validation errors occurred.", responseDetails.Title);
+ Assert.Equal(StatusCodes.Status400BadRequest, responseDetails.Status);
+ }
- // Assert
- Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value);
- }
+ [Fact]
+ public async Task ExecuteAsync_SetsProblemDetailsStatus_ForValidationProblemDetails()
+ {
+ // Arrange
+ var details = new HttpValidationProblemDetails();
- [Fact]
- public async Task ExecuteAsync_GetsStatusCodeFromProblemDetails()
+ var result = new ObjectResult(details, StatusCodes.Status422UnprocessableEntity);
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var details = new ProblemDetails { Status = StatusCodes.Status413RequestEntityTooLarge, };
+ RequestServices = CreateServices(),
+ };
- var result = new ObjectResult(details);
+ // Act
+ await result.ExecuteAsync(httpContext);
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- };
+ // Assert
+ Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value);
+ }
- // Act
- await result.ExecuteAsync(httpContext);
+ [Fact]
+ public async Task ExecuteAsync_GetsStatusCodeFromProblemDetails()
+ {
+ // Arrange
+ var details = new ProblemDetails { Status = StatusCodes.Status413RequestEntityTooLarge, };
- // Assert
- Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, details.Status.Value);
- Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, result.StatusCode.Value);
- Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, httpContext.Response.StatusCode);
- }
+ var result = new ObjectResult(details);
- [Fact]
- public async Task ExecuteAsync_UsesStatusCodeFromResultTypeForProblemDetails()
+ var httpContext = new DefaultHttpContext()
{
- // Arrange
- var details = new ProblemDetails { Status = StatusCodes.Status422UnprocessableEntity, };
+ RequestServices = CreateServices(),
+ };
- var result = new BadRequestObjectResult(details);
+ // Act
+ await result.ExecuteAsync(httpContext);
- var httpContext = new DefaultHttpContext()
- {
- RequestServices = CreateServices(),
- };
+ // Assert
+ Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, details.Status.Value);
+ Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, result.StatusCode.Value);
+ Assert.Equal(StatusCodes.Status413RequestEntityTooLarge, httpContext.Response.StatusCode);
+ }
- // Act
- await result.ExecuteAsync(httpContext);
+ [Fact]
+ public async Task ExecuteAsync_UsesStatusCodeFromResultTypeForProblemDetails()
+ {
+ // Arrange
+ var details = new ProblemDetails { Status = StatusCodes.Status422UnprocessableEntity, };
- // Assert
- Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value);
- Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode.Value);
- Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
- }
+ var result = new BadRequestObjectResult(details);
- private static IServiceProvider CreateServices()
+ var httpContext = new DefaultHttpContext()
{
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+ RequestServices = CreateServices(),
+ };
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value);
+ Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode.Value);
+ Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
+ }
+
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
- return services.BuildServiceProvider();
- }
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/OkObjectResultTest.cs b/src/Http/Http.Results/test/OkObjectResultTest.cs
index 39b7f47db7..d154863360 100644
--- a/src/Http/Http.Results/test/OkObjectResultTest.cs
+++ b/src/Http/Http.Results/test/OkObjectResultTest.cs
@@ -9,38 +9,37 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class OkObjectResultTest
{
- public class OkObjectResultTest
+ [Fact]
+ public async Task OkObjectResult_SetsStatusCodeAndValue()
{
- [Fact]
- public async Task OkObjectResult_SetsStatusCodeAndValue()
- {
- // Arrange
- var result = new OkObjectResult("Hello world");
- var httpContext = GetHttpContext();
+ // Arrange
+ var result = new OkObjectResult("Hello world");
+ var httpContext = GetHttpContext();
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status200OK, httpContext.Response.StatusCode);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status200OK, httpContext.Response.StatusCode);
+ }
- private static HttpContext GetHttpContext()
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.PathBase = new PathString("");
- httpContext.Response.Body = new MemoryStream();
- httpContext.RequestServices = CreateServices();
- return httpContext;
- }
+ private static HttpContext GetHttpContext()
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.PathBase = new PathString("");
+ httpContext.Response.Body = new MemoryStream();
+ httpContext.RequestServices = CreateServices();
+ return httpContext;
+ }
- private static IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
- return services.BuildServiceProvider();
- }
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Http.Results/test/PhysicalFileResultTest.cs b/src/Http/Http.Results/test/PhysicalFileResultTest.cs
index 1a99d1aa93..f64808ee54 100644
--- a/src/Http/Http.Results/test/PhysicalFileResultTest.cs
+++ b/src/Http/Http.Results/test/PhysicalFileResultTest.cs
@@ -6,36 +6,35 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class PhysicalFileResultTest : PhysicalFileResultTestBase
{
- public class PhysicalFileResultTest : PhysicalFileResultTestBase
+ protected override Task ExecuteAsync(
+ HttpContext httpContext,
+ string path,
+ string contentType,
+ DateTimeOffset? lastModified = null,
+ EntityTagHeaderValue entityTag = null,
+ bool enableRangeProcessing = false)
{
- protected override Task ExecuteAsync(
- HttpContext httpContext,
- string path,
- string contentType,
- DateTimeOffset? lastModified = null,
- EntityTagHeaderValue entityTag = null,
- bool enableRangeProcessing = false)
+ var fileResult = new PhysicalFileResult(path, contentType)
{
- var fileResult = new PhysicalFileResult(path, contentType)
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing,
+ GetFileInfoWrapper = (path) =>
{
- LastModified = lastModified,
- EntityTag = entityTag,
- EnableRangeProcessing = enableRangeProcessing,
- GetFileInfoWrapper = (path) =>
+ var lastModified = DateTimeOffset.MinValue.AddDays(1);
+ return new()
{
- var lastModified = DateTimeOffset.MinValue.AddDays(1);
- return new()
- {
- Exists = true,
- Length = 34,
- LastWriteTimeUtc = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0))
- };
- }
- };
+ Exists = true,
+ Length = 34,
+ LastWriteTimeUtc = new DateTimeOffset(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second, TimeSpan.FromSeconds(0))
+ };
+ }
+ };
- return fileResult.ExecuteAsync(httpContext);
- }
+ return fileResult.ExecuteAsync(httpContext);
}
}
diff --git a/src/Http/Http.Results/test/RedirectResultTest.cs b/src/Http/Http.Results/test/RedirectResultTest.cs
index 2b0567e12a..a6fc4d31f8 100644
--- a/src/Http/Http.Results/test/RedirectResultTest.cs
+++ b/src/Http/Http.Results/test/RedirectResultTest.cs
@@ -4,29 +4,28 @@
using Microsoft.AspNetCore.Internal;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class RedirectResultTest : RedirectResultTestBase
{
- public class RedirectResultTest : RedirectResultTestBase
+ [Fact]
+ public void RedirectResult_Constructor_WithParameterUrlPermanentAndPreservesMethod_SetsResultUrlPermanentAndPreservesMethod()
{
- [Fact]
- public void RedirectResult_Constructor_WithParameterUrlPermanentAndPreservesMethod_SetsResultUrlPermanentAndPreservesMethod()
- {
- // Arrange
- var url = "/test/url";
+ // Arrange
+ var url = "/test/url";
- // Act
- var result = new RedirectResult(url, permanent: true, preserveMethod: true);
+ // Act
+ var result = new RedirectResult(url, permanent: true, preserveMethod: true);
- // Assert
- Assert.True(result.PreserveMethod);
- Assert.True(result.Permanent);
- Assert.Same(url, result.Url);
- }
+ // Assert
+ Assert.True(result.PreserveMethod);
+ Assert.True(result.Permanent);
+ Assert.Same(url, result.Url);
+ }
- protected override Task ExecuteAsync(HttpContext httpContext, string contentPath)
- {
- var redirectResult = new RedirectResult(contentPath, false, false);
- return redirectResult.ExecuteAsync(httpContext);
- }
+ protected override Task ExecuteAsync(HttpContext httpContext, string contentPath)
+ {
+ var redirectResult = new RedirectResult(contentPath, false, false);
+ return redirectResult.ExecuteAsync(httpContext);
}
}
diff --git a/src/Http/Http.Results/test/RedirectToRouteResultTest.cs b/src/Http/Http.Results/test/RedirectToRouteResultTest.cs
index b2e2e9c05a..2d5af54aae 100644
--- a/src/Http/Http.Results/test/RedirectToRouteResultTest.cs
+++ b/src/Http/Http.Results/test/RedirectToRouteResultTest.cs
@@ -12,100 +12,99 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class RedirectToRouteResultTest
{
- public class RedirectToRouteResultTest
+ [Fact]
+ public async Task RedirectToRoute_Execute_ThrowsOnNullUrl()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices(null).BuildServiceProvider();
+
+ var result = new RedirectToRouteResult(null, new Dictionary<string, object>());
+
+ // Act & Assert
+ await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
+ async () =>
+ {
+ await result.ExecuteAsync(httpContext);
+ },
+ "No route matches the supplied values.");
+ }
+
+ [Fact]
+ public async Task ExecuteResultAsync_UsesRouteName_ToGenerateLocationHeader()
{
- [Fact]
- public async Task RedirectToRoute_Execute_ThrowsOnNullUrl()
- {
- // Arrange
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices(null).BuildServiceProvider();
-
- var result = new RedirectToRouteResult(null, new Dictionary<string, object>());
-
- // Act & Assert
- await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
- async () =>
- {
- await result.ExecuteAsync(httpContext);
- },
- "No route matches the supplied values.");
- }
-
- [Fact]
- public async Task ExecuteResultAsync_UsesRouteName_ToGenerateLocationHeader()
- {
- // Arrange
- var routeName = "orders_api";
- var locationUrl = "/api/orders/10";
-
- var httpContext = GetHttpContext(locationUrl);
-
- var result = new RedirectToRouteResult(routeName, new { id = 10 });
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.True(httpContext.Response.Headers.ContainsKey("Location"), "Location header not found");
- Assert.Equal(locationUrl, httpContext.Response.Headers["Location"]);
- }
-
- [Fact]
- public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect()
- {
- // Arrange
- var expectedUrl = "/SampleAction#test";
- var expectedStatusCode = StatusCodes.Status301MovedPermanently;
- var httpContext = GetHttpContext(expectedUrl);
-
- var result = new RedirectToRouteResult("Sample", null, true, "test");
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
- Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
- }
-
- [Fact]
- public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect_WithPreserveMethod()
- {
- // Arrange
- var expectedUrl = "/SampleAction#test";
- var expectedStatusCode = StatusCodes.Status308PermanentRedirect;
-
- var httpContext = GetHttpContext(expectedUrl);
- var result = new RedirectToRouteResult("Sample", null, true, true, "test");
-
- // Act
- await result.ExecuteAsync(httpContext);
-
- // Assert
- Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
- Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
- }
-
- private static HttpContext GetHttpContext(string path)
- {
- var services = CreateServices(path);
-
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = services.BuildServiceProvider();
-
- return httpContext;
- }
-
- private static IServiceCollection CreateServices(string path)
- {
- var services = new ServiceCollection();
- services.AddSingleton<LinkGenerator>(new TestLinkGenerator { Url = path });
-
- services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
- return services;
- }
+ // Arrange
+ var routeName = "orders_api";
+ var locationUrl = "/api/orders/10";
+
+ var httpContext = GetHttpContext(locationUrl);
+
+ var result = new RedirectToRouteResult(routeName, new { id = 10 });
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.True(httpContext.Response.Headers.ContainsKey("Location"), "Location header not found");
+ Assert.Equal(locationUrl, httpContext.Response.Headers["Location"]);
+ }
+
+ [Fact]
+ public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect()
+ {
+ // Arrange
+ var expectedUrl = "/SampleAction#test";
+ var expectedStatusCode = StatusCodes.Status301MovedPermanently;
+ var httpContext = GetHttpContext(expectedUrl);
+
+ var result = new RedirectToRouteResult("Sample", null, true, "test");
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
+
+ [Fact]
+ public async Task ExecuteResultAsync_WithFragment_PassesCorrectValuesToRedirect_WithPreserveMethod()
+ {
+ // Arrange
+ var expectedUrl = "/SampleAction#test";
+ var expectedStatusCode = StatusCodes.Status308PermanentRedirect;
+
+ var httpContext = GetHttpContext(expectedUrl);
+ var result = new RedirectToRouteResult("Sample", null, true, true, "test");
+
+ // Act
+ await result.ExecuteAsync(httpContext);
+
+ // Assert
+ Assert.Equal(expectedStatusCode, httpContext.Response.StatusCode);
+ Assert.Equal(expectedUrl, httpContext.Response.Headers["Location"]);
+ }
+
+ private static HttpContext GetHttpContext(string path)
+ {
+ var services = CreateServices(path);
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = services.BuildServiceProvider();
+
+ return httpContext;
+ }
+
+ private static IServiceCollection CreateServices(string path)
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<LinkGenerator>(new TestLinkGenerator { Url = path });
+
+ services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ return services;
}
}
diff --git a/src/Http/Http.Results/test/SignInResultTest.cs b/src/Http/Http.Results/test/SignInResultTest.cs
index 2de4d09df7..ba80cce51a 100644
--- a/src/Http/Http.Results/test/SignInResultTest.cs
+++ b/src/Http/Http.Results/test/SignInResultTest.cs
@@ -10,86 +10,85 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class SignInResultTest
{
- public class SignInResultTest
+ [Fact]
+ public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManager()
{
- [Fact]
- public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManager()
- {
- // Arrange
- var principal = new ClaimsPrincipal();
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), "", principal, null))
- .Returns(Task.CompletedTask)
- .Verifiable();
+ // Arrange
+ var principal = new ClaimsPrincipal();
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), "", principal, null))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new SignInResult("", principal, null);
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new SignInResult("", principal, null);
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- auth.Verify();
- }
+ // Assert
+ auth.Verify();
+ }
- [Fact]
- public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManagerWithDefaultScheme()
- {
- // Arrange
- var principal = new ClaimsPrincipal();
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), null, principal, null))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new SignInResult(principal);
+ [Fact]
+ public async Task ExecuteAsync_InvokesSignInAsyncOnAuthenticationManagerWithDefaultScheme()
+ {
+ // Arrange
+ var principal = new ClaimsPrincipal();
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), null, principal, null))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new SignInResult(principal);
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- auth.Verify();
- }
+ // Assert
+ auth.Verify();
+ }
- [Fact]
- public async Task ExecuteAsync_InvokesSignInAsyncOnConfiguredScheme()
- {
- // Arrange
- var principal = new ClaimsPrincipal();
- var authProperties = new AuthenticationProperties();
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), "Scheme1", principal, authProperties))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new SignInResult("Scheme1", principal, authProperties);
+ [Fact]
+ public async Task ExecuteAsync_InvokesSignInAsyncOnConfiguredScheme()
+ {
+ // Arrange
+ var principal = new ClaimsPrincipal();
+ var authProperties = new AuthenticationProperties();
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.SignInAsync(It.IsAny<HttpContext>(), "Scheme1", principal, authProperties))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new SignInResult("Scheme1", principal, authProperties);
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- auth.Verify();
- }
+ // Assert
+ auth.Verify();
+ }
- private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices()
- .AddSingleton(auth)
- .BuildServiceProvider();
- return httpContext;
- }
+ private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices()
+ .AddSingleton(auth)
+ .BuildServiceProvider();
+ return httpContext;
+ }
- private static IServiceCollection CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
- return services;
- }
+ private static IServiceCollection CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ return services;
}
}
diff --git a/src/Http/Http.Results/test/SignOutResultTest.cs b/src/Http/Http.Results/test/SignOutResultTest.cs
index 055ee04637..8f7368be11 100644
--- a/src/Http/Http.Results/test/SignOutResultTest.cs
+++ b/src/Http/Http.Results/test/SignOutResultTest.cs
@@ -10,86 +10,85 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class SignOutResultTest
{
- public class SignOutResultTest
+ [Fact]
+ public async Task ExecuteAsync_NoArgsInvokesDefaultSignOut()
{
- [Fact]
- public async Task ExecuteAsync_NoArgsInvokesDefaultSignOut()
- {
- // Arrange
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), null, null))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new SignOutResult();
+ // Arrange
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), null, null))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new SignOutResult();
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- auth.Verify();
- }
+ // Assert
+ auth.Verify();
+ }
- [Fact]
- public async Task ExecuteAsync_InvokesSignOutAsyncOnAuthenticationManager()
- {
- // Arrange
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "", null))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new SignOutResult("", null);
+ [Fact]
+ public async Task ExecuteAsync_InvokesSignOutAsyncOnAuthenticationManager()
+ {
+ // Arrange
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "", null))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new SignOutResult("", null);
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- auth.Verify();
- }
+ // Assert
+ auth.Verify();
+ }
- [Fact]
- public async Task ExecuteAsync_InvokesSignOutAsyncOnAllConfiguredSchemes()
- {
- // Arrange
- var authProperties = new AuthenticationProperties();
- var auth = new Mock<IAuthenticationService>();
- auth
- .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "Scheme1", authProperties))
- .Returns(Task.CompletedTask)
- .Verifiable();
- auth
- .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "Scheme2", authProperties))
- .Returns(Task.CompletedTask)
- .Verifiable();
- var httpContext = GetHttpContext(auth.Object);
- var result = new SignOutResult(new[] { "Scheme1", "Scheme2" }, authProperties);
+ [Fact]
+ public async Task ExecuteAsync_InvokesSignOutAsyncOnAllConfiguredSchemes()
+ {
+ // Arrange
+ var authProperties = new AuthenticationProperties();
+ var auth = new Mock<IAuthenticationService>();
+ auth
+ .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "Scheme1", authProperties))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ auth
+ .Setup(c => c.SignOutAsync(It.IsAny<HttpContext>(), "Scheme2", authProperties))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
+ var httpContext = GetHttpContext(auth.Object);
+ var result = new SignOutResult(new[] { "Scheme1", "Scheme2" }, authProperties);
- // Act
- await result.ExecuteAsync(httpContext);
+ // Act
+ await result.ExecuteAsync(httpContext);
- // Assert
- auth.Verify();
- }
+ // Assert
+ auth.Verify();
+ }
- private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = CreateServices()
- .AddSingleton(auth)
- .BuildServiceProvider();
- return httpContext;
- }
+ private static DefaultHttpContext GetHttpContext(IAuthenticationService auth)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = CreateServices()
+ .AddSingleton(auth)
+ .BuildServiceProvider();
+ return httpContext;
+ }
- private static IServiceCollection CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
- return services;
- }
+ private static IServiceCollection CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
+ return services;
}
}
diff --git a/src/Http/Http.Results/test/StatusCodeResultTests.cs b/src/Http/Http.Results/test/StatusCodeResultTests.cs
index bc32a64ae4..85dca965ce 100644
--- a/src/Http/Http.Results/test/StatusCodeResultTests.cs
+++ b/src/Http/Http.Results/test/StatusCodeResultTests.cs
@@ -6,40 +6,39 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class StatusCodeResultTests
{
- public class StatusCodeResultTests
+ [Fact]
+ public void StatusCodeResult_ExecuteResultSetsResponseStatusCode()
{
- [Fact]
- public void StatusCodeResult_ExecuteResultSetsResponseStatusCode()
- {
- // Arrange
- var result = new StatusCodeResult(StatusCodes.Status404NotFound);
+ // Arrange
+ var result = new StatusCodeResult(StatusCodes.Status404NotFound);
- var httpContext = GetHttpContext();
+ var httpContext = GetHttpContext();
- // Act
- result.ExecuteAsync(httpContext);
+ // Act
+ result.ExecuteAsync(httpContext);
- // Assert
- Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode);
+ }
- private static IServiceCollection CreateServices()
- {
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
- return services;
- }
+ private static IServiceCollection CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+ return services;
+ }
- private static HttpContext GetHttpContext()
- {
- var services = CreateServices();
+ private static HttpContext GetHttpContext()
+ {
+ var services = CreateServices();
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = services.BuildServiceProvider();
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = services.BuildServiceProvider();
- return httpContext;
- }
+ return httpContext;
}
}
diff --git a/src/Http/Http.Results/test/TestLinkGenerator.cs b/src/Http/Http.Results/test/TestLinkGenerator.cs
index ec026e13a7..8f8d9e0167 100644
--- a/src/Http/Http.Results/test/TestLinkGenerator.cs
+++ b/src/Http/Http.Results/test/TestLinkGenerator.cs
@@ -4,26 +4,25 @@
using System;
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+internal sealed class TestLinkGenerator : LinkGenerator
{
- internal sealed class TestLinkGenerator : LinkGenerator
- {
- public string Url { get; set; }
+ public string Url { get; set; }
- public override string GetPathByAddress<TAddress>(HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null)
- {
- throw new NotImplementedException();
- }
+ public override string GetPathByAddress<TAddress>(HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
- public override string GetPathByAddress<TAddress>(TAddress address, RouteValueDictionary values, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null)
- {
- throw new NotImplementedException();
- }
+ public override string GetPathByAddress<TAddress>(TAddress address, RouteValueDictionary values, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null)
+ {
+ throw new NotImplementedException();
+ }
- public override string GetUriByAddress<TAddress>(HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues = null, string scheme = null, HostString? host = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null)
- => Url;
+ public override string GetUriByAddress<TAddress>(HttpContext httpContext, TAddress address, RouteValueDictionary values, RouteValueDictionary ambientValues = null, string scheme = null, HostString? host = null, PathString? pathBase = null, FragmentString fragment = default, LinkOptions options = null)
+ => Url;
- public override string GetUriByAddress<TAddress>(TAddress address, RouteValueDictionary values, string scheme, HostString host, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null)
- => Url;
- }
+ public override string GetUriByAddress<TAddress>(TAddress address, RouteValueDictionary values, string scheme, HostString host, PathString pathBase = default, FragmentString fragment = default, LinkOptions options = null)
+ => Url;
}
diff --git a/src/Http/Http.Results/test/UnauthorizedResultTests.cs b/src/Http/Http.Results/test/UnauthorizedResultTests.cs
index f4050fe6e4..f6e2640fa7 100644
--- a/src/Http/Http.Results/test/UnauthorizedResultTests.cs
+++ b/src/Http/Http.Results/test/UnauthorizedResultTests.cs
@@ -3,18 +3,17 @@
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class UnauthorizedResultTests
{
- public class UnauthorizedResultTests
+ [Fact]
+ public void UnauthorizedResult_InitializesStatusCode()
{
- [Fact]
- public void UnauthorizedResult_InitializesStatusCode()
- {
- // Arrange & act
- var result = new UnauthorizedResult();
+ // Arrange & act
+ var result = new UnauthorizedResult();
- // Assert
- Assert.Equal(StatusCodes.Status401Unauthorized, result.StatusCode);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status401Unauthorized, result.StatusCode);
}
}
diff --git a/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs b/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs
index 1d5b68413a..7c26e98fb5 100644
--- a/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs
+++ b/src/Http/Http.Results/test/UnprocessableEntityObjectResultTests.cs
@@ -3,20 +3,19 @@
using Xunit;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class UnprocessableEntityObjectResultTests
{
- public class UnprocessableEntityObjectResultTests
+ [Fact]
+ public void UnprocessableEntityObjectResult_SetsStatusCodeAndValue()
{
- [Fact]
- public void UnprocessableEntityObjectResult_SetsStatusCodeAndValue()
- {
- // Arrange & Act
- var obj = new object();
- var result = new UnprocessableEntityObjectResult(obj);
+ // Arrange & Act
+ var obj = new object();
+ var result = new UnprocessableEntityObjectResult(obj);
- // Assert
- Assert.Equal(StatusCodes.Status422UnprocessableEntity, result.StatusCode);
- Assert.Equal(obj, result.Value);
- }
+ // Assert
+ Assert.Equal(StatusCodes.Status422UnprocessableEntity, result.StatusCode);
+ Assert.Equal(obj, result.Value);
}
}
diff --git a/src/Http/Http.Results/test/VirtualFileResultTest.cs b/src/Http/Http.Results/test/VirtualFileResultTest.cs
index e38ed10778..76946cdb0a 100644
--- a/src/Http/Http.Results/test/VirtualFileResultTest.cs
+++ b/src/Http/Http.Results/test/VirtualFileResultTest.cs
@@ -6,20 +6,19 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Result
+namespace Microsoft.AspNetCore.Http.Result;
+
+public class VirtualFileResultTest : VirtualFileResultTestBase
{
- public class VirtualFileResultTest : VirtualFileResultTestBase
+ protected override Task ExecuteAsync(HttpContext httpContext, string path, string contentType, DateTimeOffset? lastModified = null, EntityTagHeaderValue entityTag = null, bool enableRangeProcessing = false)
{
- protected override Task ExecuteAsync(HttpContext httpContext, string path, string contentType, DateTimeOffset? lastModified = null, EntityTagHeaderValue entityTag = null, bool enableRangeProcessing = false)
+ var result = new VirtualFileResult(path, contentType)
{
- var result = new VirtualFileResult(path, contentType)
- {
- LastModified = lastModified,
- EntityTag = entityTag,
- EnableRangeProcessing = enableRangeProcessing,
- };
+ LastModified = lastModified,
+ EntityTag = entityTag,
+ EnableRangeProcessing = enableRangeProcessing,
+ };
- return result.ExecuteAsync(httpContext);
- }
+ return result.ExecuteAsync(httpContext);
}
}
diff --git a/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs
index 1cf5d3f344..5d9b0ddffe 100644
--- a/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs
+++ b/src/Http/Http/perf/Microbenchmarks/AdaptiveCapacityDictionaryBenchmark.cs
@@ -6,25 +6,25 @@ using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Internal;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class AdaptiveCapacityDictionaryBenchmark
{
- public class AdaptiveCapacityDictionaryBenchmark
- {
- private AdaptiveCapacityDictionary<string, string> _smallCapDict;
- private AdaptiveCapacityDictionary<string, string> _smallCapDictTen;
- private AdaptiveCapacityDictionary<string, string> _filledSmallDictionary;
- private Dictionary<string, string> _dict;
- private Dictionary<string, string> _dictTen;
- private Dictionary<string, string> _filledDictTen;
- private KeyValuePair<string, string> _oneValue;
- private List<KeyValuePair<string, string>> _tenValues;
-
- [IterationSetup]
- public void Setup()
- {
- _oneValue = new KeyValuePair<string, string>("a", "b");
+ private AdaptiveCapacityDictionary<string, string> _smallCapDict;
+ private AdaptiveCapacityDictionary<string, string> _smallCapDictTen;
+ private AdaptiveCapacityDictionary<string, string> _filledSmallDictionary;
+ private Dictionary<string, string> _dict;
+ private Dictionary<string, string> _dictTen;
+ private Dictionary<string, string> _filledDictTen;
+ private KeyValuePair<string, string> _oneValue;
+ private List<KeyValuePair<string, string>> _tenValues;
+
+ [IterationSetup]
+ public void Setup()
+ {
+ _oneValue = new KeyValuePair<string, string>("a", "b");
- _tenValues = new List<KeyValuePair<string, string>>()
+ _tenValues = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("a", "b"),
new KeyValuePair<string, string>("c", "d"),
@@ -38,298 +38,297 @@ namespace Microsoft.AspNetCore.Http
new KeyValuePair<string, string>("s", "t"),
};
- _smallCapDict = new AdaptiveCapacityDictionary<string, string>(capacity: 1, StringComparer.OrdinalIgnoreCase);
- _smallCapDictTen = new AdaptiveCapacityDictionary<string, string>(capacity: 10, StringComparer.OrdinalIgnoreCase);
- _filledSmallDictionary = new AdaptiveCapacityDictionary<string, string>(capacity: 10, StringComparer.OrdinalIgnoreCase);
- foreach (var a in _tenValues)
- {
- _filledSmallDictionary[a.Key] = a.Value;
- }
-
- _dict = new Dictionary<string, string>(1, StringComparer.OrdinalIgnoreCase);
- _dictTen = new Dictionary<string, string>(10, StringComparer.OrdinalIgnoreCase);
- _filledDictTen = new Dictionary<string, string>(10, StringComparer.OrdinalIgnoreCase);
-
- foreach (var a in _tenValues)
- {
- _filledDictTen[a.Key] = a.Value;
- }
- }
-
- [Benchmark]
- public void OneValue_SmallDict()
+ _smallCapDict = new AdaptiveCapacityDictionary<string, string>(capacity: 1, StringComparer.OrdinalIgnoreCase);
+ _smallCapDictTen = new AdaptiveCapacityDictionary<string, string>(capacity: 10, StringComparer.OrdinalIgnoreCase);
+ _filledSmallDictionary = new AdaptiveCapacityDictionary<string, string>(capacity: 10, StringComparer.OrdinalIgnoreCase);
+ foreach (var a in _tenValues)
{
- _smallCapDict[_oneValue.Key] = _oneValue.Value;
- _ = _smallCapDict[_oneValue.Key];
+ _filledSmallDictionary[a.Key] = a.Value;
}
- [Benchmark]
- public void OneValue_Dict()
- {
- _dict[_oneValue.Key] = _oneValue.Value;
- _ = _dict[_oneValue.Key];
- }
+ _dict = new Dictionary<string, string>(1, StringComparer.OrdinalIgnoreCase);
+ _dictTen = new Dictionary<string, string>(10, StringComparer.OrdinalIgnoreCase);
+ _filledDictTen = new Dictionary<string, string>(10, StringComparer.OrdinalIgnoreCase);
- [Benchmark]
- public void OneValue_SmallDict_Set()
+ foreach (var a in _tenValues)
{
- _smallCapDict[_oneValue.Key] = _oneValue.Value;
+ _filledDictTen[a.Key] = a.Value;
}
+ }
- [Benchmark]
- public void OneValue_Dict_Set()
- {
- _dict[_oneValue.Key] = _oneValue.Value;
- }
+ [Benchmark]
+ public void OneValue_SmallDict()
+ {
+ _smallCapDict[_oneValue.Key] = _oneValue.Value;
+ _ = _smallCapDict[_oneValue.Key];
+ }
+ [Benchmark]
+ public void OneValue_Dict()
+ {
+ _dict[_oneValue.Key] = _oneValue.Value;
+ _ = _dict[_oneValue.Key];
+ }
- [Benchmark]
- public void OneValue_SmallDict_Get()
- {
- _smallCapDict.TryGetValue("test", out var val);
- }
+ [Benchmark]
+ public void OneValue_SmallDict_Set()
+ {
+ _smallCapDict[_oneValue.Key] = _oneValue.Value;
+ }
- [Benchmark]
- public void OneValue_Dict_Get()
- {
- _dict.TryGetValue("test", out var val);
- }
+ [Benchmark]
+ public void OneValue_Dict_Set()
+ {
+ _dict[_oneValue.Key] = _oneValue.Value;
+ }
+
+
+ [Benchmark]
+ public void OneValue_SmallDict_Get()
+ {
+ _smallCapDict.TryGetValue("test", out var val);
+ }
+
+ [Benchmark]
+ public void OneValue_Dict_Get()
+ {
+ _dict.TryGetValue("test", out var val);
+ }
- [Benchmark]
- public void FourValues_SmallDict()
+ [Benchmark]
+ public void FourValues_SmallDict()
+ {
+ for (var i = 0; i < 4; i++)
{
- for (var i = 0; i < 4; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void FiveValues_SmallDict()
+ [Benchmark]
+ public void FiveValues_SmallDict()
+ {
+ for (var i = 0; i < 5; i++)
{
- for (var i = 0; i < 5; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void SixValues_SmallDict()
+ [Benchmark]
+ public void SixValues_SmallDict()
+ {
+ for (var i = 0; i < 6; i++)
{
- for (var i = 0; i < 6; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void SevenValues_SmallDict()
+ [Benchmark]
+ public void SevenValues_SmallDict()
+ {
+ for (var i = 0; i < 7; i++)
{
- for (var i = 0; i < 7; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void EightValues_SmallDict()
+ [Benchmark]
+ public void EightValues_SmallDict()
+ {
+ for (var i = 0; i < 8; i++)
{
- for (var i = 0; i < 8; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void NineValues_SmallDict()
+ [Benchmark]
+ public void NineValues_SmallDict()
+ {
+ for (var i = 0; i < 9; i++)
{
- for (var i = 0; i < 9; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void TenValues_SmallDict()
+ [Benchmark]
+ public void TenValues_SmallDict()
+ {
+ for (var i = 0; i < 10; i++)
{
- for (var i = 0; i < 10; i++)
- {
- var val = _tenValues[i];
- _smallCapDictTen[val.Key] = val.Value;
- _ = _smallCapDictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _smallCapDictTen[val.Key] = val.Value;
+ _ = _smallCapDictTen[val.Key];
}
+ }
- [Benchmark]
- public void FourValues_Dict()
+ [Benchmark]
+ public void FourValues_Dict()
+ {
+ for (var i = 0; i < 4; i++)
{
- for (var i = 0; i < 4; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
+ }
- [Benchmark]
- public void FiveValues_Dict()
+ [Benchmark]
+ public void FiveValues_Dict()
+ {
+ for (var i = 0; i < 5; i++)
{
- for (var i = 0; i < 5; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
- [Benchmark]
- public void SixValues_Dict()
+ }
+ [Benchmark]
+ public void SixValues_Dict()
+ {
+ for (var i = 0; i < 6; i++)
{
- for (var i = 0; i < 6; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
- [Benchmark]
- public void SevenValues_Dict()
+ }
+ [Benchmark]
+ public void SevenValues_Dict()
+ {
+ for (var i = 0; i < 7; i++)
{
- for (var i = 0; i < 7; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
- [Benchmark]
- public void EightValues_Dict()
+ }
+ [Benchmark]
+ public void EightValues_Dict()
+ {
+ for (var i = 0; i < 8; i++)
{
- for (var i = 0; i < 8; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
- [Benchmark]
- public void NineValues_Dict()
+ }
+ [Benchmark]
+ public void NineValues_Dict()
+ {
+ for (var i = 0; i < 9; i++)
{
- for (var i = 0; i < 9; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
+ }
- [Benchmark]
- public void TenValues_Dict()
+ [Benchmark]
+ public void TenValues_Dict()
+ {
+ for (var i = 0; i < 10; i++)
{
- for (var i = 0; i < 10; i++)
- {
- var val = _tenValues[i];
- _dictTen[val.Key] = val.Value;
- _ = _dictTen[val.Key];
- }
+ var val = _tenValues[i];
+ _dictTen[val.Key] = val.Value;
+ _ = _dictTen[val.Key];
}
+ }
- [Benchmark]
- public void FourValues_SmallDictGet()
- {
- _ = _filledSmallDictionary["g"];
- }
+ [Benchmark]
+ public void FourValues_SmallDictGet()
+ {
+ _ = _filledSmallDictionary["g"];
+ }
- [Benchmark]
- public void FiveValues_SmallDictGet()
- {
- _ = _filledSmallDictionary["i"];
- }
+ [Benchmark]
+ public void FiveValues_SmallDictGet()
+ {
+ _ = _filledSmallDictionary["i"];
+ }
- [Benchmark]
- public void SixValues_SmallDictGetGet()
- {
- _ = _filledSmallDictionary["k"];
+ [Benchmark]
+ public void SixValues_SmallDictGetGet()
+ {
+ _ = _filledSmallDictionary["k"];
- }
+ }
- [Benchmark]
- public void SevenValues_SmallDictGetGet()
- {
- _ = _filledSmallDictionary["m"];
- }
+ [Benchmark]
+ public void SevenValues_SmallDictGetGet()
+ {
+ _ = _filledSmallDictionary["m"];
+ }
- [Benchmark]
- public void EightValues_SmallDictGet()
- {
- _ = _filledSmallDictionary["o"];
- }
+ [Benchmark]
+ public void EightValues_SmallDictGet()
+ {
+ _ = _filledSmallDictionary["o"];
+ }
- [Benchmark]
- public void NineValues_SmallDictGet()
- {
- _ = _filledSmallDictionary["q"];
- }
+ [Benchmark]
+ public void NineValues_SmallDictGet()
+ {
+ _ = _filledSmallDictionary["q"];
+ }
- [Benchmark]
- public void TenValues_SmallDictGet()
- {
- _ = _filledSmallDictionary["s"];
- }
+ [Benchmark]
+ public void TenValues_SmallDictGet()
+ {
+ _ = _filledSmallDictionary["s"];
+ }
- [Benchmark]
- public void TenValues_DictGet()
- {
- _ = _filledDictTen["s"];
- }
+ [Benchmark]
+ public void TenValues_DictGet()
+ {
+ _ = _filledDictTen["s"];
+ }
- [Benchmark]
- public void SmallDict()
- {
- _ = new AdaptiveCapacityDictionary<string, string>(capacity: 1);
- }
+ [Benchmark]
+ public void SmallDict()
+ {
+ _ = new AdaptiveCapacityDictionary<string, string>(capacity: 1);
+ }
- [Benchmark]
- public void Dict()
- {
- _ = new Dictionary<string, string>(capacity: 1);
- }
+ [Benchmark]
+ public void Dict()
+ {
+ _ = new Dictionary<string, string>(capacity: 1);
+ }
- [Benchmark]
- public void SmallDictFour()
- {
- _ = new AdaptiveCapacityDictionary<string, string>(capacity: 4);
- }
+ [Benchmark]
+ public void SmallDictFour()
+ {
+ _ = new AdaptiveCapacityDictionary<string, string>(capacity: 4);
+ }
- [Benchmark]
- public void DictFour()
- {
- _ = new Dictionary<string, string>(capacity: 4);
- }
+ [Benchmark]
+ public void DictFour()
+ {
+ _ = new Dictionary<string, string>(capacity: 4);
+ }
- [Benchmark]
- public void SmallDictTen()
- {
- _ = new AdaptiveCapacityDictionary<string, string>(capacity: 10);
- }
+ [Benchmark]
+ public void SmallDictTen()
+ {
+ _ = new AdaptiveCapacityDictionary<string, string>(capacity: 10);
+ }
- [Benchmark]
- public void DictTen()
- {
- _ = new Dictionary<string, string>(capacity: 10);
- }
+ [Benchmark]
+ public void DictTen()
+ {
+ _ = new Dictionary<string, string>(capacity: 10);
}
}
diff --git a/src/Http/Http/perf/Microbenchmarks/HeaderUtilitiesBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/HeaderUtilitiesBenchmark.cs
index 05b3a8a46e..e1597ef619 100644
--- a/src/Http/Http/perf/Microbenchmarks/HeaderUtilitiesBenchmark.cs
+++ b/src/Http/Http/perf/Microbenchmarks/HeaderUtilitiesBenchmark.cs
@@ -5,20 +5,19 @@ using System;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+public class HeaderUtilitiesBenchmark
{
- public class HeaderUtilitiesBenchmark
+ [Benchmark]
+ public StringSegment UnescapeAsQuotedString()
{
- [Benchmark]
- public StringSegment UnescapeAsQuotedString()
- {
- return HeaderUtilities.UnescapeAsQuotedString("\"hello\\\"foo\\\\bar\\\\baz\\\\\"");
- }
+ return HeaderUtilities.UnescapeAsQuotedString("\"hello\\\"foo\\\\bar\\\\baz\\\\\"");
+ }
- [Benchmark]
- public StringSegment EscapeAsQuotedString()
- {
- return HeaderUtilities.EscapeAsQuotedString("\"hello\\\"foo\\\\bar\\\\baz\\\\\"");
- }
+ [Benchmark]
+ public StringSegment EscapeAsQuotedString()
+ {
+ return HeaderUtilities.EscapeAsQuotedString("\"hello\\\"foo\\\\bar\\\\baz\\\\\"");
}
}
diff --git a/src/Http/Http/perf/Microbenchmarks/QueryCollectionBenchmarks.cs b/src/Http/Http/perf/Microbenchmarks/QueryCollectionBenchmarks.cs
index c7368a2c88..6d5c8a93f6 100644
--- a/src/Http/Http/perf/Microbenchmarks/QueryCollectionBenchmarks.cs
+++ b/src/Http/Http/perf/Microbenchmarks/QueryCollectionBenchmarks.cs
@@ -7,91 +7,90 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using static Microsoft.AspNetCore.Http.Features.QueryFeature;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
+[CategoriesColumn]
+public class QueryCollectionBenchmarks
{
- [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
- [CategoriesColumn]
- public class QueryCollectionBenchmarks
- {
- private string _queryString;
- private string _singleValue;
- private string _singleValueWithPlus;
- private string _encoded;
+ private string _queryString;
+ private string _singleValue;
+ private string _singleValueWithPlus;
+ private string _encoded;
- [IterationSetup]
- public void Setup()
- {
- _queryString = "?key1=value1&key2=value2&key3=value3&key4=&key5=";
- _singleValue = "?key1=value1";
- _singleValueWithPlus = "?key1=value1+value2+value3";
- _encoded = "?key1=value%231";
- }
+ [IterationSetup]
+ public void Setup()
+ {
+ _queryString = "?key1=value1&key2=value2&key3=value3&key4=&key5=";
+ _singleValue = "?key1=value1";
+ _singleValueWithPlus = "?key1=value1+value2+value3";
+ _encoded = "?key1=value%231";
+ }
- [Benchmark(Description = "ParseNew")]
- [BenchmarkCategory("QueryString")]
- public void ParseNew()
- {
- _ = QueryFeature.ParseNullableQueryInternal(_queryString);
- }
+ [Benchmark(Description = "ParseNew")]
+ [BenchmarkCategory("QueryString")]
+ public void ParseNew()
+ {
+ _ = QueryFeature.ParseNullableQueryInternal(_queryString);
+ }
- [Benchmark(Description = "ParseNew")]
- [BenchmarkCategory("Single")]
- public void ParseNewSingle()
- {
- _ = QueryFeature.ParseNullableQueryInternal(_singleValue);
- }
+ [Benchmark(Description = "ParseNew")]
+ [BenchmarkCategory("Single")]
+ public void ParseNewSingle()
+ {
+ _ = QueryFeature.ParseNullableQueryInternal(_singleValue);
+ }
- [Benchmark(Description = "ParseNew")]
- [BenchmarkCategory("SingleWithPlus")]
- public void ParseNewSingleWithPlus()
- {
- _ = QueryFeature.ParseNullableQueryInternal(_singleValueWithPlus);
- }
+ [Benchmark(Description = "ParseNew")]
+ [BenchmarkCategory("SingleWithPlus")]
+ public void ParseNewSingleWithPlus()
+ {
+ _ = QueryFeature.ParseNullableQueryInternal(_singleValueWithPlus);
+ }
- [Benchmark(Description = "ParseNew")]
- [BenchmarkCategory("Encoded")]
- public void ParseNewEncoded()
- {
- _ = QueryFeature.ParseNullableQueryInternal(_encoded);
- }
+ [Benchmark(Description = "ParseNew")]
+ [BenchmarkCategory("Encoded")]
+ public void ParseNewEncoded()
+ {
+ _ = QueryFeature.ParseNullableQueryInternal(_encoded);
+ }
- [Benchmark(Description = "QueryHelpersParse")]
- [BenchmarkCategory("QueryString")]
- public void QueryHelpersParse()
- {
- _ = QueryHelpers.ParseNullableQuery(_queryString);
- }
+ [Benchmark(Description = "QueryHelpersParse")]
+ [BenchmarkCategory("QueryString")]
+ public void QueryHelpersParse()
+ {
+ _ = QueryHelpers.ParseNullableQuery(_queryString);
+ }
- [Benchmark(Description = "QueryHelpersParse")]
- [BenchmarkCategory("Single")]
- public void QueryHelpersParseSingle()
- {
- _ = QueryHelpers.ParseNullableQuery(_singleValue);
- }
+ [Benchmark(Description = "QueryHelpersParse")]
+ [BenchmarkCategory("Single")]
+ public void QueryHelpersParseSingle()
+ {
+ _ = QueryHelpers.ParseNullableQuery(_singleValue);
+ }
- [Benchmark(Description = "QueryHelpersParse")]
- [BenchmarkCategory("SingleWithPlus")]
- public void QueryHelpersParseSingleWithPlus()
- {
- _ = QueryHelpers.ParseNullableQuery(_singleValueWithPlus);
- }
+ [Benchmark(Description = "QueryHelpersParse")]
+ [BenchmarkCategory("SingleWithPlus")]
+ public void QueryHelpersParseSingleWithPlus()
+ {
+ _ = QueryHelpers.ParseNullableQuery(_singleValueWithPlus);
+ }
- [Benchmark(Description = "QueryHelpersParse")]
- [BenchmarkCategory("Encoded")]
- public void QueryHelpersParseEncoded()
- {
- _ = QueryHelpers.ParseNullableQuery(_encoded);
- }
+ [Benchmark(Description = "QueryHelpersParse")]
+ [BenchmarkCategory("Encoded")]
+ public void QueryHelpersParseEncoded()
+ {
+ _ = QueryHelpers.ParseNullableQuery(_encoded);
+ }
- [Benchmark]
- [BenchmarkCategory("Constructor")]
- public void Constructor()
+ [Benchmark]
+ [BenchmarkCategory("Constructor")]
+ public void Constructor()
+ {
+ var dict = new KvpAccumulator();
+ if (dict.HasValues)
{
- var dict = new KvpAccumulator();
- if (dict.HasValues)
- {
- return;
- }
+ return;
}
}
}
diff --git a/src/Http/Http/perf/Microbenchmarks/RequestCookieCollectionBenchmarks.cs b/src/Http/Http/perf/Microbenchmarks/RequestCookieCollectionBenchmarks.cs
index 219a077886..b68a170809 100644
--- a/src/Http/Http/perf/Microbenchmarks/RequestCookieCollectionBenchmarks.cs
+++ b/src/Http/Http/perf/Microbenchmarks/RequestCookieCollectionBenchmarks.cs
@@ -4,22 +4,21 @@
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class RequestCookieCollectionBenchmarks
{
- public class RequestCookieCollectionBenchmarks
- {
- private StringValues _cookie;
+ private StringValues _cookie;
- [IterationSetup]
- public void Setup()
- {
- _cookie = ".AspNetCore.Cookies=CfDJ8BAklVa9EYREk8_ipRUUYJYhRsleKr485k18s_q5XD6vcRJ-DtowUuLCwwMiY728zRZ3rVFY3DEcXDAQUOTtg1e4tkSIVmYLX38Q6mqdFFyw-8dksclDywe9vnN84cEWvfV0wP3EgOsJGHaND7kTJ47gr7Pc1tLHWOm4Pe7Q1vrT9EkcTMr1Wts3aptBl3bdOLLqjmSdgk-OI7qG7uQGz1OGdnSer6-KLUPBcfXblzs4YCjvwu3bGnM42xLGtkZNIF8izPpyqKkIf7ec6O6LEHMp4gcq86PGHCXHn5NKuNSD";
- }
+ [IterationSetup]
+ public void Setup()
+ {
+ _cookie = ".AspNetCore.Cookies=CfDJ8BAklVa9EYREk8_ipRUUYJYhRsleKr485k18s_q5XD6vcRJ-DtowUuLCwwMiY728zRZ3rVFY3DEcXDAQUOTtg1e4tkSIVmYLX38Q6mqdFFyw-8dksclDywe9vnN84cEWvfV0wP3EgOsJGHaND7kTJ47gr7Pc1tLHWOm4Pe7Q1vrT9EkcTMr1Wts3aptBl3bdOLLqjmSdgk-OI7qG7uQGz1OGdnSer6-KLUPBcfXblzs4YCjvwu3bGnM42xLGtkZNIF8izPpyqKkIf7ec6O6LEHMp4gcq86PGHCXHn5NKuNSD";
+ }
- [Benchmark]
- public void Parse_TypicalCookie()
- {
- _ = RequestCookieCollection.Parse(_cookie);
- }
+ [Benchmark]
+ public void Parse_TypicalCookie()
+ {
+ _ = RequestCookieCollection.Parse(_cookie);
}
}
diff --git a/src/Http/Http/perf/Microbenchmarks/RouteValueDictionaryBenchmark.cs b/src/Http/Http/perf/Microbenchmarks/RouteValueDictionaryBenchmark.cs
index 2ea18befda..1544409586 100644
--- a/src/Http/Http/perf/Microbenchmarks/RouteValueDictionaryBenchmark.cs
+++ b/src/Http/Http/perf/Microbenchmarks/RouteValueDictionaryBenchmark.cs
@@ -4,322 +4,321 @@
using System;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteValueDictionaryBenchmark
{
- public class RouteValueDictionaryBenchmark
- {
- private RouteValueDictionary _arrayValues;
- private RouteValueDictionary _propertyValues;
- private RouteValueDictionary _arrayValuesEmpty;
+ private RouteValueDictionary _arrayValues;
+ private RouteValueDictionary _propertyValues;
+ private RouteValueDictionary _arrayValuesEmpty;
- // We modify the route value dictionaries in many of these benchmarks.
- [IterationSetup]
- public void Setup()
- {
- _arrayValues = new RouteValueDictionary()
+ // We modify the route value dictionaries in many of these benchmarks.
+ [IterationSetup]
+ public void Setup()
+ {
+ _arrayValues = new RouteValueDictionary()
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" },
};
- _arrayValuesEmpty = new RouteValueDictionary();
- _propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
- }
+ _arrayValuesEmpty = new RouteValueDictionary();
+ _propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
+ }
- [Benchmark]
- public void Ctor_Values_RouteValueDictionary_EmptyArray()
- {
- new RouteValueDictionary(_arrayValuesEmpty);
- }
+ [Benchmark]
+ public void Ctor_Values_RouteValueDictionary_EmptyArray()
+ {
+ new RouteValueDictionary(_arrayValuesEmpty);
+ }
- [Benchmark]
- public RouteValueDictionary Ctor_Values_RouteValueDictionary_Array()
- {
- return new RouteValueDictionary(_arrayValues);
- }
+ [Benchmark]
+ public RouteValueDictionary Ctor_Values_RouteValueDictionary_Array()
+ {
+ return new RouteValueDictionary(_arrayValues);
+ }
- [Benchmark]
- public RouteValueDictionary AddSingleItem()
- {
- var dictionary = new RouteValueDictionary
+ [Benchmark]
+ public RouteValueDictionary AddSingleItem()
+ {
+ var dictionary = new RouteValueDictionary
{
{ "action", "Index" }
};
- return dictionary;
- }
+ return dictionary;
+ }
- [Benchmark]
- public RouteValueDictionary AddThreeItems()
- {
- var dictionary = new RouteValueDictionary
+ [Benchmark]
+ public RouteValueDictionary AddThreeItems()
+ {
+ var dictionary = new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "15" }
};
- return dictionary;
- }
+ return dictionary;
+ }
- [Benchmark]
- public void ContainsKey_Array_Found()
- {
- _arrayValues.ContainsKey("id");
- }
+ [Benchmark]
+ public void ContainsKey_Array_Found()
+ {
+ _arrayValues.ContainsKey("id");
+ }
- [Benchmark]
- public void ContainsKey_Array_NotFound()
- {
- _arrayValues.ContainsKey("name");
- }
+ [Benchmark]
+ public void ContainsKey_Array_NotFound()
+ {
+ _arrayValues.ContainsKey("name");
+ }
- [Benchmark]
- public void ContainsKey_Properties_Found()
- {
- _propertyValues.ContainsKey("id");
- }
+ [Benchmark]
+ public void ContainsKey_Properties_Found()
+ {
+ _propertyValues.ContainsKey("id");
+ }
- [Benchmark]
- public void ContainsKey_Properties_NotFound()
- {
- _propertyValues.ContainsKey("name");
- }
+ [Benchmark]
+ public void ContainsKey_Properties_NotFound()
+ {
+ _propertyValues.ContainsKey("name");
+ }
- [Benchmark]
- public void TryAdd_Properties_AtCapacity_KeyExists()
- {
- var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17", area = "root" });
- propertyValues.TryAdd("id", "15");
- }
+ [Benchmark]
+ public void TryAdd_Properties_AtCapacity_KeyExists()
+ {
+ var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17", area = "root" });
+ propertyValues.TryAdd("id", "15");
+ }
- [Benchmark]
- public void TryAdd_Properties_AtCapacity_KeyDoesNotExist()
- {
- var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17", area = "root" });
- _propertyValues.TryAdd("name", "Service");
- }
+ [Benchmark]
+ public void TryAdd_Properties_AtCapacity_KeyDoesNotExist()
+ {
+ var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17", area = "root" });
+ _propertyValues.TryAdd("name", "Service");
+ }
- [Benchmark]
- public void TryAdd_Properties_NotAtCapacity_KeyExists()
- {
- var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
- propertyValues.TryAdd("id", "15");
- }
+ [Benchmark]
+ public void TryAdd_Properties_NotAtCapacity_KeyExists()
+ {
+ var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
+ propertyValues.TryAdd("id", "15");
+ }
- [Benchmark]
- public void TryAdd_Properties_NotAtCapacity_KeyDoesNotExist()
- {
- var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
- _propertyValues.TryAdd("name", "Service");
- }
+ [Benchmark]
+ public void TryAdd_Properties_NotAtCapacity_KeyDoesNotExist()
+ {
+ var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
+ _propertyValues.TryAdd("name", "Service");
+ }
- [Benchmark]
- public void TryAdd_Array_AtCapacity_KeyExists()
- {
- var arrayValues = new RouteValueDictionary
+ [Benchmark]
+ public void TryAdd_Array_AtCapacity_KeyExists()
+ {
+ var arrayValues = new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" },
{ "area", "root" }
};
- arrayValues.TryAdd("id", "15");
- }
+ arrayValues.TryAdd("id", "15");
+ }
- [Benchmark]
- public void TryAdd_Array_AtCapacity_KeyDoesNotExist()
- {
- var arrayValues = new RouteValueDictionary
+ [Benchmark]
+ public void TryAdd_Array_AtCapacity_KeyDoesNotExist()
+ {
+ var arrayValues = new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" },
{ "area", "root" }
};
- arrayValues.TryAdd("name", "Service");
- }
+ arrayValues.TryAdd("name", "Service");
+ }
- [Benchmark]
- public void TryAdd_Array_NotAtCapacity_KeyExists()
- {
- var arrayValues = new RouteValueDictionary
+ [Benchmark]
+ public void TryAdd_Array_NotAtCapacity_KeyExists()
+ {
+ var arrayValues = new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" }
};
- arrayValues.TryAdd("id", "15");
- }
+ arrayValues.TryAdd("id", "15");
+ }
- [Benchmark]
- public void TryAdd_Array_NotAtCapacity_KeyDoesNotExist()
- {
- var arrayValues = new RouteValueDictionary
+ [Benchmark]
+ public void TryAdd_Array_NotAtCapacity_KeyDoesNotExist()
+ {
+ var arrayValues = new RouteValueDictionary
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" },
};
- arrayValues.TryAdd("name", "Service");
- }
+ arrayValues.TryAdd("name", "Service");
+ }
- [Benchmark]
- public void ConditionalAdd_Array()
- {
- var arrayValues = new RouteValueDictionary()
+ [Benchmark]
+ public void ConditionalAdd_Array()
+ {
+ var arrayValues = new RouteValueDictionary()
{
{ "action", "Index" },
{ "controller", "Home" },
{ "id", "17" },
};
- if (!arrayValues.ContainsKey("name"))
- {
- arrayValues.Add("name", "Service");
- }
- }
-
- [Benchmark]
- public void ConditionalAdd_Properties()
+ if (!arrayValues.ContainsKey("name"))
{
- var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
-
- if (!propertyValues.ContainsKey("name"))
- {
- propertyValues.Add("name", "Service");
- }
+ arrayValues.Add("name", "Service");
}
+ }
- [Benchmark]
- public RouteValueDictionary ConditionalAdd_ContainsKey_Array()
- {
- var dictionary = _arrayValues;
-
- if (!dictionary.ContainsKey("action"))
- {
- dictionary.Add("action", "Index");
- }
-
- if (!dictionary.ContainsKey("controller"))
- {
- dictionary.Add("controller", "Home");
- }
-
- if (!dictionary.ContainsKey("area"))
- {
- dictionary.Add("area", "Admin");
- }
-
- return dictionary;
- }
+ [Benchmark]
+ public void ConditionalAdd_Properties()
+ {
+ var propertyValues = new RouteValueDictionary(new { action = "Index", controller = "Home", id = "17" });
- [Benchmark]
- public RouteValueDictionary ConditionalAdd_TryAdd()
+ if (!propertyValues.ContainsKey("name"))
{
- var dictionary = _arrayValues;
-
- dictionary.TryAdd("action", "Index");
- dictionary.TryAdd("controller", "Home");
- dictionary.TryAdd("area", "Admin");
-
- return dictionary;
+ propertyValues.Add("name", "Service");
}
+ }
- [Benchmark]
- public RouteValueDictionary ForEachThreeItems_Array()
- {
- var dictionary = _arrayValues;
- foreach (var kvp in dictionary)
- {
- GC.KeepAlive(kvp.Value);
- }
- return dictionary;
- }
+ [Benchmark]
+ public RouteValueDictionary ConditionalAdd_ContainsKey_Array()
+ {
+ var dictionary = _arrayValues;
- [Benchmark]
- public RouteValueDictionary ForEachThreeItems_Properties()
+ if (!dictionary.ContainsKey("action"))
{
- var dictionary = _propertyValues;
- foreach (var kvp in dictionary)
- {
- GC.KeepAlive(kvp.Value);
- }
- return dictionary;
+ dictionary.Add("action", "Index");
}
- [Benchmark]
- public RouteValueDictionary GetThreeItems_Array()
+ if (!dictionary.ContainsKey("controller"))
{
- var dictionary = _arrayValues;
- GC.KeepAlive(dictionary["action"]);
- GC.KeepAlive(dictionary["controller"]);
- GC.KeepAlive(dictionary["id"]);
- return dictionary;
+ dictionary.Add("controller", "Home");
}
- [Benchmark]
- public RouteValueDictionary GetThreeItems_Properties()
+ if (!dictionary.ContainsKey("area"))
{
- var dictionary = _propertyValues;
- GC.KeepAlive(dictionary["action"]);
- GC.KeepAlive(dictionary["controller"]);
- GC.KeepAlive(dictionary["id"]);
- return dictionary;
+ dictionary.Add("area", "Admin");
}
- [Benchmark]
- public RouteValueDictionary SetSingleItem()
- {
- var dictionary = new RouteValueDictionary
- {
- ["action"] = "Index"
- };
- return dictionary;
- }
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary ConditionalAdd_TryAdd()
+ {
+ var dictionary = _arrayValues;
+
+ dictionary.TryAdd("action", "Index");
+ dictionary.TryAdd("controller", "Home");
+ dictionary.TryAdd("area", "Admin");
- [Benchmark]
- public RouteValueDictionary SetExistingItem()
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary ForEachThreeItems_Array()
+ {
+ var dictionary = _arrayValues;
+ foreach (var kvp in dictionary)
{
- var dictionary = _arrayValues;
- dictionary["action"] = "About";
- return dictionary;
+ GC.KeepAlive(kvp.Value);
}
+ return dictionary;
+ }
- [Benchmark]
- public RouteValueDictionary SetThreeItems()
+ [Benchmark]
+ public RouteValueDictionary ForEachThreeItems_Properties()
+ {
+ var dictionary = _propertyValues;
+ foreach (var kvp in dictionary)
{
- var dictionary = new RouteValueDictionary
- {
- ["action"] = "Index",
- ["controller"] = "Home",
- ["id"] = "15"
- };
- return dictionary;
+ GC.KeepAlive(kvp.Value);
}
+ return dictionary;
+ }
- [Benchmark]
- public RouteValueDictionary TryGetValueThreeItems_Array()
+ [Benchmark]
+ public RouteValueDictionary GetThreeItems_Array()
+ {
+ var dictionary = _arrayValues;
+ GC.KeepAlive(dictionary["action"]);
+ GC.KeepAlive(dictionary["controller"]);
+ GC.KeepAlive(dictionary["id"]);
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary GetThreeItems_Properties()
+ {
+ var dictionary = _propertyValues;
+ GC.KeepAlive(dictionary["action"]);
+ GC.KeepAlive(dictionary["controller"]);
+ GC.KeepAlive(dictionary["id"]);
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary SetSingleItem()
+ {
+ var dictionary = new RouteValueDictionary
{
- var dictionary = _arrayValues;
- dictionary.TryGetValue("action", out var action);
- dictionary.TryGetValue("controller", out var controller);
- dictionary.TryGetValue("id", out var id);
- GC.KeepAlive(action);
- GC.KeepAlive(controller);
- GC.KeepAlive(id);
- return dictionary;
- }
+ ["action"] = "Index"
+ };
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary SetExistingItem()
+ {
+ var dictionary = _arrayValues;
+ dictionary["action"] = "About";
+ return dictionary;
+ }
- [Benchmark]
- public RouteValueDictionary TryGetValueThreeItems_Properties()
+ [Benchmark]
+ public RouteValueDictionary SetThreeItems()
+ {
+ var dictionary = new RouteValueDictionary
{
- var dictionary = _propertyValues;
- dictionary.TryGetValue("action", out var action);
- dictionary.TryGetValue("controller", out var controller);
- dictionary.TryGetValue("id", out var id);
- GC.KeepAlive(action);
- GC.KeepAlive(controller);
- GC.KeepAlive(id);
- return dictionary;
- }
+ ["action"] = "Index",
+ ["controller"] = "Home",
+ ["id"] = "15"
+ };
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary TryGetValueThreeItems_Array()
+ {
+ var dictionary = _arrayValues;
+ dictionary.TryGetValue("action", out var action);
+ dictionary.TryGetValue("controller", out var controller);
+ dictionary.TryGetValue("id", out var id);
+ GC.KeepAlive(action);
+ GC.KeepAlive(controller);
+ GC.KeepAlive(id);
+ return dictionary;
+ }
+
+ [Benchmark]
+ public RouteValueDictionary TryGetValueThreeItems_Properties()
+ {
+ var dictionary = _propertyValues;
+ dictionary.TryGetValue("action", out var action);
+ dictionary.TryGetValue("controller", out var controller);
+ dictionary.TryGetValue("id", out var id);
+ GC.KeepAlive(action);
+ GC.KeepAlive(controller);
+ GC.KeepAlive(id);
+ return dictionary;
}
}
diff --git a/src/Http/Http/src/BindingAddress.cs b/src/Http/Http/src/BindingAddress.cs
index 6a5a135d6a..ff61870954 100644
--- a/src/Http/Http/src/BindingAddress.cs
+++ b/src/Http/Http/src/BindingAddress.cs
@@ -5,229 +5,228 @@ using System;
using System.Globalization;
using System.IO;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// An address that a HTTP server may bind to.
+/// </summary>
+public class BindingAddress
{
+ private const string UnixPipeHostPrefix = "unix:/";
+
+ private BindingAddress(string host, string pathBase, int port, string scheme)
+ {
+ Host = host;
+ PathBase = pathBase;
+ Port = port;
+ Scheme = scheme;
+ }
+
/// <summary>
- /// An address that a HTTP server may bind to.
+ /// Initializes a new instance of <see cref="BindingAddress"/>.
/// </summary>
- public class BindingAddress
+ [Obsolete("This constructor is obsolete and will be removed in a future version. Use BindingAddress.Parse(address) to create a BindingAddress instance.")]
+ public BindingAddress()
{
- private const string UnixPipeHostPrefix = "unix:/";
+ throw new InvalidOperationException("This constructor is obsolete and will be removed in a future version. Use BindingAddress.Parse(address) to create a BindingAddress instance.");
+ }
- private BindingAddress(string host, string pathBase, int port, string scheme)
- {
- Host = host;
- PathBase = pathBase;
- Port = port;
- Scheme = scheme;
- }
+ /// <summary>
+ /// Gets the host component.
+ /// </summary>
+ public string Host { get; }
- /// <summary>
- /// Initializes a new instance of <see cref="BindingAddress"/>.
- /// </summary>
- [Obsolete("This constructor is obsolete and will be removed in a future version. Use BindingAddress.Parse(address) to create a BindingAddress instance.")]
- public BindingAddress()
- {
- throw new InvalidOperationException("This constructor is obsolete and will be removed in a future version. Use BindingAddress.Parse(address) to create a BindingAddress instance.");
- }
+ /// <summary>
+ /// Gets the path component.
+ /// </summary>
+ public string PathBase { get; }
- /// <summary>
- /// Gets the host component.
- /// </summary>
- public string Host { get; }
-
- /// <summary>
- /// Gets the path component.
- /// </summary>
- public string PathBase { get; }
-
- /// <summary>
- /// Gets the port.
- /// </summary>
- public int Port { get; }
-
- /// <summary>
- /// Gets the scheme component.
- /// </summary>
- public string Scheme { get; }
-
- /// <summary>
- /// Gets a value that determines if this instance represents a Unix pipe.
- /// <para>
- /// Returns <see langword="true"/> if <see cref="Host"/> starts with <c>unix://</c> prefix.
- /// </para>
- /// </summary>
- public bool IsUnixPipe => Host.StartsWith(UnixPipeHostPrefix, StringComparison.Ordinal);
-
- /// <summary>
- /// Gets the unix pipe path if this instance represents a Unix pipe.
- /// </summary>
- public string UnixPipePath
- {
- get
- {
- if (!IsUnixPipe)
- {
- throw new InvalidOperationException("Binding address is not a unix pipe.");
- }
+ /// <summary>
+ /// Gets the port.
+ /// </summary>
+ public int Port { get; }
- return GetUnixPipePath(Host);
- }
- }
+ /// <summary>
+ /// Gets the scheme component.
+ /// </summary>
+ public string Scheme { get; }
- private static string GetUnixPipePath(string host)
+ /// <summary>
+ /// Gets a value that determines if this instance represents a Unix pipe.
+ /// <para>
+ /// Returns <see langword="true"/> if <see cref="Host"/> starts with <c>unix://</c> prefix.
+ /// </para>
+ /// </summary>
+ public bool IsUnixPipe => Host.StartsWith(UnixPipeHostPrefix, StringComparison.Ordinal);
+
+ /// <summary>
+ /// Gets the unix pipe path if this instance represents a Unix pipe.
+ /// </summary>
+ public string UnixPipePath
+ {
+ get
{
- var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
- if (!OperatingSystem.IsWindows())
+ if (!IsUnixPipe)
{
- // "/" character in unix refers to root. Windows has drive letters and volume separator (c:)
- unixPipeHostPrefixLength--;
+ throw new InvalidOperationException("Binding address is not a unix pipe.");
}
- return host.Substring(unixPipeHostPrefixLength);
+
+ return GetUnixPipePath(Host);
}
+ }
- /// <inheritdoc />
- public override string ToString()
+ private static string GetUnixPipePath(string host)
+ {
+ var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
+ if (!OperatingSystem.IsWindows())
{
- if (IsUnixPipe)
- {
- return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant();
- }
- else
- {
- return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase;
- }
+ // "/" character in unix refers to root. Windows has drive letters and volume separator (c:)
+ unixPipeHostPrefixLength--;
}
+ return host.Substring(unixPipeHostPrefixLength);
+ }
- /// <inheritdoc />
- public override int GetHashCode()
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ if (IsUnixPipe)
{
- return ToString().GetHashCode();
+ return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant();
}
-
- /// <inheritdoc />
- public override bool Equals(object? obj)
+ else
{
- 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;
+ return Scheme.ToLowerInvariant() + Uri.SchemeDelimiter + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase;
}
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ return ToString().GetHashCode();
+ }
- /// <summary>
- /// Parses the specified <paramref name="address"/> as a <see cref="BindingAddress"/>.
- /// </summary>
- /// <param name="address">The address to parse.</param>
- /// <returns>The parsed address.</returns>
- public static BindingAddress Parse(string address)
+ /// <inheritdoc />
+ public override bool Equals(object? obj)
+ {
+ var other = obj as BindingAddress;
+ if (other == null)
{
- // A null/empty address will throw FormatException
- address = address ?? string.Empty;
+ return false;
+ }
+ return string.Equals(Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(Host, other.Host, StringComparison.OrdinalIgnoreCase)
+ && Port == other.Port
+ && PathBase == other.PathBase;
+ }
- var schemeDelimiterStart = address.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
- if (schemeDelimiterStart < 0)
- {
- throw new FormatException($"Invalid url: '{address}'");
- }
- var schemeDelimiterEnd = schemeDelimiterStart + Uri.SchemeDelimiter.Length;
+ /// <summary>
+ /// Parses the specified <paramref name="address"/> as a <see cref="BindingAddress"/>.
+ /// </summary>
+ /// <param name="address">The address to parse.</param>
+ /// <returns>The parsed address.</returns>
+ public static BindingAddress Parse(string address)
+ {
+ // A null/empty address will throw FormatException
+ address = address ?? string.Empty;
- var isUnixPipe = address.IndexOf(UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
+ var schemeDelimiterStart = address.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
+ if (schemeDelimiterStart < 0)
+ {
+ throw new FormatException($"Invalid url: '{address}'");
+ }
+ var schemeDelimiterEnd = schemeDelimiterStart + Uri.SchemeDelimiter.Length;
- int pathDelimiterStart;
- int pathDelimiterEnd;
- if (!isUnixPipe)
- {
- pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
- pathDelimiterEnd = pathDelimiterStart;
- }
- else
+ var isUnixPipe = address.IndexOf(UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
+
+ int pathDelimiterStart;
+ int pathDelimiterEnd;
+ if (!isUnixPipe)
+ {
+ pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
+ pathDelimiterEnd = pathDelimiterStart;
+ }
+ else
+ {
+ var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
+ if (OperatingSystem.IsWindows())
{
- var unixPipeHostPrefixLength = UnixPipeHostPrefix.Length;
- if (OperatingSystem.IsWindows())
+ // Windows has drive letters and volume separator (c:)
+ unixPipeHostPrefixLength += 2;
+ if (schemeDelimiterEnd + unixPipeHostPrefixLength > address.Length)
{
- // Windows has drive letters and volume separator (c:)
- unixPipeHostPrefixLength += 2;
- if (schemeDelimiterEnd + unixPipeHostPrefixLength > address.Length)
- {
- throw new FormatException($"Invalid url: '{address}'");
- }
+ throw new FormatException($"Invalid url: '{address}'");
}
-
- pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + unixPipeHostPrefixLength, StringComparison.Ordinal);
- pathDelimiterEnd = pathDelimiterStart + ":".Length;
}
- if (pathDelimiterStart < 0)
- {
- pathDelimiterStart = pathDelimiterEnd = address.Length;
- }
+ pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + unixPipeHostPrefixLength, StringComparison.Ordinal);
+ pathDelimiterEnd = pathDelimiterStart + ":".Length;
+ }
+
+ if (pathDelimiterStart < 0)
+ {
+ pathDelimiterStart = pathDelimiterEnd = address.Length;
+ }
- var scheme = address.Substring(0, schemeDelimiterStart);
- string? host = null;
- var port = 0;
+ var scheme = address.Substring(0, schemeDelimiterStart);
+ string? host = null;
+ var port = 0;
- var hasSpecifiedPort = false;
- if (!isUnixPipe)
+ var hasSpecifiedPort = false;
+ if (!isUnixPipe)
+ {
+ var portDelimiterStart = address.LastIndexOf(":", pathDelimiterStart - 1, pathDelimiterStart - schemeDelimiterEnd, StringComparison.Ordinal);
+ if (portDelimiterStart >= 0)
{
- var portDelimiterStart = address.LastIndexOf(":", pathDelimiterStart - 1, pathDelimiterStart - schemeDelimiterEnd, StringComparison.Ordinal);
- if (portDelimiterStart >= 0)
- {
- var portDelimiterEnd = portDelimiterStart + ":".Length;
-
- var portString = address.Substring(portDelimiterEnd, pathDelimiterStart - portDelimiterEnd);
- int portNumber;
- if (int.TryParse(portString, NumberStyles.Integer, CultureInfo.InvariantCulture, out portNumber))
- {
- hasSpecifiedPort = true;
- host = address.Substring(schemeDelimiterEnd, portDelimiterStart - schemeDelimiterEnd);
- port = portNumber;
- }
- }
+ var portDelimiterEnd = portDelimiterStart + ":".Length;
- if (!hasSpecifiedPort)
+ var portString = address.Substring(portDelimiterEnd, pathDelimiterStart - portDelimiterEnd);
+ int portNumber;
+ if (int.TryParse(portString, NumberStyles.Integer, CultureInfo.InvariantCulture, out portNumber))
{
- if (string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase))
- {
- port = 80;
- }
- else if (string.Equals(scheme, "https", StringComparison.OrdinalIgnoreCase))
- {
- port = 443;
- }
+ hasSpecifiedPort = true;
+ host = address.Substring(schemeDelimiterEnd, portDelimiterStart - schemeDelimiterEnd);
+ port = portNumber;
}
}
if (!hasSpecifiedPort)
{
- host = address.Substring(schemeDelimiterEnd, pathDelimiterStart - schemeDelimiterEnd);
+ if (string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase))
+ {
+ port = 80;
+ }
+ else if (string.Equals(scheme, "https", StringComparison.OrdinalIgnoreCase))
+ {
+ port = 443;
+ }
}
+ }
- if (string.IsNullOrEmpty(host))
- {
- throw new FormatException($"Invalid url: '{address}'");
- }
+ if (!hasSpecifiedPort)
+ {
+ host = address.Substring(schemeDelimiterEnd, pathDelimiterStart - schemeDelimiterEnd);
+ }
- if (isUnixPipe && !Path.IsPathRooted(GetUnixPipePath(host)))
- {
- throw new FormatException($"Invalid url, unix socket path must be absolute: '{address}'");
- }
+ if (string.IsNullOrEmpty(host))
+ {
+ throw new FormatException($"Invalid url: '{address}'");
+ }
- string pathBase;
- if (address[address.Length - 1] == '/')
- {
- pathBase = address.Substring(pathDelimiterEnd, address.Length - pathDelimiterEnd - 1);
- }
- else
- {
- pathBase = address.Substring(pathDelimiterEnd);
- }
+ if (isUnixPipe && !Path.IsPathRooted(GetUnixPipePath(host)))
+ {
+ throw new FormatException($"Invalid url, unix socket path must be absolute: '{address}'");
+ }
- return new BindingAddress(host: host, pathBase: pathBase, port: port, scheme: scheme);
+ string pathBase;
+ if (address[address.Length - 1] == '/')
+ {
+ pathBase = address.Substring(pathDelimiterEnd, address.Length - pathDelimiterEnd - 1);
}
+ else
+ {
+ pathBase = address.Substring(pathDelimiterEnd);
+ }
+
+ return new BindingAddress(host: host, pathBase: pathBase, port: port, scheme: scheme);
}
}
diff --git a/src/Http/Http/src/Builder/ApplicationBuilder.cs b/src/Http/Http/src/Builder/ApplicationBuilder.cs
index ae70aed021..a7301fdcaf 100644
--- a/src/Http/Http/src/Builder/ApplicationBuilder.cs
+++ b/src/Http/Http/src/Builder/ApplicationBuilder.cs
@@ -8,140 +8,139 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Internal;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Default implementation for <see cref="IApplicationBuilder"/>.
+/// </summary>
+public class ApplicationBuilder : IApplicationBuilder
{
+ private const string ServerFeaturesKey = "server.Features";
+ private const string ApplicationServicesKey = "application.Services";
+
+ private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new();
+
/// <summary>
- /// Default implementation for <see cref="IApplicationBuilder"/>.
+ /// Initializes a new instance of <see cref="ApplicationBuilder"/>.
/// </summary>
- public class ApplicationBuilder : IApplicationBuilder
+ /// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param>
+ public ApplicationBuilder(IServiceProvider serviceProvider)
{
- private const string ServerFeaturesKey = "server.Features";
- private const string ApplicationServicesKey = "application.Services";
-
- private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new();
+ Properties = new Dictionary<string, object?>(StringComparer.Ordinal);
+ ApplicationServices = serviceProvider;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ApplicationBuilder"/>.
- /// </summary>
- /// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param>
- public ApplicationBuilder(IServiceProvider serviceProvider)
- {
- Properties = new Dictionary<string, object?>(StringComparer.Ordinal);
- ApplicationServices = serviceProvider;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="ApplicationBuilder"/>.
+ /// </summary>
+ /// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param>
+ /// <param name="server">The server instance that hosts the application.</param>
+ public ApplicationBuilder(IServiceProvider serviceProvider, object server)
+ : this(serviceProvider)
+ {
+ SetProperty(ServerFeaturesKey, server);
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="ApplicationBuilder"/>.
- /// </summary>
- /// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param>
- /// <param name="server">The server instance that hosts the application.</param>
- public ApplicationBuilder(IServiceProvider serviceProvider, object server)
- : this(serviceProvider)
- {
- SetProperty(ServerFeaturesKey, server);
- }
+ private ApplicationBuilder(ApplicationBuilder builder)
+ {
+ Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal);
+ }
- private ApplicationBuilder(ApplicationBuilder builder)
+ /// <summary>
+ /// Gets the <see cref="IServiceProvider"/> for application services.
+ /// </summary>
+ public IServiceProvider ApplicationServices
+ {
+ get
{
- Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal);
+ return GetProperty<IServiceProvider>(ApplicationServicesKey)!;
}
-
- /// <summary>
- /// Gets the <see cref="IServiceProvider"/> for application services.
- /// </summary>
- public IServiceProvider ApplicationServices
+ set
{
- get
- {
- return GetProperty<IServiceProvider>(ApplicationServicesKey)!;
- }
- set
- {
- SetProperty<IServiceProvider>(ApplicationServicesKey, value);
- }
+ SetProperty<IServiceProvider>(ApplicationServicesKey, value);
}
+ }
- /// <summary>
- /// Gets the <see cref="IFeatureCollection"/> for server features.
- /// </summary>
- public IFeatureCollection ServerFeatures
+ /// <summary>
+ /// Gets the <see cref="IFeatureCollection"/> for server features.
+ /// </summary>
+ public IFeatureCollection ServerFeatures
+ {
+ get
{
- get
- {
- return GetProperty<IFeatureCollection>(ServerFeaturesKey)!;
- }
+ return GetProperty<IFeatureCollection>(ServerFeaturesKey)!;
}
+ }
- /// <summary>
- /// Gets a set of properties for <see cref="ApplicationBuilder"/>.
- /// </summary>
- public IDictionary<string, object?> Properties { get; }
+ /// <summary>
+ /// Gets a set of properties for <see cref="ApplicationBuilder"/>.
+ /// </summary>
+ public IDictionary<string, object?> Properties { get; }
- private T? GetProperty<T>(string key)
- {
- return Properties.TryGetValue(key, out var value) ? (T?)value : default(T);
- }
+ private T? GetProperty<T>(string key)
+ {
+ return Properties.TryGetValue(key, out var value) ? (T?)value : default(T);
+ }
- private void SetProperty<T>(string key, T value)
- {
- Properties[key] = value;
- }
+ private void SetProperty<T>(string key, T value)
+ {
+ Properties[key] = value;
+ }
- /// <summary>
- /// Adds the middleware to the application request pipeline.
- /// </summary>
- /// <param name="middleware">The middleware.</param>
- /// <returns>An instance of <see cref="IApplicationBuilder"/> after the operation has completed.</returns>
- public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
- {
- _components.Add(middleware);
- return this;
- }
+ /// <summary>
+ /// Adds the middleware to the application request pipeline.
+ /// </summary>
+ /// <param name="middleware">The middleware.</param>
+ /// <returns>An instance of <see cref="IApplicationBuilder"/> after the operation has completed.</returns>
+ public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
+ {
+ _components.Add(middleware);
+ return this;
+ }
- /// <summary>
- /// Creates a copy of this application builder.
- /// <para>
- /// The created clone has the same properties as the current instance, but does not copy
- /// the request pipeline.
- /// </para>
- /// </summary>
- /// <returns>The cloned instance.</returns>
- public IApplicationBuilder New()
- {
- return new ApplicationBuilder(this);
- }
+ /// <summary>
+ /// Creates a copy of this application builder.
+ /// <para>
+ /// The created clone has the same properties as the current instance, but does not copy
+ /// the request pipeline.
+ /// </para>
+ /// </summary>
+ /// <returns>The cloned instance.</returns>
+ public IApplicationBuilder New()
+ {
+ return new ApplicationBuilder(this);
+ }
- /// <summary>
- /// Produces a <see cref="RequestDelegate"/> that executes added middlewares.
- /// </summary>
- /// <returns>The <see cref="RequestDelegate"/>.</returns>
- public RequestDelegate Build()
+ /// <summary>
+ /// Produces a <see cref="RequestDelegate"/> that executes added middlewares.
+ /// </summary>
+ /// <returns>The <see cref="RequestDelegate"/>.</returns>
+ public RequestDelegate Build()
+ {
+ RequestDelegate app = context =>
{
- RequestDelegate app = context =>
- {
// If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
// This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
var endpoint = context.GetEndpoint();
- var endpointRequestDelegate = endpoint?.RequestDelegate;
- if (endpointRequestDelegate != null)
- {
- var message =
- $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
- $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
- $"routing.";
- throw new InvalidOperationException(message);
- }
-
- context.Response.StatusCode = StatusCodes.Status404NotFound;
- return Task.CompletedTask;
- };
-
- for (var c = _components.Count - 1; c >= 0; c--)
+ var endpointRequestDelegate = endpoint?.RequestDelegate;
+ if (endpointRequestDelegate != null)
{
- app = _components[c](app);
+ var message =
+ $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
+ $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
+ $"routing.";
+ throw new InvalidOperationException(message);
}
- return app;
+ context.Response.StatusCode = StatusCodes.Status404NotFound;
+ return Task.CompletedTask;
+ };
+
+ for (var c = _components.Count - 1; c >= 0; c--)
+ {
+ app = _components[c](app);
}
+
+ return app;
}
}
diff --git a/src/Http/Http/src/DefaultHttpContext.cs b/src/Http/Http/src/DefaultHttpContext.cs
index 01afc2d26c..ad10681de3 100644
--- a/src/Http/Http/src/DefaultHttpContext.cs
+++ b/src/Http/Http/src/DefaultHttpContext.cs
@@ -12,242 +12,241 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Represents an implementation of the HTTP Context class.
+/// </summary>
+public sealed class DefaultHttpContext : HttpContext
{
+ // The initial size of the feature collection when using the default constructor; based on number of common features
+ // https://github.com/dotnet/aspnetcore/issues/31249
+ private const int DefaultFeatureCollectionSize = 10;
+
+ // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
+ private static readonly Func<DefaultHttpContext, IServiceProvidersFeature> _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory);
+ private static readonly Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
+ private static readonly Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
+ private static readonly Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
+ private static readonly Func<IFeatureCollection, ISessionFeature?> _nullSessionFeature = f => null;
+ private static readonly Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
+
+ private FeatureReferences<FeatureInterfaces> _features;
+
+ private readonly DefaultHttpRequest _request;
+ private readonly DefaultHttpResponse _response;
+
+ private DefaultConnectionInfo? _connection;
+ private DefaultWebSocketManager? _websockets;
+
+ // This is field exists to make analyzing memory dumps easier.
+ // https://github.com/dotnet/aspnetcore/issues/29709
+ internal bool _active;
+
/// <summary>
- /// Represents an implementation of the HTTP Context class.
+ /// Initializes a new instance of the <see cref="DefaultHttpContext"/> class.
/// </summary>
- public sealed class DefaultHttpContext : HttpContext
+ public DefaultHttpContext()
+ : this(new FeatureCollection(DefaultFeatureCollectionSize))
{
- // The initial size of the feature collection when using the default constructor; based on number of common features
- // https://github.com/dotnet/aspnetcore/issues/31249
- private const int DefaultFeatureCollectionSize = 10;
-
- // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
- private static readonly Func<DefaultHttpContext, IServiceProvidersFeature> _newServiceProvidersFeature = context => new RequestServicesFeature(context, context.ServiceScopeFactory);
- private static readonly Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
- private static readonly Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
- private static readonly Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
- private static readonly Func<IFeatureCollection, ISessionFeature?> _nullSessionFeature = f => null;
- private static readonly Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
-
- private FeatureReferences<FeatureInterfaces> _features;
-
- private readonly DefaultHttpRequest _request;
- private readonly DefaultHttpResponse _response;
-
- private DefaultConnectionInfo? _connection;
- private DefaultWebSocketManager? _websockets;
-
- // This is field exists to make analyzing memory dumps easier.
- // https://github.com/dotnet/aspnetcore/issues/29709
- internal bool _active;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="DefaultHttpContext"/> class.
- /// </summary>
- public DefaultHttpContext()
- : this(new FeatureCollection(DefaultFeatureCollectionSize))
- {
- Features.Set<IHttpRequestFeature>(new HttpRequestFeature());
- Features.Set<IHttpResponseFeature>(new HttpResponseFeature());
- Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
- }
+ Features.Set<IHttpRequestFeature>(new HttpRequestFeature());
+ Features.Set<IHttpResponseFeature>(new HttpResponseFeature());
+ Features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="DefaultHttpContext"/> class with provided features.
- /// </summary>
- /// <param name="features">Initial set of features for the <see cref="DefaultHttpContext"/>.</param>
- public DefaultHttpContext(IFeatureCollection features)
- {
- _features.Initalize(features);
- _request = new DefaultHttpRequest(this);
- _response = new DefaultHttpResponse(this);
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DefaultHttpContext"/> class with provided features.
+ /// </summary>
+ /// <param name="features">Initial set of features for the <see cref="DefaultHttpContext"/>.</param>
+ public DefaultHttpContext(IFeatureCollection features)
+ {
+ _features.Initalize(features);
+ _request = new DefaultHttpRequest(this);
+ _response = new DefaultHttpResponse(this);
+ }
- /// <summary>
- /// Reinitialize the current instant of the class with features passed in.
- /// </summary>
- /// <remarks>
- /// This method allows the consumer to re-use the <see cref="DefaultHttpContext" /> for another request, rather than having to allocate a new instance.
- /// </remarks>
- /// <param name="features">The new set of features for the <see cref="DefaultHttpContext" />.</param>
- public void Initialize(IFeatureCollection features)
- {
- var revision = features.Revision;
- _features.Initalize(features, revision);
- _request.Initialize(revision);
- _response.Initialize(revision);
- _connection?.Initialize(features, revision);
- _websockets?.Initialize(features, revision);
- _active = true;
- }
+ /// <summary>
+ /// Reinitialize the current instant of the class with features passed in.
+ /// </summary>
+ /// <remarks>
+ /// This method allows the consumer to re-use the <see cref="DefaultHttpContext" /> for another request, rather than having to allocate a new instance.
+ /// </remarks>
+ /// <param name="features">The new set of features for the <see cref="DefaultHttpContext" />.</param>
+ public void Initialize(IFeatureCollection features)
+ {
+ var revision = features.Revision;
+ _features.Initalize(features, revision);
+ _request.Initialize(revision);
+ _response.Initialize(revision);
+ _connection?.Initialize(features, revision);
+ _websockets?.Initialize(features, revision);
+ _active = true;
+ }
- /// <summary>
- /// Uninitialize all the features in the <see cref="DefaultHttpContext" />.
- /// </summary>
- public void Uninitialize()
- {
- _features = default;
- _request.Uninitialize();
- _response.Uninitialize();
- _connection?.Uninitialize();
- _websockets?.Uninitialize();
- _active = false;
- }
+ /// <summary>
+ /// Uninitialize all the features in the <see cref="DefaultHttpContext" />.
+ /// </summary>
+ public void Uninitialize()
+ {
+ _features = default;
+ _request.Uninitialize();
+ _response.Uninitialize();
+ _connection?.Uninitialize();
+ _websockets?.Uninitialize();
+ _active = false;
+ }
- /// <summary>
- /// Gets or set the <see cref="FormOptions" /> for this instance.
- /// </summary>
- /// <returns>
- /// <see cref="FormOptions"/>
- /// </returns>
- public FormOptions FormOptions { get; set; } = default!;
+ /// <summary>
+ /// Gets or set the <see cref="FormOptions" /> for this instance.
+ /// </summary>
+ /// <returns>
+ /// <see cref="FormOptions"/>
+ /// </returns>
+ public FormOptions FormOptions { get; set; } = default!;
- /// <summary>
- /// Gets or sets the <see cref="IServiceScopeFactory" /> for this instance.
- /// </summary>
- /// <returns>
- /// <see cref="IServiceScopeFactory"/>
- /// </returns>
- public IServiceScopeFactory ServiceScopeFactory { get; set; } = default!;
+ /// <summary>
+ /// Gets or sets the <see cref="IServiceScopeFactory" /> for this instance.
+ /// </summary>
+ /// <returns>
+ /// <see cref="IServiceScopeFactory"/>
+ /// </returns>
+ public IServiceScopeFactory ServiceScopeFactory { get; set; } = default!;
- private IItemsFeature ItemsFeature =>
- _features.Fetch(ref _features.Cache.Items, _newItemsFeature)!;
+ private IItemsFeature ItemsFeature =>
+ _features.Fetch(ref _features.Cache.Items, _newItemsFeature)!;
- private IServiceProvidersFeature ServiceProvidersFeature =>
- _features.Fetch(ref _features.Cache.ServiceProviders, this, _newServiceProvidersFeature)!;
+ private IServiceProvidersFeature ServiceProvidersFeature =>
+ _features.Fetch(ref _features.Cache.ServiceProviders, this, _newServiceProvidersFeature)!;
- private IHttpAuthenticationFeature HttpAuthenticationFeature =>
- _features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature)!;
+ private IHttpAuthenticationFeature HttpAuthenticationFeature =>
+ _features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature)!;
- private IHttpRequestLifetimeFeature LifetimeFeature =>
- _features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature)!;
+ private IHttpRequestLifetimeFeature LifetimeFeature =>
+ _features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature)!;
- private ISessionFeature SessionFeature =>
- _features.Fetch(ref _features.Cache.Session, _newSessionFeature)!;
+ private ISessionFeature SessionFeature =>
+ _features.Fetch(ref _features.Cache.Session, _newSessionFeature)!;
- private ISessionFeature? SessionFeatureOrNull =>
- _features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
+ private ISessionFeature? SessionFeatureOrNull =>
+ _features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
- private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
- _features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature)!;
+ private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
+ _features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature)!;
- /// <inheritdoc/>
- public override IFeatureCollection Features => _features.Collection ?? ContextDisposed();
+ /// <inheritdoc/>
+ public override IFeatureCollection Features => _features.Collection ?? ContextDisposed();
- /// <inheritdoc/>
- public override HttpRequest Request => _request;
+ /// <inheritdoc/>
+ public override HttpRequest Request => _request;
- /// <inheritdoc/>
- public override HttpResponse Response => _response;
+ /// <inheritdoc/>
+ public override HttpResponse Response => _response;
- /// <inheritdoc/>
- public override ConnectionInfo Connection => _connection ?? (_connection = new DefaultConnectionInfo(Features));
+ /// <inheritdoc/>
+ public override ConnectionInfo Connection => _connection ?? (_connection = new DefaultConnectionInfo(Features));
- /// <inheritdoc/>
- public override WebSocketManager WebSockets => _websockets ?? (_websockets = new DefaultWebSocketManager(Features));
+ /// <inheritdoc/>
+ public override WebSocketManager WebSockets => _websockets ?? (_websockets = new DefaultWebSocketManager(Features));
- /// <inheritdoc/>
- public override ClaimsPrincipal User
+ /// <inheritdoc/>
+ public override ClaimsPrincipal User
+ {
+ get
{
- get
+ var user = HttpAuthenticationFeature.User;
+ if (user == null)
{
- var user = HttpAuthenticationFeature.User;
- if (user == null)
- {
- user = new ClaimsPrincipal(new ClaimsIdentity());
- HttpAuthenticationFeature.User = user;
- }
- return user;
+ user = new ClaimsPrincipal(new ClaimsIdentity());
+ HttpAuthenticationFeature.User = user;
}
- set { HttpAuthenticationFeature.User = value; }
+ return user;
}
+ set { HttpAuthenticationFeature.User = value; }
+ }
- /// <inheritdoc/>
- public override IDictionary<object, object?> Items
- {
- get { return ItemsFeature.Items; }
- set { ItemsFeature.Items = value; }
- }
+ /// <inheritdoc/>
+ public override IDictionary<object, object?> Items
+ {
+ get { return ItemsFeature.Items; }
+ set { ItemsFeature.Items = value; }
+ }
- /// <inheritdoc/>
- public override IServiceProvider RequestServices
- {
- get { return ServiceProvidersFeature.RequestServices; }
- set { ServiceProvidersFeature.RequestServices = value; }
- }
+ /// <inheritdoc/>
+ public override IServiceProvider RequestServices
+ {
+ get { return ServiceProvidersFeature.RequestServices; }
+ set { ServiceProvidersFeature.RequestServices = value; }
+ }
- /// <inheritdoc/>
- public override CancellationToken RequestAborted
- {
- get { return LifetimeFeature.RequestAborted; }
- set { LifetimeFeature.RequestAborted = value; }
- }
+ /// <inheritdoc/>
+ public override CancellationToken RequestAborted
+ {
+ get { return LifetimeFeature.RequestAborted; }
+ set { LifetimeFeature.RequestAborted = value; }
+ }
- /// <inheritdoc/>
- public override string TraceIdentifier
- {
- get { return RequestIdentifierFeature.TraceIdentifier; }
- set { RequestIdentifierFeature.TraceIdentifier = value; }
- }
+ /// <inheritdoc/>
+ public override string TraceIdentifier
+ {
+ get { return RequestIdentifierFeature.TraceIdentifier; }
+ set { RequestIdentifierFeature.TraceIdentifier = value; }
+ }
- /// <inheritdoc/>
- public override ISession Session
+ /// <inheritdoc/>
+ public override ISession Session
+ {
+ get
{
- get
- {
- var feature = SessionFeatureOrNull;
- if (feature == null)
- {
- throw new InvalidOperationException("Session has not been configured for this application " +
- "or request.");
- }
- return feature.Session;
- }
- set
+ var feature = SessionFeatureOrNull;
+ if (feature == null)
{
- SessionFeature.Session = value;
+ throw new InvalidOperationException("Session has not been configured for this application " +
+ "or request.");
}
+ return feature.Session;
}
-
- // This property exists because of backwards compatibility.
- // We send an anonymous object with an HttpContext property
- // via DiagnosticListener in various events throughout the pipeline. Instead
- // we just send the HttpContext to avoid extra allocations
- /// <summary>
- /// This API is used by ASP.NET Core's infrastructure and should not be used by application code.
- /// </summary>
- [EditorBrowsable(EditorBrowsableState.Never)]
- public HttpContext HttpContext => this;
-
- /// <inheritdoc/>
- public override void Abort()
+ set
{
- LifetimeFeature.Abort();
+ SessionFeature.Session = value;
}
+ }
- private static IFeatureCollection ContextDisposed()
- {
- ThrowContextDisposed();
- return null;
- }
+ // This property exists because of backwards compatibility.
+ // We send an anonymous object with an HttpContext property
+ // via DiagnosticListener in various events throughout the pipeline. Instead
+ // we just send the HttpContext to avoid extra allocations
+ /// <summary>
+ /// This API is used by ASP.NET Core's infrastructure and should not be used by application code.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public HttpContext HttpContext => this;
- [DoesNotReturn]
- private static void ThrowContextDisposed()
- {
- throw new ObjectDisposedException(nameof(HttpContext), $"Request has finished and {nameof(HttpContext)} disposed.");
- }
+ /// <inheritdoc/>
+ public override void Abort()
+ {
+ LifetimeFeature.Abort();
+ }
- struct FeatureInterfaces
- {
- public IItemsFeature? Items;
- public IServiceProvidersFeature? ServiceProviders;
- public IHttpAuthenticationFeature? Authentication;
- public IHttpRequestLifetimeFeature? Lifetime;
- public ISessionFeature? Session;
- public IHttpRequestIdentifierFeature? RequestIdentifier;
- }
+ private static IFeatureCollection ContextDisposed()
+ {
+ ThrowContextDisposed();
+ return null;
+ }
+
+ [DoesNotReturn]
+ private static void ThrowContextDisposed()
+ {
+ throw new ObjectDisposedException(nameof(HttpContext), $"Request has finished and {nameof(HttpContext)} disposed.");
+ }
+
+ 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
index 5f077c2deb..ea0c8bca6e 100644
--- a/src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs
+++ b/src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs
@@ -1,89 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods for enabling buffering in an <see cref="HttpRequest"/>.
+/// </summary>
+public static class HttpRequestRewindExtensions
{
/// <summary>
- /// Extension methods for enabling buffering in an <see cref="HttpRequest"/>.
+ /// 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>
- public static class HttpRequestRewindExtensions
+ /// <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)
{
- /// <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);
- }
+ 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 <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 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);
- }
+ /// <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
index d5f28ca62a..35adbfbaaa 100644
--- a/src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.cs
+++ b/src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.cs
@@ -3,14 +3,13 @@
using System.Security.Claims;
-namespace Microsoft.AspNetCore.Http.Features.Authentication
+namespace Microsoft.AspNetCore.Http.Features.Authentication;
+
+/// <summary>
+/// Default implementation for <see cref="IHttpAuthenticationFeature"/>.
+/// </summary>
+public class HttpAuthenticationFeature : IHttpAuthenticationFeature
{
- /// <summary>
- /// Default implementation for <see cref="IHttpAuthenticationFeature"/>.
- /// </summary>
- public class HttpAuthenticationFeature : IHttpAuthenticationFeature
- {
- /// <inheritdoc />
- public ClaimsPrincipal? User { get; set; }
- }
+ /// <inheritdoc />
+ public ClaimsPrincipal? User { get; set; }
}
diff --git a/src/Http/Http/src/Features/DefaultConnectionLifetimeNotificationFeature.cs b/src/Http/Http/src/Features/DefaultConnectionLifetimeNotificationFeature.cs
index d7042cf990..0850f4f948 100644
--- a/src/Http/Http/src/Features/DefaultConnectionLifetimeNotificationFeature.cs
+++ b/src/Http/Http/src/Features/DefaultConnectionLifetimeNotificationFeature.cs
@@ -4,36 +4,35 @@
using System.Threading;
using Microsoft.AspNetCore.Connections.Features;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation of <see cref="IConnectionLifetimeNotificationFeature"/>.
+/// </summary>
+internal sealed class DefaultConnectionLifetimeNotificationFeature : IConnectionLifetimeNotificationFeature
{
+ private readonly IHttpResponseFeature? _httpResponseFeature;
+
/// <summary>
- /// Default implementation of <see cref="IConnectionLifetimeNotificationFeature"/>.
+ ///
/// </summary>
- internal sealed class DefaultConnectionLifetimeNotificationFeature : IConnectionLifetimeNotificationFeature
+ /// <param name="httpResponseFeature"></param>
+ public DefaultConnectionLifetimeNotificationFeature(IHttpResponseFeature? httpResponseFeature)
{
- private readonly IHttpResponseFeature? _httpResponseFeature;
-
- /// <summary>
- ///
- /// </summary>
- /// <param name="httpResponseFeature"></param>
- public DefaultConnectionLifetimeNotificationFeature(IHttpResponseFeature? httpResponseFeature)
- {
- _httpResponseFeature = httpResponseFeature;
- }
+ _httpResponseFeature = httpResponseFeature;
+ }
- ///<inheritdoc/>
- public CancellationToken ConnectionClosedRequested { get; set; }
+ ///<inheritdoc/>
+ public CancellationToken ConnectionClosedRequested { get; set; }
- ///<inheritdoc/>
- public void RequestClose()
+ ///<inheritdoc/>
+ public void RequestClose()
+ {
+ if (_httpResponseFeature != null)
{
- if (_httpResponseFeature != null)
+ if (!_httpResponseFeature.HasStarted)
{
- if (!_httpResponseFeature.HasStarted)
- {
- _httpResponseFeature.Headers.Connection = "close";
- }
+ _httpResponseFeature.Headers.Connection = "close";
}
}
}
diff --git a/src/Http/Http/src/Features/DefaultSessionFeature.cs b/src/Http/Http/src/Features/DefaultSessionFeature.cs
index 4ed6c1a979..dc2569637e 100644
--- a/src/Http/Http/src/Features/DefaultSessionFeature.cs
+++ b/src/Http/Http/src/Features/DefaultSessionFeature.cs
@@ -1,15 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http.Features
+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
{
- /// <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
- {
- /// <inheritdoc />
- public ISession Session { get; set; } = default!;
- }
+ /// <inheritdoc />
+ public ISession Session { get; set; } = default!;
}
diff --git a/src/Http/Http/src/Features/FormFeature.cs b/src/Http/Http/src/Features/FormFeature.cs
index e758b1b035..4892b5207d 100644
--- a/src/Http/Http/src/Features/FormFeature.cs
+++ b/src/Http/Http/src/Features/FormFeature.cs
@@ -11,326 +11,325 @@ using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IFormFeature"/>.
+/// </summary>
+public class FormFeature : IFormFeature
{
+ private readonly HttpRequest _request;
+ private readonly FormOptions _options;
+ private Task<IFormCollection>? _parsedFormTask;
+ private IFormCollection? _form;
+
/// <summary>
- /// Default implementation for <see cref="IFormFeature"/>.
+ /// Initializes a new instance of <see cref="FormFeature"/>.
/// </summary>
- public class FormFeature : IFormFeature
+ /// <param name="form">The <see cref="IFormCollection"/> to use as the backing store.</param>
+ public FormFeature(IFormCollection form)
{
- private readonly HttpRequest _request;
- private readonly FormOptions _options;
- private Task<IFormCollection>? _parsedFormTask;
- private IFormCollection? _form;
-
- /// <summary>
- /// Initializes a new instance of <see cref="FormFeature"/>.
- /// </summary>
- /// <param name="form">The <see cref="IFormCollection"/> to use as the backing store.</param>
- public FormFeature(IFormCollection form)
+ if (form == null)
{
- if (form == null)
- {
- throw new ArgumentNullException(nameof(form));
- }
+ throw new ArgumentNullException(nameof(form));
+ }
+
+ Form = form;
+ _request = default!;
+ _options = FormOptions.Default;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormFeature"/>.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpRequest"/>.</param>
+ public FormFeature(HttpRequest request)
+ : this(request, FormOptions.Default)
+ {
+ }
- Form = form;
- _request = default!;
- _options = FormOptions.Default;
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormFeature"/>.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpRequest"/>.</param>
+ /// <param name="options">The <see cref="FormOptions"/>.</param>
+ 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;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FormFeature"/>.
- /// </summary>
- /// <param name="request">The <see cref="HttpRequest"/>.</param>
- public FormFeature(HttpRequest request)
- : this(request, FormOptions.Default)
+ private MediaTypeHeaderValue? ContentType
+ {
+ get
{
+ MediaTypeHeaderValue.TryParse(_request.ContentType, out var mt);
+ return mt;
}
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FormFeature"/>.
- /// </summary>
- /// <param name="request">The <see cref="HttpRequest"/>.</param>
- /// <param name="options">The <see cref="FormOptions"/>.</param>
- public FormFeature(HttpRequest request, FormOptions options)
+ /// <inheritdoc />
+ public bool HasFormContentType
+ {
+ get
{
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
- if (options == null)
+ // Set directly
+ if (Form != null)
{
- throw new ArgumentNullException(nameof(options));
+ return true;
}
- _request = request;
- _options = options;
+ var contentType = ContentType;
+ return HasApplicationFormContentType(contentType) || HasMultipartFormContentType(contentType);
}
+ }
- private MediaTypeHeaderValue? ContentType
+ /// <inheritdoc />
+ public IFormCollection? Form
+ {
+ get { return _form; }
+ set
{
- get
- {
- MediaTypeHeaderValue.TryParse(_request.ContentType, out var mt);
- return mt;
- }
+ _parsedFormTask = null;
+ _form = value;
}
+ }
- /// <inheritdoc />
- public bool HasFormContentType
+ /// <inheritdoc />
+ public IFormCollection ReadForm()
+ {
+ if (Form != null)
{
- get
- {
- // Set directly
- if (Form != null)
- {
- return true;
- }
-
- var contentType = ContentType;
- return HasApplicationFormContentType(contentType) || HasMultipartFormContentType(contentType);
- }
+ return Form;
}
- /// <inheritdoc />
- public IFormCollection? Form
+ if (!HasFormContentType)
{
- get { return _form; }
- set
- {
- _parsedFormTask = null;
- _form = value;
- }
+ throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
}
- /// <inheritdoc />
- public IFormCollection ReadForm()
+ // 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();
+ }
+
+ /// <inheritdoc />
+ public Task<IFormCollection> ReadFormAsync() => ReadFormAsync(CancellationToken.None);
+
+ /// <inheritdoc />
+ public Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
+ {
+ // Avoid state machine and task allocation for repeated reads
+ if (_parsedFormTask == null)
{
if (Form != null)
{
- return Form;
+ _parsedFormTask = Task.FromResult(Form);
}
-
- if (!HasFormContentType)
+ else
{
- throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
+ _parsedFormTask = InnerReadFormAsync(cancellationToken);
}
+ }
+ return _parsedFormTask;
+ }
- // 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();
+ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken)
+ {
+ if (!HasFormContentType)
+ {
+ throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
}
- /// <inheritdoc />
- public Task<IFormCollection> ReadFormAsync() => ReadFormAsync(CancellationToken.None);
+ cancellationToken.ThrowIfCancellationRequested();
- /// <inheritdoc />
- public Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
+ if (_request.ContentLength == 0)
{
- // 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;
+ return FormCollection.Empty;
}
- private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken)
+ if (_options.BufferBody)
{
- if (!HasFormContentType)
- {
- throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
- }
+ _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit);
+ }
- cancellationToken.ThrowIfCancellationRequested();
+ FormCollection? formFields = null;
+ FormFileCollection? files = null;
- if (_request.ContentLength == 0)
+ // 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))
{
- return FormCollection.Empty;
+ var encoding = FilterEncoding(contentType.Encoding);
+ var formReader = new FormPipeReader(_request.BodyReader, encoding)
+ {
+ ValueCountLimit = _options.ValueCountLimit,
+ KeyLengthLimit = _options.KeyLengthLimit,
+ ValueLengthLimit = _options.ValueLengthLimit,
+ };
+ formFields = new FormCollection(await formReader.ReadFormAsync(cancellationToken));
}
-
- if (_options.BufferBody)
+ else if (HasMultipartFormContentType(contentType))
{
- _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit);
- }
+ var formAccumulator = new KeyValueAccumulator();
- 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 boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit);
+ var multipartReader = new MultipartReader(boundary, _request.Body)
{
- var encoding = FilterEncoding(contentType.Encoding);
- var formReader = new FormPipeReader(_request.BodyReader, encoding)
- {
- ValueCountLimit = _options.ValueCountLimit,
- KeyLengthLimit = _options.KeyLengthLimit,
- ValueLengthLimit = _options.ValueLengthLimit,
- };
- formFields = new FormCollection(await formReader.ReadFormAsync(cancellationToken));
- }
- else if (HasMultipartFormContentType(contentType))
+ HeadersCountLimit = _options.MultipartHeadersCountLimit,
+ HeadersLengthLimit = _options.MultipartHeadersLengthLimit,
+ BodyLengthLimit = _options.MultipartBodyLengthLimit,
+ };
+ var section = await multipartReader.ReadNextSectionAsync(cancellationToken);
+ while (section != null)
{
- var formAccumulator = new KeyValueAccumulator();
-
- var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit);
- var multipartReader = new MultipartReader(boundary, _request.Body)
+ // Parse the content disposition here and pass it further to avoid reparsings
+ if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
{
- HeadersCountLimit = _options.MultipartHeadersCountLimit,
- HeadersLengthLimit = _options.MultipartHeadersLengthLimit,
- BodyLengthLimit = _options.MultipartBodyLengthLimit,
- };
- var section = await multipartReader.ReadNextSectionAsync(cancellationToken);
- while (section != null)
+ throw new InvalidDataException("Form section has invalid Content-Disposition value: " + section.ContentDisposition);
+ }
+
+ if (contentDisposition.IsFileDisposition())
{
- // Parse the content disposition here and pass it further to avoid reparsings
- if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition))
+ 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.GetValueOrDefault(), section.Body.Length, name, fileName);
+ }
+ else
{
- throw new InvalidDataException("Form section has invalid Content-Disposition value: " + section.ContentDisposition);
+ // Individually buffered file body
+ file = new FormFile(section.Body, 0, section.Body.Length, name, fileName);
}
+ file.Headers = new HeaderDictionary(section.Headers);
- if (contentDisposition.IsFileDisposition())
+ if (files == null)
{
- 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.GetValueOrDefault(), 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);
+ files = new FormFileCollection();
}
- else if (contentDisposition.IsFormDisposition())
+ if (files.Count >= _options.ValueCountLimit)
{
- var formDataSection = new FormMultipartSection(section, contentDisposition);
+ 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
+ // Content-Disposition: form-data; name="key"
+ //
+ // value
- // Do not limit the key name length here because the multipart headers length limit is already in effect.
- var key = formDataSection.Name;
- var value = await formDataSection.GetValueAsync();
+ // Do not limit the key name length here because the multipart 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
+ formAccumulator.Append(key, value);
+ if (formAccumulator.ValueCount > _options.ValueCountLimit)
{
- System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition);
+ throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
}
-
- section = await multipartReader.ReadNextSectionAsync(cancellationToken);
}
-
- if (formAccumulator.HasValues)
+ else
{
- formFields = new FormCollection(formAccumulator.GetResults(), files);
+ System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition);
}
- }
- }
- // Rewind so later readers don't have to.
- if (_request.Body.CanSeek)
- {
- _request.Body.Seek(0, SeekOrigin.Begin);
- }
+ section = await multipartReader.ReadNextSectionAsync(cancellationToken);
+ }
- if (formFields != null)
- {
- Form = formFields;
- }
- else if (files != null)
- {
- Form = new FormCollection(null, files);
- }
- else
- {
- Form = FormCollection.Empty;
+ if (formAccumulator.HasValues)
+ {
+ formFields = new FormCollection(formAccumulator.GetResults(), files);
+ }
}
-
- return Form;
}
- private static Encoding FilterEncoding(Encoding? encoding)
+ // Rewind so later readers don't have to.
+ if (_request.Body.CanSeek)
{
- // UTF-7 is insecure and should not be honored. UTF-8 will succeed for most cases.
- // https://docs.microsoft.com/en-us/dotnet/core/compatibility/syslib-warnings/syslib0001
- if (encoding == null || encoding.CodePage == 65000)
- {
- return Encoding.UTF8;
- }
- return encoding;
+ _request.Body.Seek(0, SeekOrigin.Begin);
}
- private static bool HasApplicationFormContentType([NotNullWhen(true)] MediaTypeHeaderValue? contentType)
+ if (formFields != null)
+ {
+ Form = formFields;
+ }
+ else if (files != null)
{
- // Content-Type: application/x-www-form-urlencoded; charset=utf-8
- return contentType != null && contentType.MediaType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase);
+ Form = new FormCollection(null, files);
}
+ else
+ {
+ Form = FormCollection.Empty;
+ }
+
+ return Form;
+ }
- private static bool HasMultipartFormContentType([NotNullWhen(true)] MediaTypeHeaderValue? contentType)
+ private static Encoding FilterEncoding(Encoding? encoding)
+ {
+ // UTF-7 is insecure and should not be honored. UTF-8 will succeed for most cases.
+ // https://docs.microsoft.com/en-us/dotnet/core/compatibility/syslib-warnings/syslib0001
+ if (encoding == null || encoding.CodePage == 65000)
{
- // Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq
- return contentType != null && contentType.MediaType.Equals("multipart/form-data", StringComparison.OrdinalIgnoreCase);
+ return Encoding.UTF8;
}
+ return encoding;
+ }
+
+ private static bool HasApplicationFormContentType([NotNullWhen(true)] 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 static bool HasMultipartFormContentType([NotNullWhen(true)] MediaTypeHeaderValue? contentType)
+ {
+ // Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq
+ return contentType != null && contentType.MediaType.Equals("multipart/form-data", StringComparison.OrdinalIgnoreCase);
+ }
- // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
- // The spec says 70 characters is a reasonable limit.
- private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
+ // 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))
{
- 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();
+ 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
index 60628c66a4..f7b7eecaa5 100644
--- a/src/Http/Http/src/Features/FormOptions.cs
+++ b/src/Http/Http/src/Features/FormOptions.cs
@@ -4,108 +4,107 @@
using System.IO;
using Microsoft.AspNetCore.WebUtilities;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Options to configure reading the request body as a HTTP form.
+/// </summary>
+public class FormOptions
{
+ internal static readonly FormOptions Default = new FormOptions();
+
+ /// <summary>
+ /// Default value for <see cref="MemoryBufferThreshold"/>.
+ /// Defaults to 65,536 bytes‬, which is approximately 64KB.
+ /// </summary>
+ public const int DefaultMemoryBufferThreshold = 1024 * 64;
+
+ /// <summary>
+ /// Default value for <see cref="BufferBodyLengthLimit"/>.
+ /// Defaults to 134,217,728 bytes‬, which is 128MB.
+ /// </summary>
+ public const int DefaultBufferBodyLengthLimit = 1024 * 1024 * 128;
+
+ /// <summary>
+ /// Default value for <see cref="MultipartBoundaryLengthLimit"/>.
+ /// Defaults to 128 bytes‬.
+ /// </summary>
+ public const int DefaultMultipartBoundaryLengthLimit = 128;
+
+ /// <summary>
+ /// Default value for <see cref="MultipartBodyLengthLimit "/>.
+ /// Defaults to 134,217,728 bytes‬, which is approximately 128MB.
+ /// </summary>
+ public const long DefaultMultipartBodyLengthLimit = 1024 * 1024 * 128;
+
+ /// <summary>
+ /// Enables full request body buffering. Use this if multiple components need to read the raw stream.
+ /// Defaults to <c>false</c>.
+ /// </summary>
+ public bool BufferBody { get; set; }
+
+ /// <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.
+ /// Defaults to 65,536 bytes‬, which is approximately 64KB.
+ /// </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.
+ /// Defaults to 134,217,728 bytes‬, which is approximately 128MB.
+ /// </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.
+ /// Defaults to 1024.
+ /// </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.
+ /// Defaults to 2,048 bytes‬, which is approximately 2KB.
+ /// </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.
+ /// Defaults to 4,194,304 bytes‬, which is approximately 4MB.
+ /// </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.
+ /// Defaults to 128 bytes‬.
+ /// </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.
+ /// Defaults to 16.
+ /// </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.
+ /// Defaults to 16,384‬ bytes‬, which is approximately 16KB.
+ /// </summary>
+ public int MultipartHeadersLengthLimit { get; set; } = MultipartReader.DefaultHeadersLengthLimit;
+
/// <summary>
- /// Options to configure reading the request body as a HTTP form.
+ /// A limit for the length of each multipart body. Forms sections that exceed this limit will throw an
+ /// <see cref="InvalidDataException"/> when parsed.
+ /// Defaults to 134,217,728 bytes‬, which is approximately 128MB.
/// </summary>
- public class FormOptions
- {
- internal static readonly FormOptions Default = new FormOptions();
-
- /// <summary>
- /// Default value for <see cref="MemoryBufferThreshold"/>.
- /// Defaults to 65,536 bytes‬, which is approximately 64KB.
- /// </summary>
- public const int DefaultMemoryBufferThreshold = 1024 * 64;
-
- /// <summary>
- /// Default value for <see cref="BufferBodyLengthLimit"/>.
- /// Defaults to 134,217,728 bytes‬, which is 128MB.
- /// </summary>
- public const int DefaultBufferBodyLengthLimit = 1024 * 1024 * 128;
-
- /// <summary>
- /// Default value for <see cref="MultipartBoundaryLengthLimit"/>.
- /// Defaults to 128 bytes‬.
- /// </summary>
- public const int DefaultMultipartBoundaryLengthLimit = 128;
-
- /// <summary>
- /// Default value for <see cref="MultipartBodyLengthLimit "/>.
- /// Defaults to 134,217,728 bytes‬, which is approximately 128MB.
- /// </summary>
- public const long DefaultMultipartBodyLengthLimit = 1024 * 1024 * 128;
-
- /// <summary>
- /// Enables full request body buffering. Use this if multiple components need to read the raw stream.
- /// Defaults to <c>false</c>.
- /// </summary>
- public bool BufferBody { get; set; }
-
- /// <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.
- /// Defaults to 65,536 bytes‬, which is approximately 64KB.
- /// </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.
- /// Defaults to 134,217,728 bytes‬, which is approximately 128MB.
- /// </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.
- /// Defaults to 1024.
- /// </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.
- /// Defaults to 2,048 bytes‬, which is approximately 2KB.
- /// </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.
- /// Defaults to 4,194,304 bytes‬, which is approximately 4MB.
- /// </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.
- /// Defaults to 128 bytes‬.
- /// </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.
- /// Defaults to 16.
- /// </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.
- /// Defaults to 16,384‬ bytes‬, which is approximately 16KB.
- /// </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.
- /// Defaults to 134,217,728 bytes‬, which is approximately 128MB.
- /// </summary>
- public long MultipartBodyLengthLimit { get; set; } = DefaultMultipartBodyLengthLimit;
- }
+ public long MultipartBodyLengthLimit { get; set; } = DefaultMultipartBodyLengthLimit;
}
diff --git a/src/Http/Http/src/Features/HttpConnectionFeature.cs b/src/Http/Http/src/Features/HttpConnectionFeature.cs
index 4268e401b6..6a4ba99a97 100644
--- a/src/Http/Http/src/Features/HttpConnectionFeature.cs
+++ b/src/Http/Http/src/Features/HttpConnectionFeature.cs
@@ -3,26 +3,25 @@
using System.Net;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IHttpConnectionFeature"/>.
+/// </summary>
+public class HttpConnectionFeature : IHttpConnectionFeature
{
- /// <summary>
- /// Default implementation for <see cref="IHttpConnectionFeature"/>.
- /// </summary>
- public class HttpConnectionFeature : IHttpConnectionFeature
- {
- /// <inheritdoc />
- public string ConnectionId { get; set; } = default!;
+ /// <inheritdoc />
+ public string ConnectionId { get; set; } = default!;
- /// <inheritdoc />
- public IPAddress? LocalIpAddress { get; set; }
+ /// <inheritdoc />
+ public IPAddress? LocalIpAddress { get; set; }
- /// <inheritdoc />
- public int LocalPort { get; set; }
+ /// <inheritdoc />
+ public int LocalPort { get; set; }
- /// <inheritdoc />
- public IPAddress? RemoteIpAddress { get; set; }
+ /// <inheritdoc />
+ public IPAddress? RemoteIpAddress { get; set; }
- /// <inheritdoc />
- public int RemotePort { get; set; }
- }
+ /// <inheritdoc />
+ public int RemotePort { get; set; }
}
diff --git a/src/Http/Http/src/Features/HttpRequestFeature.cs b/src/Http/Http/src/Features/HttpRequestFeature.cs
index 2882014c39..69d6c9e2be 100644
--- a/src/Http/Http/src/Features/HttpRequestFeature.cs
+++ b/src/Http/Http/src/Features/HttpRequestFeature.cs
@@ -3,54 +3,53 @@
using System.IO;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IHttpRequestFeature"/>.
+/// </summary>
+public class HttpRequestFeature : IHttpRequestFeature
{
/// <summary>
- /// Default implementation for <see cref="IHttpRequestFeature"/>.
+ /// Initiaizes a new instance of <see cref="HttpRequestFeature"/>.
/// </summary>
- public class HttpRequestFeature : IHttpRequestFeature
+ public HttpRequestFeature()
{
- /// <summary>
- /// Initiaizes a new instance of <see cref="HttpRequestFeature"/>.
- /// </summary>
- 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;
- }
-
- /// <inheritdoc />
- public string Protocol { get; set; }
-
- /// <inheritdoc />
- public string Scheme { get; set; }
-
- /// <inheritdoc />
- public string Method { get; set; }
-
- /// <inheritdoc />
- public string PathBase { get; set; }
-
- /// <inheritdoc />
- public string Path { get; set; }
-
- /// <inheritdoc />
- public string QueryString { get; set; }
-
- /// <inheritdoc />
- public string RawTarget { get; set; }
-
- /// <inheritdoc />
- public IHeaderDictionary Headers { get; set; }
-
- /// <inheritdoc />
- public Stream Body { get; set; }
+ 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;
}
+
+ /// <inheritdoc />
+ public string Protocol { get; set; }
+
+ /// <inheritdoc />
+ public string Scheme { get; set; }
+
+ /// <inheritdoc />
+ public string Method { get; set; }
+
+ /// <inheritdoc />
+ public string PathBase { get; set; }
+
+ /// <inheritdoc />
+ public string Path { get; set; }
+
+ /// <inheritdoc />
+ public string QueryString { get; set; }
+
+ /// <inheritdoc />
+ public string RawTarget { get; set; }
+
+ /// <inheritdoc />
+ public IHeaderDictionary Headers { get; set; }
+
+ /// <inheritdoc />
+ public Stream Body { get; set; }
}
diff --git a/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs b/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs
index 3cf0cb094f..5bd927b5da 100644
--- a/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs
+++ b/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs
@@ -4,60 +4,59 @@
using System;
using System.Threading;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IHttpRequestIdentifierFeature"/>.
+/// </summary>
+public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature
{
- /// <summary>
- /// Default implementation for <see cref="IHttpRequestIdentifierFeature"/>.
- /// </summary>
- public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature
- {
- // Base32 encoding - in ascii sort order for easy text based sorting
- private static readonly char[] s_encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV".ToCharArray();
- // 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;
+ // Base32 encoding - in ascii sort order for easy text based sorting
+ private static readonly char[] s_encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV".ToCharArray();
+ // 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;
+ private string? _id;
- /// <inheritdoc />
- public string TraceIdentifier
+ /// <inheritdoc />
+ public string TraceIdentifier
+ {
+ get
{
- get
+ // Don't incur the cost of generating the request ID until it's asked for
+ if (_id == null)
{
- // 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;
+ _id = GenerateRequestId(Interlocked.Increment(ref _requestId));
}
+ return _id;
}
+ set
+ {
+ _id = value;
+ }
+ }
- private static string GenerateRequestId(long id)
+ private static string GenerateRequestId(long id)
+ {
+ return string.Create(13, id, (buffer, value) =>
{
- return string.Create(13, id, (buffer, value) =>
- {
- var encode32Chars = s_encode32Chars;
+ var encode32Chars = s_encode32Chars;
- buffer[12] = encode32Chars[value & 31];
- buffer[11] = encode32Chars[(value >> 5) & 31];
- buffer[10] = encode32Chars[(value >> 10) & 31];
- buffer[9] = encode32Chars[(value >> 15) & 31];
- buffer[8] = encode32Chars[(value >> 20) & 31];
- buffer[7] = encode32Chars[(value >> 25) & 31];
- buffer[6] = encode32Chars[(value >> 30) & 31];
- buffer[5] = encode32Chars[(value >> 35) & 31];
- buffer[4] = encode32Chars[(value >> 40) & 31];
- buffer[3] = encode32Chars[(value >> 45) & 31];
- buffer[2] = encode32Chars[(value >> 50) & 31];
- buffer[1] = encode32Chars[(value >> 55) & 31];
- buffer[0] = encode32Chars[(value >> 60) & 31];
- });
- }
+ buffer[12] = encode32Chars[value & 31];
+ buffer[11] = encode32Chars[(value >> 5) & 31];
+ buffer[10] = encode32Chars[(value >> 10) & 31];
+ buffer[9] = encode32Chars[(value >> 15) & 31];
+ buffer[8] = encode32Chars[(value >> 20) & 31];
+ buffer[7] = encode32Chars[(value >> 25) & 31];
+ buffer[6] = encode32Chars[(value >> 30) & 31];
+ buffer[5] = encode32Chars[(value >> 35) & 31];
+ buffer[4] = encode32Chars[(value >> 40) & 31];
+ buffer[3] = encode32Chars[(value >> 45) & 31];
+ buffer[2] = encode32Chars[(value >> 50) & 31];
+ buffer[1] = encode32Chars[(value >> 55) & 31];
+ buffer[0] = encode32Chars[(value >> 60) & 31];
+ });
}
}
diff --git a/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs b/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs
index 6775f134fd..50ed489368 100644
--- a/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs
+++ b/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs
@@ -3,19 +3,18 @@
using System.Threading;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IHttpRequestLifetimeFeature"/>.
+/// </summary>
+public class HttpRequestLifetimeFeature : IHttpRequestLifetimeFeature
{
- /// <summary>
- /// Default implementation for <see cref="IHttpRequestLifetimeFeature"/>.
- /// </summary>
- public class HttpRequestLifetimeFeature : IHttpRequestLifetimeFeature
- {
- /// <inheritdoc />
- public CancellationToken RequestAborted { get; set; }
+ /// <inheritdoc />
+ public CancellationToken RequestAborted { get; set; }
- /// <inheritdoc />
- public void Abort()
- {
- }
+ /// <inheritdoc />
+ public void Abort()
+ {
}
}
diff --git a/src/Http/Http/src/Features/HttpResponseFeature.cs b/src/Http/Http/src/Features/HttpResponseFeature.cs
index fd21362f2f..a7d2fd02ca 100644
--- a/src/Http/Http/src/Features/HttpResponseFeature.cs
+++ b/src/Http/Http/src/Features/HttpResponseFeature.cs
@@ -5,46 +5,45 @@ using System;
using System.IO;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IHttpResponseFeature"/>.
+/// </summary>
+public class HttpResponseFeature : IHttpResponseFeature
{
/// <summary>
- /// Default implementation for <see cref="IHttpResponseFeature"/>.
+ /// Initializes a new instance of <see cref="HttpResponseFeature"/>.
/// </summary>
- public class HttpResponseFeature : IHttpResponseFeature
+ public HttpResponseFeature()
+ {
+ StatusCode = 200;
+ Headers = new HeaderDictionary();
+ Body = Stream.Null;
+ }
+
+ /// <inheritdoc />
+ public int StatusCode { get; set; }
+
+ /// <inheritdoc />
+ public string? ReasonPhrase { get; set; }
+
+ /// <inheritdoc />
+ public IHeaderDictionary Headers { get; set; }
+
+ /// <inheritdoc />
+ public Stream Body { get; set; }
+
+ /// <inheritdoc />
+ public virtual bool HasStarted => false;
+
+ /// <inheritdoc />
+ public virtual void OnStarting(Func<object, Task> callback, object state)
+ {
+ }
+
+ /// <inheritdoc />
+ public virtual void OnCompleted(Func<object, Task> callback, object state)
{
- /// <summary>
- /// Initializes a new instance of <see cref="HttpResponseFeature"/>.
- /// </summary>
- public HttpResponseFeature()
- {
- StatusCode = 200;
- Headers = new HeaderDictionary();
- Body = Stream.Null;
- }
-
- /// <inheritdoc />
- public int StatusCode { get; set; }
-
- /// <inheritdoc />
- public string? ReasonPhrase { get; set; }
-
- /// <inheritdoc />
- public IHeaderDictionary Headers { get; set; }
-
- /// <inheritdoc />
- public Stream Body { get; set; }
-
- /// <inheritdoc />
- public virtual bool HasStarted => false;
-
- /// <inheritdoc />
- public virtual void OnStarting(Func<object, Task> callback, object state)
- {
- }
-
- /// <inheritdoc />
- public virtual void OnCompleted(Func<object, Task> callback, object state)
- {
- }
}
}
diff --git a/src/Http/Http/src/Features/IHttpActivityFeature.cs b/src/Http/Http/src/Features/IHttpActivityFeature.cs
index 0ec4bd223f..2e7c165413 100644
--- a/src/Http/Http/src/Features/IHttpActivityFeature.cs
+++ b/src/Http/Http/src/Features/IHttpActivityFeature.cs
@@ -3,16 +3,15 @@
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Feature to access the <see cref="Activity"/> associated with a request.
+/// </summary>
+public interface IHttpActivityFeature
{
/// <summary>
- /// Feature to access the <see cref="Activity"/> associated with a request.
+ /// Returns the <see cref="Activity"/> associated with the current request.
/// </summary>
- public interface IHttpActivityFeature
- {
- /// <summary>
- /// Returns the <see cref="Activity"/> associated with the current request.
- /// </summary>
- Activity Activity { get; set; }
- }
+ Activity Activity { get; set; }
}
diff --git a/src/Http/Http/src/Features/ItemsFeature.cs b/src/Http/Http/src/Features/ItemsFeature.cs
index 5a4e1f28f6..3533617461 100644
--- a/src/Http/Http/src/Features/ItemsFeature.cs
+++ b/src/Http/Http/src/Features/ItemsFeature.cs
@@ -3,22 +3,21 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IItemsFeature"/>.
+/// </summary>
+public class ItemsFeature : IItemsFeature
{
/// <summary>
- /// Default implementation for <see cref="IItemsFeature"/>.
+ /// Initializes a new instance of <see cref="ItemsFeature"/>.
/// </summary>
- public class ItemsFeature : IItemsFeature
+ public ItemsFeature()
{
- /// <summary>
- /// Initializes a new instance of <see cref="ItemsFeature"/>.
- /// </summary>
- public ItemsFeature()
- {
- Items = new ItemsDictionary();
- }
-
- /// <inheritdoc />
- public IDictionary<object, object?> Items { get; set; }
+ Items = new ItemsDictionary();
}
+
+ /// <inheritdoc />
+ public IDictionary<object, object?> Items { get; set; }
}
diff --git a/src/Http/Http/src/Features/QueryFeature.cs b/src/Http/Http/src/Features/QueryFeature.cs
index 66c0bfa030..abc0f85c1e 100644
--- a/src/Http/Http/src/Features/QueryFeature.cs
+++ b/src/Http/Http/src/Features/QueryFeature.cs
@@ -8,219 +8,218 @@ using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IQueryFeature"/>.
+/// </summary>
+public class QueryFeature : IQueryFeature
{
+ // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
+
+ private FeatureReferences<IHttpRequestFeature> _features;
+
+ private string? _original;
+ private IQueryCollection? _parsedValues;
+
/// <summary>
- /// Default implementation for <see cref="IQueryFeature"/>.
+ /// Initializes a new instance of <see cref="QueryFeature"/>.
/// </summary>
- public class QueryFeature : IQueryFeature
+ /// <param name="query">The <see cref="IQueryCollection"/> to use as a backing store.</param>
+ public QueryFeature(IQueryCollection query)
{
- // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
-
- private FeatureReferences<IHttpRequestFeature> _features;
+ if (query == null)
+ {
+ throw new ArgumentNullException(nameof(query));
+ }
- private string? _original;
- private IQueryCollection? _parsedValues;
+ _parsedValues = query;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="QueryFeature"/>.
- /// </summary>
- /// <param name="query">The <see cref="IQueryCollection"/> to use as a backing store.</param>
- public QueryFeature(IQueryCollection query)
+ /// <summary>
+ /// Initializes a new instance of <see cref="QueryFeature"/>.
+ /// </summary>
+ /// <param name="features">The <see cref="IFeatureCollection"/> to initialize.</param>
+ public QueryFeature(IFeatureCollection features)
+ {
+ if (features == null)
{
- if (query == null)
- {
- throw new ArgumentNullException(nameof(query));
- }
-
- _parsedValues = query;
+ throw new ArgumentNullException(nameof(features));
}
- /// <summary>
- /// Initializes a new instance of <see cref="QueryFeature"/>.
- /// </summary>
- /// <param name="features">The <see cref="IFeatureCollection"/> to initialize.</param>
- public QueryFeature(IFeatureCollection features)
+ _features.Initalize(features);
+ }
+
+ private IHttpRequestFeature HttpRequestFeature =>
+ _features.Fetch(ref _features.Cache, _nullRequestFeature)!;
+
+ /// <inheritdoc />
+ public IQueryCollection Query
+ {
+ get
{
- if (features == null)
+ if (_features.Collection is null)
{
- throw new ArgumentNullException(nameof(features));
+ return _parsedValues ?? QueryCollection.Empty;
}
- _features.Initalize(features);
- }
+ var current = HttpRequestFeature.QueryString;
+ if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal))
+ {
+ _original = current;
- private IHttpRequestFeature HttpRequestFeature =>
- _features.Fetch(ref _features.Cache, _nullRequestFeature)!;
+ var result = ParseNullableQueryInternal(current);
- /// <inheritdoc />
- public IQueryCollection Query
+ _parsedValues = result is not null
+ ? new QueryCollectionInternal(result)
+ : QueryCollection.Empty;
+ }
+ return _parsedValues;
+ }
+ set
{
- get
+ _parsedValues = value;
+ if (_features.Collection != null)
{
- if (_features.Collection is null)
+ if (value == null)
{
- return _parsedValues ?? QueryCollection.Empty;
+ _original = string.Empty;
+ HttpRequestFeature.QueryString = string.Empty;
}
-
- var current = HttpRequestFeature.QueryString;
- if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal))
- {
- _original = current;
-
- var result = ParseNullableQueryInternal(current);
-
- _parsedValues = result is not null
- ? new QueryCollectionInternal(result)
- : QueryCollection.Empty;
- }
- return _parsedValues;
- }
- set
- {
- _parsedValues = value;
- if (_features.Collection != null)
+ else
{
- if (value == null)
- {
- _original = string.Empty;
- HttpRequestFeature.QueryString = string.Empty;
- }
- else
- {
- _original = QueryString.Create(_parsedValues).ToString();
- HttpRequestFeature.QueryString = _original;
- }
+ _original = QueryString.Create(_parsedValues).ToString();
+ HttpRequestFeature.QueryString = _original;
}
}
}
+ }
+
+ /// <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>
+ [SkipLocalsInit]
+ internal static AdaptiveCapacityDictionary<string, StringValues>? ParseNullableQueryInternal(string? queryString)
+ {
+ if (string.IsNullOrEmpty(queryString) || (queryString.Length == 1 && queryString[0] == '?'))
+ {
+ return null;
+ }
+ var accumulator = new KvpAccumulator();
+ var enumerable = new QueryStringEnumerable(queryString);
+ foreach (var pair in enumerable)
+ {
+ accumulator.Append(pair.DecodeName().Span, pair.DecodeValue().Span);
+ }
+
+ return accumulator.HasValues
+ ? accumulator.GetResults()
+ : null;
+ }
+
+ internal struct KvpAccumulator
+ {
/// <summary>
- /// Parse a query string into its component key and value parts.
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
/// </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>
- [SkipLocalsInit]
- internal static AdaptiveCapacityDictionary<string, StringValues>? ParseNullableQueryInternal(string? queryString)
+ private AdaptiveCapacityDictionary<string, StringValues> _accumulator;
+ private AdaptiveCapacityDictionary<string, List<string>> _expandingAccumulator;
+
+ public void Append(ReadOnlySpan<char> key, ReadOnlySpan<char> value)
+ => Append(key.ToString(), value.ToString());
+
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public void Append(string key, string value)
{
- if (string.IsNullOrEmpty(queryString) || (queryString.Length == 1 && queryString[0] == '?'))
+ if (_accumulator is null)
{
- return null;
+ _accumulator = new AdaptiveCapacityDictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
}
- var accumulator = new KvpAccumulator();
- var enumerable = new QueryStringEnumerable(queryString);
- foreach (var pair in enumerable)
+ if (!_accumulator.TryGetValue(key, out var values))
+ {
+ // First value for this key
+ _accumulator[key] = new StringValues(value);
+ }
+ else
{
- accumulator.Append(pair.DecodeName().Span, pair.DecodeValue().Span);
+ AppendToExpandingAccumulator(key, value, values);
}
- return accumulator.HasValues
- ? accumulator.GetResults()
- : null;
+ ValueCount++;
}
- internal struct KvpAccumulator
+ private void AppendToExpandingAccumulator(string key, string value, StringValues values)
{
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- private AdaptiveCapacityDictionary<string, StringValues> _accumulator;
- private AdaptiveCapacityDictionary<string, List<string>> _expandingAccumulator;
-
- public void Append(ReadOnlySpan<char> key, ReadOnlySpan<char> value)
- => Append(key.ToString(), value.ToString());
-
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public void Append(string key, string value)
+ // When there are some values for the same key, so switch to expanding accumulator, and
+ // add a zero count marker in the accumulator to indicate that switch.
+
+ if (values.Count != 0)
{
- if (_accumulator is null)
- {
- _accumulator = new AdaptiveCapacityDictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
- }
+ _accumulator[key] = default;
- if (!_accumulator.TryGetValue(key, out var values))
- {
- // First value for this key
- _accumulator[key] = new StringValues(value);
- }
- else
+ if (_expandingAccumulator is null)
{
- AppendToExpandingAccumulator(key, value, values);
+ _expandingAccumulator = new AdaptiveCapacityDictionary<string, List<string>>(capacity: 5, StringComparer.OrdinalIgnoreCase);
}
- ValueCount++;
- }
+ // Already 2 (1 existing + the new one) entries so use List's expansion mechanism for more
+ var list = new List<string>();
- private void AppendToExpandingAccumulator(string key, string value, StringValues values)
- {
- // When there are some values for the same key, so switch to expanding accumulator, and
- // add a zero count marker in the accumulator to indicate that switch.
-
- if (values.Count != 0)
- {
- _accumulator[key] = default;
+ list.AddRange(values);
+ list.Add(value);
- if (_expandingAccumulator is null)
- {
- _expandingAccumulator = new AdaptiveCapacityDictionary<string, List<string>>(capacity: 5, StringComparer.OrdinalIgnoreCase);
- }
+ _expandingAccumulator[key] = list;
+ }
+ else
+ {
+ // The marker indicates we are in the expanding accumulator, so just append to the list.
+ _expandingAccumulator[key].Add(value);
+ }
+ }
- // Already 2 (1 existing + the new one) entries so use List's expansion mechanism for more
- var list = new List<string>();
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public bool HasValues => ValueCount > 0;
- list.AddRange(values);
- list.Add(value);
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public int KeyCount => _accumulator?.Count ?? 0;
- _expandingAccumulator[key] = list;
- }
- else
- {
- // The marker indicates we are in the expanding accumulator, so just append to the list.
- _expandingAccumulator[key].Add(value);
- }
- }
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public int ValueCount { get; private set; }
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public bool HasValues => ValueCount > 0;
-
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public int KeyCount => _accumulator?.Count ?? 0;
-
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public int ValueCount { get; private set; }
-
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public AdaptiveCapacityDictionary<string, StringValues> GetResults()
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public AdaptiveCapacityDictionary<string, StringValues> GetResults()
+ {
+ if (_expandingAccumulator != null)
{
- if (_expandingAccumulator != null)
+ // Coalesce count 3+ multi-value entries into _accumulator dictionary
+ foreach (var entry in _expandingAccumulator)
{
- // Coalesce count 3+ multi-value entries into _accumulator dictionary
- foreach (var entry in _expandingAccumulator)
- {
- _accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
- }
+ _accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
}
-
- return _accumulator ?? new AdaptiveCapacityDictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
}
+
+ return _accumulator ?? new AdaptiveCapacityDictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
}
}
}
diff --git a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs
index a9aa3e8db5..0b546d0a04 100644
--- a/src/Http/Http/src/Features/RequestBodyPipeFeature.cs
+++ b/src/Http/Http/src/Features/RequestBodyPipeFeature.cs
@@ -6,50 +6,49 @@ using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IRequestBodyPipeFeature"/>.
+/// </summary>
+public class RequestBodyPipeFeature : IRequestBodyPipeFeature
{
+ private PipeReader? _internalPipeReader;
+ private Stream? _streamInstanceWhenWrapped;
+ private readonly HttpContext _context;
+
/// <summary>
- /// Default implementation for <see cref="IRequestBodyPipeFeature"/>.
+ /// Initializes a new instance of <see cref="IRequestBodyPipeFeature"/>.
/// </summary>
- public class RequestBodyPipeFeature : IRequestBodyPipeFeature
+ /// <param name="context"></param>
+ public RequestBodyPipeFeature(HttpContext context)
{
- private PipeReader? _internalPipeReader;
- private Stream? _streamInstanceWhenWrapped;
- private readonly HttpContext _context;
-
- /// <summary>
- /// Initializes a new instance of <see cref="IRequestBodyPipeFeature"/>.
- /// </summary>
- /// <param name="context"></param>
- public RequestBodyPipeFeature(HttpContext context)
+ if (context == null)
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
- _context = context;
+ throw new ArgumentNullException(nameof(context));
}
+ _context = context;
+ }
- /// <inheritdoc />
- public PipeReader Reader
+ /// <inheritdoc />
+ public PipeReader Reader
+ {
+ get
{
- get
+ if (_internalPipeReader == null ||
+ !ReferenceEquals(_streamInstanceWhenWrapped, _context.Request.Body))
{
- if (_internalPipeReader == null ||
- !ReferenceEquals(_streamInstanceWhenWrapped, _context.Request.Body))
- {
- _streamInstanceWhenWrapped = _context.Request.Body;
- _internalPipeReader = PipeReader.Create(_context.Request.Body);
-
- _context.Response.OnCompleted((self) =>
- {
- ((PipeReader)self).Complete();
- return Task.CompletedTask;
- }, _internalPipeReader);
- }
+ _streamInstanceWhenWrapped = _context.Request.Body;
+ _internalPipeReader = PipeReader.Create(_context.Request.Body);
- return _internalPipeReader;
+ _context.Response.OnCompleted((self) =>
+ {
+ ((PipeReader)self).Complete();
+ return Task.CompletedTask;
+ }, _internalPipeReader);
}
+
+ return _internalPipeReader;
}
}
}
diff --git a/src/Http/Http/src/Features/RequestCookiesFeature.cs b/src/Http/Http/src/Features/RequestCookiesFeature.cs
index 6adb9a2c62..9add9ab21d 100644
--- a/src/Http/Http/src/Features/RequestCookiesFeature.cs
+++ b/src/Http/Http/src/Features/RequestCookiesFeature.cs
@@ -6,96 +6,95 @@ using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IRequestCookiesFeature"/>.
+/// </summary>
+public class RequestCookiesFeature : IRequestCookiesFeature
{
+ // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
+
+ private FeatureReferences<IHttpRequestFeature> _features;
+ private StringValues _original;
+ private IRequestCookieCollection? _parsedValues;
+
/// <summary>
- /// Default implementation for <see cref="IRequestCookiesFeature"/>.
+ /// Initializes a new instance of <see cref="RequestCookiesFeature"/>.
/// </summary>
- public class RequestCookiesFeature : IRequestCookiesFeature
+ /// <param name="cookies">The <see cref="IRequestCookieCollection"/> to use as backing store.</param>
+ public RequestCookiesFeature(IRequestCookieCollection cookies)
{
- // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
-
- private FeatureReferences<IHttpRequestFeature> _features;
- private StringValues _original;
- private IRequestCookieCollection? _parsedValues;
-
- /// <summary>
- /// Initializes a new instance of <see cref="RequestCookiesFeature"/>.
- /// </summary>
- /// <param name="cookies">The <see cref="IRequestCookieCollection"/> to use as backing store.</param>
- public RequestCookiesFeature(IRequestCookieCollection cookies)
+ if (cookies == null)
{
- if (cookies == null)
- {
- throw new ArgumentNullException(nameof(cookies));
- }
-
- _parsedValues = cookies;
+ throw new ArgumentNullException(nameof(cookies));
}
- /// <summary>
- /// Initializes a new instance of <see cref="RequestCookiesFeature"/>.
- /// </summary>
- /// <param name="features">The <see cref="IFeatureCollection"/> to initialize.</param>
- public RequestCookiesFeature(IFeatureCollection features)
- {
- if (features == null)
- {
- throw new ArgumentNullException(nameof(features));
- }
+ _parsedValues = cookies;
+ }
- _features.Initalize(features);
+ /// <summary>
+ /// Initializes a new instance of <see cref="RequestCookiesFeature"/>.
+ /// </summary>
+ /// <param name="features">The <see cref="IFeatureCollection"/> to initialize.</param>
+ public RequestCookiesFeature(IFeatureCollection features)
+ {
+ if (features == null)
+ {
+ throw new ArgumentNullException(nameof(features));
}
- private IHttpRequestFeature HttpRequestFeature =>
- _features.Fetch(ref _features.Cache, _nullRequestFeature)!;
+ _features.Initalize(features);
+ }
+
+ private IHttpRequestFeature HttpRequestFeature =>
+ _features.Fetch(ref _features.Cache, _nullRequestFeature)!;
- /// <inheritdoc />
- public IRequestCookieCollection Cookies
+ /// <inheritdoc />
+ public IRequestCookieCollection Cookies
+ {
+ get
{
- get
+ if (_features.Collection == null)
{
- if (_features.Collection == null)
+ if (_parsedValues == null)
{
- if (_parsedValues == null)
- {
- _parsedValues = RequestCookieCollection.Empty;
- }
- return _parsedValues;
+ _parsedValues = RequestCookieCollection.Empty;
}
+ return _parsedValues;
+ }
- var headers = HttpRequestFeature.Headers;
- var current = headers.Cookie;
-
- if (_parsedValues == null || _original != current)
- {
- _original = current;
- _parsedValues = RequestCookieCollection.Parse(current);
- }
+ var headers = HttpRequestFeature.Headers;
+ var current = headers.Cookie;
- return _parsedValues;
+ if (_parsedValues == null || _original != current)
+ {
+ _original = current;
+ _parsedValues = RequestCookieCollection.Parse(current);
}
- set
+
+ return _parsedValues;
+ }
+ set
+ {
+ _parsedValues = value;
+ _original = StringValues.Empty;
+ if (_features.Collection != null)
{
- _parsedValues = value;
- _original = StringValues.Empty;
- if (_features.Collection != null)
+ if (_parsedValues == null || _parsedValues.Count == 0)
{
- if (_parsedValues == null || _parsedValues.Count == 0)
- {
- HttpRequestFeature.Headers.Cookie = default;
- }
- else
+ HttpRequestFeature.Headers.Cookie = default;
+ }
+ else
+ {
+ var headers = new List<string>(_parsedValues.Count);
+ foreach (var pair in _parsedValues)
{
- var headers = new List<string>(_parsedValues.Count);
- foreach (var pair in _parsedValues)
- {
- headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString());
- }
- _original = headers.ToArray();
- HttpRequestFeature.Headers.Cookie = _original;
+ headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString());
}
+ _original = headers.ToArray();
+ HttpRequestFeature.Headers.Cookie = _original;
}
}
}
diff --git a/src/Http/Http/src/Features/RequestServicesFeature.cs b/src/Http/Http/src/Features/RequestServicesFeature.cs
index b670170c6e..0a29155c10 100644
--- a/src/Http/Http/src/Features/RequestServicesFeature.cs
+++ b/src/Http/Http/src/Features/RequestServicesFeature.cs
@@ -5,89 +5,88 @@ using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// An implementation for <see cref="IServiceProvidersFeature"/> for accessing request services.
+/// </summary>
+public class RequestServicesFeature : IServiceProvidersFeature, IDisposable, IAsyncDisposable
{
+ private readonly IServiceScopeFactory? _scopeFactory;
+ private IServiceProvider? _requestServices;
+ private IServiceScope? _scope;
+ private bool _requestServicesSet;
+ private readonly HttpContext _context;
+
/// <summary>
- /// An implementation for <see cref="IServiceProvidersFeature"/> for accessing request services.
+ /// Initializes a new instance of <see cref="RequestServicesFeature"/>.
/// </summary>
- public class RequestServicesFeature : IServiceProvidersFeature, IDisposable, IAsyncDisposable
+ /// <param name="context">The <see cref="HttpContext"/>.</param>
+ /// <param name="scopeFactory">The <see cref="IServiceScopeFactory"/>.</param>
+ public RequestServicesFeature(HttpContext context, IServiceScopeFactory? scopeFactory)
{
- private readonly IServiceScopeFactory? _scopeFactory;
- private IServiceProvider? _requestServices;
- private IServiceScope? _scope;
- private bool _requestServicesSet;
- private readonly HttpContext _context;
-
- /// <summary>
- /// Initializes a new instance of <see cref="RequestServicesFeature"/>.
- /// </summary>
- /// <param name="context">The <see cref="HttpContext"/>.</param>
- /// <param name="scopeFactory">The <see cref="IServiceScopeFactory"/>.</param>
- public RequestServicesFeature(HttpContext context, IServiceScopeFactory? scopeFactory)
- {
- _context = context;
- _scopeFactory = scopeFactory;
- }
+ _context = context;
+ _scopeFactory = scopeFactory;
+ }
- /// <inheritdoc />
- public IServiceProvider RequestServices
+ /// <inheritdoc />
+ public IServiceProvider RequestServices
+ {
+ get
{
- get
+ if (!_requestServicesSet && _scopeFactory != null)
{
- if (!_requestServicesSet && _scopeFactory != null)
- {
- _context.Response.RegisterForDisposeAsync(this);
- _scope = _scopeFactory.CreateScope();
- _requestServices = _scope.ServiceProvider;
- _requestServicesSet = true;
- }
- return _requestServices!;
- }
-
- set
- {
- _requestServices = value;
+ _context.Response.RegisterForDisposeAsync(this);
+ _scope = _scopeFactory.CreateScope();
+ _requestServices = _scope.ServiceProvider;
_requestServicesSet = true;
}
+ return _requestServices!;
}
- /// <inheritdoc />
- public ValueTask DisposeAsync()
+ set
{
- switch (_scope)
- {
- case IAsyncDisposable asyncDisposable:
- var vt = asyncDisposable.DisposeAsync();
- if (!vt.IsCompletedSuccessfully)
- {
- return Awaited(this, vt);
- }
- // If its a IValueTaskSource backed ValueTask,
- // inform it its result has been read so it can reset
- vt.GetAwaiter().GetResult();
- break;
- case IDisposable disposable:
- disposable.Dispose();
- break;
- }
+ _requestServices = value;
+ _requestServicesSet = true;
+ }
+ }
- _scope = null;
- _requestServices = null;
+ /// <inheritdoc />
+ public ValueTask DisposeAsync()
+ {
+ switch (_scope)
+ {
+ case IAsyncDisposable asyncDisposable:
+ var vt = asyncDisposable.DisposeAsync();
+ if (!vt.IsCompletedSuccessfully)
+ {
+ return Awaited(this, vt);
+ }
+ // If its a IValueTaskSource backed ValueTask,
+ // inform it its result has been read so it can reset
+ vt.GetAwaiter().GetResult();
+ break;
+ case IDisposable disposable:
+ disposable.Dispose();
+ break;
+ }
- return default;
+ _scope = null;
+ _requestServices = null;
- static async ValueTask Awaited(RequestServicesFeature servicesFeature, ValueTask vt)
- {
- await vt;
- servicesFeature._scope = null;
- servicesFeature._requestServices = null;
- }
- }
+ return default;
- /// <inheritdoc />
- public void Dispose()
+ static async ValueTask Awaited(RequestServicesFeature servicesFeature, ValueTask vt)
{
- DisposeAsync().AsTask().GetAwaiter().GetResult();
+ await vt;
+ servicesFeature._scope = null;
+ servicesFeature._requestServices = null;
}
}
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ DisposeAsync().AsTask().GetAwaiter().GetResult();
+ }
}
diff --git a/src/Http/Http/src/Features/ResponseCookiesFeature.cs b/src/Http/Http/src/Features/ResponseCookiesFeature.cs
index 8e4da199fe..ff4cdd121b 100644
--- a/src/Http/Http/src/Features/ResponseCookiesFeature.cs
+++ b/src/Http/Http/src/Features/ResponseCookiesFeature.cs
@@ -5,54 +5,53 @@ using System;
using System.Text;
using Microsoft.Extensions.ObjectPool;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation of <see cref="IResponseCookiesFeature"/>.
+/// </summary>
+public class ResponseCookiesFeature : IResponseCookiesFeature
{
+ private readonly IFeatureCollection _features;
+ private IResponseCookies? _cookiesCollection;
+
/// <summary>
- /// Default implementation of <see cref="IResponseCookiesFeature"/>.
+ /// Initializes a new <see cref="ResponseCookiesFeature"/> instance.
/// </summary>
- public class ResponseCookiesFeature : IResponseCookiesFeature
+ /// <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)
{
- private readonly IFeatureCollection _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)
- {
- _features = features ?? throw new ArgumentNullException(nameof(features));
- }
+ _features = features ?? throw new ArgumentNullException(nameof(features));
+ }
- /// <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>
- [Obsolete("This constructor is obsolete and will be removed in a future version.")]
- public ResponseCookiesFeature(IFeatureCollection features, ObjectPool<StringBuilder>? builderPool)
- {
- _features = features ?? throw new ArgumentNullException(nameof(features));
- }
+ /// <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>
+ [Obsolete("This constructor is obsolete and will be removed in a future version.")]
+ public ResponseCookiesFeature(IFeatureCollection features, ObjectPool<StringBuilder>? builderPool)
+ {
+ _features = features ?? throw new ArgumentNullException(nameof(features));
+ }
- /// <inheritdoc />
- public IResponseCookies Cookies
+ /// <inheritdoc />
+ public IResponseCookies Cookies
+ {
+ get
{
- get
+ if (_cookiesCollection == null)
{
- if (_cookiesCollection == null)
- {
- _cookiesCollection = new ResponseCookies(_features);
- }
-
- return _cookiesCollection;
+ _cookiesCollection = new ResponseCookies(_features);
}
+
+ return _cookiesCollection;
}
}
}
diff --git a/src/Http/Http/src/Features/RouteValuesFeature.cs b/src/Http/Http/src/Features/RouteValuesFeature.cs
index bca9a66c54..e97df5b8e4 100644
--- a/src/Http/Http/src/Features/RouteValuesFeature.cs
+++ b/src/Http/Http/src/Features/RouteValuesFeature.cs
@@ -3,32 +3,31 @@
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// A feature for routing values. Use <see cref="HttpContext.Features"/>
+/// to access the values associated with the current request.
+/// </summary>
+public class RouteValuesFeature : IRouteValuesFeature
{
+ private RouteValueDictionary? _routeValues;
+
/// <summary>
- /// A feature for routing values. Use <see cref="HttpContext.Features"/>
- /// to access the values associated with the current request.
+ /// Gets or sets the <see cref="RouteValueDictionary"/> associated with the currrent
+ /// request.
/// </summary>
- public class RouteValuesFeature : IRouteValuesFeature
+ public RouteValueDictionary RouteValues
{
- private RouteValueDictionary? _routeValues;
-
- /// <summary>
- /// Gets or sets the <see cref="RouteValueDictionary"/> associated with the currrent
- /// request.
- /// </summary>
- public RouteValueDictionary RouteValues
+ get
{
- get
+ if (_routeValues == null)
{
- if (_routeValues == null)
- {
- _routeValues = new RouteValueDictionary();
- }
-
- return _routeValues;
+ _routeValues = new RouteValueDictionary();
}
- set => _routeValues = value;
+
+ return _routeValues;
}
+ set => _routeValues = value;
}
}
diff --git a/src/Http/Http/src/Features/ServiceProvidersFeature.cs b/src/Http/Http/src/Features/ServiceProvidersFeature.cs
index ee92def94a..a687137bcc 100644
--- a/src/Http/Http/src/Features/ServiceProvidersFeature.cs
+++ b/src/Http/Http/src/Features/ServiceProvidersFeature.cs
@@ -3,14 +3,13 @@
using System;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="IServiceProvidersFeature"/>.
+/// </summary>
+public class ServiceProvidersFeature : IServiceProvidersFeature
{
- /// <summary>
- /// Default implementation for <see cref="IServiceProvidersFeature"/>.
- /// </summary>
- public class ServiceProvidersFeature : IServiceProvidersFeature
- {
- /// <inheritdoc />
- public IServiceProvider RequestServices { get; set; } = default!;
- }
+ /// <inheritdoc />
+ public IServiceProvider RequestServices { get; set; } = default!;
}
diff --git a/src/Http/Http/src/Features/TlsConnectionFeature.cs b/src/Http/Http/src/Features/TlsConnectionFeature.cs
index a11afe3881..0f52f65f84 100644
--- a/src/Http/Http/src/Features/TlsConnectionFeature.cs
+++ b/src/Http/Http/src/Features/TlsConnectionFeature.cs
@@ -5,20 +5,19 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+/// <summary>
+/// Default implementation for <see cref="TlsConnectionFeature"/>.
+/// </summary>
+public class TlsConnectionFeature : ITlsConnectionFeature
{
- /// <summary>
- /// Default implementation for <see cref="TlsConnectionFeature"/>.
- /// </summary>
- public class TlsConnectionFeature : ITlsConnectionFeature
- {
- /// <inheritdoc />
- public X509Certificate2? ClientCertificate { get; set; }
+ /// <inheritdoc />
+ public X509Certificate2? ClientCertificate { get; set; }
- /// <inheritdoc />
- public Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken)
- {
- return Task.FromResult(ClientCertificate);
- }
+ /// <inheritdoc />
+ public Task<X509Certificate2?> GetClientCertificateAsync(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(ClientCertificate);
}
}
diff --git a/src/Http/Http/src/FormCollection.cs b/src/Http/Http/src/FormCollection.cs
index 2ec522f7aa..aeef7c09dd 100644
--- a/src/Http/Http/src/FormCollection.cs
+++ b/src/Http/Http/src/FormCollection.cs
@@ -6,230 +6,229 @@ using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Contains the parsed HTTP form values.
+/// </summary>
+public class FormCollection : IFormCollection
{
/// <summary>
- /// Contains the parsed HTTP form values.
+ /// An empty <see cref="FormCollection"/>.
/// </summary>
- public class FormCollection : IFormCollection
- {
- /// <summary>
- /// An empty <see cref="FormCollection"/>.
- /// </summary>
- public static readonly FormCollection Empty = new FormCollection();
- private static readonly string[] EmptyKeys = Array.Empty<string>();
+ public static readonly FormCollection Empty = new FormCollection();
+ private static readonly string[] EmptyKeys = Array.Empty<string>();
- // Pre-box
- private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = default(Enumerator);
- private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
+ // Pre-box
+ private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = default(Enumerator);
+ private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
- private static readonly IFormFileCollection EmptyFiles = new FormFileCollection();
+ private static readonly IFormFileCollection EmptyFiles = new FormFileCollection();
- private IFormFileCollection? _files;
+ private IFormFileCollection? _files;
- private FormCollection()
- {
- // For static Empty
- }
+ private FormCollection()
+ {
+ // For static Empty
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FormCollection"/>.
- /// </summary>
- /// <param name="fields">The backing fields.</param>
- /// <param name="files">The files associated with the form.</param>
- public FormCollection(Dictionary<string, StringValues>? fields, IFormFileCollection? files = null)
- {
- // can be null
- Store = fields;
- _files = files;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormCollection"/>.
+ /// </summary>
+ /// <param name="fields">The backing fields.</param>
+ /// <param name="files">The files associated with the form.</param>
+ public FormCollection(Dictionary<string, StringValues>? fields, IFormFileCollection? files = null)
+ {
+ // can be null
+ Store = fields;
+ _files = files;
+ }
- /// <summary>
- /// Gets the files associated with the HTTP form.
- /// </summary>
- public IFormFileCollection Files
- {
- get => _files ?? EmptyFiles;
- private set => _files = value;
- }
+ /// <summary>
+ /// Gets the files associated with the HTTP form.
+ /// </summary>
+ public IFormFileCollection Files
+ {
+ get => _files ?? EmptyFiles;
+ private set => _files = value;
+ }
- private Dictionary<string, StringValues>? Store { get; set; }
+ 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 <see cref="StringValues"/>
- /// or <see cref="StringValues.Empty"/> if the key is not present.</returns>
- public StringValues this[string key]
+ /// <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 <see cref="StringValues"/>
+ /// or <see cref="StringValues.Empty"/> if the key is not present.</returns>
+ public StringValues this[string key]
+ {
+ get
{
- get
+ if (Store == null)
{
- if (Store == null)
- {
- return StringValues.Empty;
- }
-
- if (TryGetValue(key, out var value))
- {
- return value;
- }
return StringValues.Empty;
}
- }
- /// <inheritdoc />
- public int Count
- {
- get
+ if (TryGetValue(key, out var value))
{
- return Store?.Count ?? 0;
+ return value;
}
+ return StringValues.Empty;
}
+ }
- /// <inheritdoc />
- public ICollection<string> Keys
+ /// <inheritdoc />
+ public int Count
+ {
+ get
{
- get
- {
- if (Store == null)
- {
- return EmptyKeys;
- }
- return Store.Keys;
- }
+ return Store?.Count ?? 0;
}
+ }
- /// <inheritdoc />
- public bool ContainsKey(string key)
+ /// <inheritdoc />
+ public ICollection<string> Keys
+ {
+ get
{
if (Store == null)
{
- return false;
+ return EmptyKeys;
}
- return Store.ContainsKey(key);
+ return Store.Keys;
}
+ }
- /// <inheritdoc />
- public bool TryGetValue(string key, out StringValues value)
+ /// <inheritdoc />
+ public bool ContainsKey(string key)
+ {
+ if (Store == null)
{
- if (Store == null)
- {
- value = default(StringValues);
- return false;
- }
- return Store.TryGetValue(key, out value);
+ return false;
}
+ return Store.ContainsKey(key);
+ }
- /// <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()
+ /// <inheritdoc />
+ 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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return default;
- }
// Non-boxed Enumerator
- return new Enumerator(Store.GetEnumerator());
+ return default;
}
+ // 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()
+ /// <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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return EmptyIEnumeratorType;
- }
- // Boxed Enumerator
- return Store.GetEnumerator();
+ // 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();
+ }
+
+ /// <summary>
+ /// Enumerates a <see cref="FormCollection"/>.
+ /// </summary>
+ public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ {
+ // Do NOT make this readonly, or MoveNext will not work
+ private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+ private readonly bool _notEmpty;
+
+ internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+ {
+ _dictionaryEnumerator = dictionaryEnumerator;
+ _notEmpty = true;
}
/// <summary>
- /// Returns an enumerator that iterates through a collection, boxes in non-empty path.
+ /// Advances the enumerator to the next element of the <see cref="FormCollection"/>.
/// </summary>
- /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
- IEnumerator IEnumerable.GetEnumerator()
+ /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
+ /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
+ public bool MoveNext()
{
- if (Store == null || Store.Count == 0)
+ if (_notEmpty)
{
- // Non-boxed Enumerator
- return EmptyIEnumerator;
+ return _dictionaryEnumerator.MoveNext();
}
- // Boxed Enumerator
- return Store.GetEnumerator();
+ return false;
}
/// <summary>
- /// Enumerates a <see cref="FormCollection"/>.
+ /// Gets the element at the current position of the enumerator.
/// </summary>
- public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ public KeyValuePair<string, StringValues> Current
{
- // Do NOT make this readonly, or MoveNext will not work
- private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
- private readonly bool _notEmpty;
-
- internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
- {
- _dictionaryEnumerator = dictionaryEnumerator;
- _notEmpty = true;
- }
-
- /// <summary>
- /// Advances the enumerator to the next element of the <see cref="FormCollection"/>.
- /// </summary>
- /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
- /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
- public bool MoveNext()
+ get
{
if (_notEmpty)
{
- return _dictionaryEnumerator.MoveNext();
- }
- return false;
- }
-
- /// <summary>
- /// Gets the element at the current position of the enumerator.
- /// </summary>
- public KeyValuePair<string, StringValues> Current
- {
- get
- {
- if (_notEmpty)
- {
- return _dictionaryEnumerator.Current;
- }
- return default;
+ return _dictionaryEnumerator.Current;
}
+ return default;
}
+ }
- /// <inheritdoc />
- public void Dispose()
- {
- }
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ }
- object IEnumerator.Current
+ object IEnumerator.Current
+ {
+ get
{
- get
- {
- return Current;
- }
+ return Current;
}
+ }
- void IEnumerator.Reset()
+ void IEnumerator.Reset()
+ {
+ if (_notEmpty)
{
- if (_notEmpty)
- {
- ((IEnumerator)_dictionaryEnumerator).Reset();
- }
+ ((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
diff --git a/src/Http/Http/src/FormFile.cs b/src/Http/Http/src/FormFile.cs
index b7b78bed11..ff57e28cde 100644
--- a/src/Http/Http/src/FormFile.cs
+++ b/src/Http/Http/src/FormFile.cs
@@ -1,115 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Default implementation of <see cref="IFormFile"/>.
+/// </summary>
+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;
+
/// <summary>
- /// Default implementation of <see cref="IFormFile"/>.
+ /// Initializes a new instance of <see cref="FormFile"/>.
/// </summary>
- public class FormFile : IFormFile
+ /// <param name="baseStream">The <see cref="Stream"/> containing the form file.</param>
+ /// <param name="baseStreamOffset">The offset at which the form file begins.</param>
+ /// <param name="length">The length of the form file.</param>
+ /// <param name="name">The name of the form file from the <c>Content-Disposition</c> header.</param>
+ /// <param name="fileName">The file name from the <c>Content-Disposition</c> header.</param>
+ public FormFile(Stream baseStream, long baseStreamOffset, long length, string name, string fileName)
{
- // Stream.CopyTo method uses 80KB as the default buffer size.
- private const int DefaultBufferSize = 80 * 1024;
-
- private readonly Stream _baseStream;
- private readonly long _baseStreamOffset;
+ _baseStream = baseStream;
+ _baseStreamOffset = baseStreamOffset;
+ Length = length;
+ Name = name;
+ FileName = fileName;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FormFile"/>.
- /// </summary>
- /// <param name="baseStream">The <see cref="Stream"/> containing the form file.</param>
- /// <param name="baseStreamOffset">The offset at which the form file begins.</param>
- /// <param name="length">The length of the form file.</param>
- /// <param name="name">The name of the form file from the <c>Content-Disposition</c> header.</param>
- /// <param name="fileName">The file name from the <c>Content-Disposition</c> header.</param>
- 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.ContentDisposition.ToString(); }
+ set { Headers.ContentDisposition = value; }
+ }
- /// <summary>
- /// Gets the raw Content-Disposition header of the uploaded file.
- /// </summary>
- public string ContentDisposition
- {
- get { return Headers.ContentDisposition.ToString(); }
- set { Headers.ContentDisposition = value; }
- }
+ /// <summary>
+ /// Gets the raw Content-Type header of the uploaded file.
+ /// </summary>
+ public string ContentType
+ {
+ get { return Headers.ContentType.ToString(); }
+ set { Headers.ContentType = value; }
+ }
- /// <summary>
- /// Gets the raw Content-Type header of the uploaded file.
- /// </summary>
- public string ContentType
- {
- get { return Headers.ContentType.ToString(); }
- set { Headers.ContentType = value; }
- }
+ /// <summary>
+ /// Gets the header dictionary of the uploaded file.
+ /// </summary>
+ public IHeaderDictionary Headers { get; set; } = default!;
- /// <summary>
- /// Gets the header dictionary of the uploaded file.
- /// </summary>
- public IHeaderDictionary Headers { get; set; } = default!;
+ /// <summary>
+ /// Gets the file length in bytes.
+ /// </summary>
+ public long Length { get; }
- /// <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 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>
- /// 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>
- /// Opens the request stream for reading the uploaded file.
- /// </summary>
- public 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>
+ public void CopyTo(Stream target)
+ {
+ if (target == null)
{
- return new ReferenceReadStream(_baseStream, _baseStreamOffset, Length);
+ throw new ArgumentNullException(nameof(target));
}
- /// <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)
+ using (var readStream = OpenReadStream())
{
- if (target == null)
- {
- throw new ArgumentNullException(nameof(target));
- }
-
- using (var readStream = OpenReadStream())
- {
- readStream.CopyTo(target, DefaultBufferSize);
- }
+ 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))
+ /// <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)
{
- if (target == null)
- {
- throw new ArgumentNullException(nameof(target));
- }
+ throw new ArgumentNullException(nameof(target));
+ }
- using (var readStream = OpenReadStream())
- {
- await readStream.CopyToAsync(target, DefaultBufferSize, cancellationToken);
- }
+ using (var readStream = OpenReadStream())
+ {
+ await readStream.CopyToAsync(target, DefaultBufferSize, cancellationToken);
}
}
}
diff --git a/src/Http/Http/src/FormFileCollection.cs b/src/Http/Http/src/FormFileCollection.cs
index 0e3e460e91..f38fbba16c 100644
--- a/src/Http/Http/src/FormFileCollection.cs
+++ b/src/Http/Http/src/FormFileCollection.cs
@@ -4,44 +4,43 @@
using System;
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Default implementation of <see cref="IFormFileCollection"/>.
+/// </summary>
+public class FormFileCollection : List<IFormFile>, IFormFileCollection
{
- /// <summary>
- /// Default implementation of <see cref="IFormFileCollection"/>.
- /// </summary>
- public class FormFileCollection : List<IFormFile>, IFormFileCollection
- {
- /// <inheritdoc />
- public IFormFile? this[string name] => GetFile(name);
+ /// <inheritdoc />
+ public IFormFile? this[string name] => GetFile(name);
- /// <inheritdoc />
- public IFormFile? GetFile(string name)
+ /// <inheritdoc />
+ public IFormFile? GetFile(string name)
+ {
+ foreach (var file in this)
{
- foreach (var file in this)
+ if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase))
- {
- return file;
- }
+ return file;
}
-
- return null;
}
- /// <inheritdoc />
- public IReadOnlyList<IFormFile> GetFiles(string name)
- {
- var files = new List<IFormFile>();
+ return null;
+ }
- foreach (var file in this)
+ /// <inheritdoc />
+ public IReadOnlyList<IFormFile> GetFiles(string name)
+ {
+ var files = new List<IFormFile>();
+
+ foreach (var file in this)
+ {
+ if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase))
- {
- files.Add(file);
- }
+ files.Add(file);
}
-
- return files;
}
+
+ return files;
}
}
diff --git a/src/Http/Http/src/HeaderDictionary.cs b/src/Http/Http/src/HeaderDictionary.cs
index 4cda2646ba..a1cb5db23a 100644
--- a/src/Http/Http/src/HeaderDictionary.cs
+++ b/src/Http/Http/src/HeaderDictionary.cs
@@ -8,430 +8,429 @@ using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+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>();
+ // Pre-box
+ private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = default(Enumerator);
+ private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
+
/// <summary>
- /// Represents a wrapper for RequestHeaders and ResponseHeaders.
+ /// Initializes a new instance of <see cref="HeaderDictionary"/>.
/// </summary>
- public class HeaderDictionary : IHeaderDictionary
+ public HeaderDictionary()
{
- private static readonly string[] EmptyKeys = Array.Empty<string>();
- private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
- // Pre-box
- private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = default(Enumerator);
- private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="HeaderDictionary"/>.
- /// </summary>
- public HeaderDictionary()
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="HeaderDictionary"/>.
+ /// </summary>
+ /// <param name="store">The value to use as the backing store.</param>
+ public HeaderDictionary(Dictionary<string, StringValues>? store)
+ {
+ Store = store;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="HeaderDictionary"/>.
- /// </summary>
- /// <param name="store">The value to use as the backing store.</param>
- public HeaderDictionary(Dictionary<string, StringValues>? store)
- {
- Store = store;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="HeaderDictionary"/>.
+ /// </summary>
+ /// <param name="capacity">The initial number of headers that this instance can contain.</param>
+ public HeaderDictionary(int capacity)
+ {
+ EnsureStore(capacity);
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="HeaderDictionary"/>.
- /// </summary>
- /// <param name="capacity">The initial number of headers that this instance can contain.</param>
- public HeaderDictionary(int capacity)
+ private Dictionary<string, StringValues>? Store { get; set; }
+
+ [MemberNotNull(nameof(Store))]
+ private void EnsureStore(int capacity)
+ {
+ if (Store == null)
{
- EnsureStore(capacity);
+ Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
}
+ }
- private Dictionary<string, StringValues>? Store { get; set; }
-
- [MemberNotNull(nameof(Store))]
- private void EnsureStore(int capacity)
+ /// <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)
{
- Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
+ return StringValues.Empty;
}
- }
- /// <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]
+ if (TryGetValue(key, out var value))
+ {
+ return value;
+ }
+ return StringValues.Empty;
+ }
+ set
{
- get
+ if (key == null)
{
- if (Store == null)
- {
- return StringValues.Empty;
- }
+ throw new ArgumentNullException(nameof(key));
+ }
+ ThrowIfReadOnly();
- if (TryGetValue(key, out var value))
- {
- return value;
- }
- return StringValues.Empty;
+ if (value.Count == 0)
+ {
+ Store?.Remove(key);
}
- set
+ else
{
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
- ThrowIfReadOnly();
-
- if (value.Count == 0)
- {
- Store?.Remove(key);
- }
- else
- {
- EnsureStore(1);
- Store[key] = value;
- }
+ EnsureStore(1);
+ Store[key] = value;
}
}
+ }
- StringValues IDictionary<string, StringValues>.this[string key]
+ StringValues IDictionary<string, StringValues>.this[string key]
+ {
+ get { return this[key]; }
+ set
{
- get { return this[key]; }
- set
- {
- ThrowIfReadOnly();
- this[key] = value;
- }
+ ThrowIfReadOnly();
+ this[key] = value;
}
+ }
- /// <inheritdoc />
- public long? ContentLength
+ /// <inheritdoc />
+ public long? ContentLength
+ {
+ get
{
- 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))
{
- 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 value;
+ }
- return null;
+ return null;
+ }
+ set
+ {
+ ThrowIfReadOnly();
+ if (value.HasValue)
+ {
+ this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.GetValueOrDefault());
}
- set
+ else
{
- ThrowIfReadOnly();
- if (value.HasValue)
- {
- this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.GetValueOrDefault());
- }
- else
- {
- this.Remove(HeaderNames.ContentLength);
- }
+ 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 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; }
+ /// <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; }
- /// <summary>
- /// Gets the collection of HTTP header names in this instance.
- /// </summary>
- public ICollection<string> Keys
+ /// <summary>
+ /// Gets the collection of HTTP header names in this instance.
+ /// </summary>
+ public ICollection<string> Keys
+ {
+ get
{
- get
+ if (Store == null)
{
- if (Store == null)
- {
- return EmptyKeys;
- }
- return Store.Keys;
+ return EmptyKeys;
}
+ return Store.Keys;
}
+ }
- /// <summary>
- /// Gets the collection of HTTP header values in this instance.
- /// </summary>
- public ICollection<StringValues> Values
+ /// <summary>
+ /// Gets the collection of HTTP header values in this instance.
+ /// </summary>
+ public ICollection<StringValues> Values
+ {
+ get
{
- get
+ if (Store == null)
{
- if (Store == null)
- {
- return EmptyValues;
- }
- return Store.Values;
+ return EmptyValues;
}
+ return Store.Values;
}
+ }
- /// <summary>
- /// Adds a new header item to the collection.
- /// </summary>
- /// <param name="item">The item to add.</param>
- public void Add(KeyValuePair<string, StringValues> item)
+ /// <summary>
+ /// Adds a new header item to the collection.
+ /// </summary>
+ /// <param name="item">The item to add.</param>
+ public void Add(KeyValuePair<string, StringValues> item)
+ {
+ if (item.Key == null)
{
- if (item.Key == null)
- {
- throw new ArgumentException("The key is null");
- }
- ThrowIfReadOnly();
- EnsureStore(1);
- Store.Add(item.Key, item.Value);
+ throw new ArgumentException("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)
+ /// <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)
{
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
- ThrowIfReadOnly();
- EnsureStore(1);
- Store.Add(key, value);
+ throw new ArgumentNullException(nameof(key));
}
+ ThrowIfReadOnly();
+ EnsureStore(1);
+ Store.Add(key, value);
+ }
- /// <summary>
- /// Clears the entire list of objects.
- /// </summary>
- public void Clear()
+ /// <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)
+ {
+ if (Store == null ||
+ !Store.TryGetValue(item.Key, out var value) ||
+ !StringValues.Equals(value, item.Value))
{
- ThrowIfReadOnly();
- Store?.Clear();
+ return false;
}
+ return true;
+ }
- /// <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)
+ /// <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)
{
- if (Store == null ||
- !Store.TryGetValue(item.Key, out var value) ||
- !StringValues.Equals(value, item.Value))
- {
- return false;
- }
- return true;
+ return false;
}
+ return Store.ContainsKey(key);
+ }
- /// <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)
+ /// <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)
{
- if (Store == null)
- {
- return false;
- }
- return Store.ContainsKey(key);
+ return;
}
- /// <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)
+ foreach (var item in Store)
{
- if (Store == null)
- {
- return;
- }
+ array[arrayIndex] = item;
+ arrayIndex++;
+ }
+ }
- 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;
}
- /// <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)
+ if (Store.TryGetValue(item.Key, out var value) && StringValues.Equals(item.Value, value))
{
- ThrowIfReadOnly();
- if (Store == null)
- {
- return false;
- }
+ return Store.Remove(item.Key);
+ }
+ return false;
+ }
- if (Store.TryGetValue(item.Key, out var value) && StringValues.Equals(item.Value, value))
- {
- return Store.Remove(item.Key);
- }
+ /// <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>
- /// 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)
+ /// <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)
{
- ThrowIfReadOnly();
- if (Store == null)
- {
- return false;
- }
- return Store.Remove(key);
+ value = default(StringValues);
+ return false;
}
+ return Store.TryGetValue(key, out value);
+ }
- /// <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)
+ /// <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)
{
- if (Store == null)
- {
- value = default(StringValues);
- return false;
- }
- return Store.TryGetValue(key, out value);
+ // Non-boxed Enumerator
+ return default;
}
+ return new Enumerator(Store.GetEnumerator());
+ }
- /// <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()
+ /// <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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return default;
- }
- return new Enumerator(Store.GetEnumerator());
+ // 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<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return EmptyIEnumeratorType;
- }
- return Store.GetEnumerator();
+ // Non-boxed Enumerator
+ return EmptyIEnumerator;
}
+ 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()
+ private void ThrowIfReadOnly()
+ {
+ if (IsReadOnly)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return EmptyIEnumerator;
- }
- return Store.GetEnumerator();
+ throw new InvalidOperationException("The response headers cannot be modified because the response has already started.");
}
+ }
+
+ /// <summary>
+ /// Enumerates a <see cref="HeaderDictionary"/>.
+ /// </summary>
+ public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ {
+ // Do NOT make this readonly, or MoveNext will not work
+ private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+ private readonly bool _notEmpty;
- private void ThrowIfReadOnly()
+ internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
{
- if (IsReadOnly)
- {
- throw new InvalidOperationException("The response headers cannot be modified because the response has already started.");
- }
+ _dictionaryEnumerator = dictionaryEnumerator;
+ _notEmpty = true;
}
/// <summary>
- /// Enumerates a <see cref="HeaderDictionary"/>.
+ /// Advances the enumerator to the next element of the <see cref="HeaderDictionary"/>.
/// </summary>
- public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
+ /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
+ public bool MoveNext()
{
- // Do NOT make this readonly, or MoveNext will not work
- private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
- private readonly bool _notEmpty;
-
- internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+ if (_notEmpty)
{
- _dictionaryEnumerator = dictionaryEnumerator;
- _notEmpty = true;
+ return _dictionaryEnumerator.MoveNext();
}
+ return false;
+ }
- /// <summary>
- /// Advances the enumerator to the next element of the <see cref="HeaderDictionary"/>.
- /// </summary>
- /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
- /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
- public bool MoveNext()
+ /// <summary>
+ /// Gets the element at the current position of the enumerator.
+ /// </summary>
+ public KeyValuePair<string, StringValues> Current
+ {
+ get
{
if (_notEmpty)
{
- return _dictionaryEnumerator.MoveNext();
- }
- return false;
- }
-
- /// <summary>
- /// Gets the element at the current position of the enumerator.
- /// </summary>
- public KeyValuePair<string, StringValues> Current
- {
- get
- {
- if (_notEmpty)
- {
- return _dictionaryEnumerator.Current;
- }
- return default(KeyValuePair<string, StringValues>);
+ return _dictionaryEnumerator.Current;
}
+ return default(KeyValuePair<string, StringValues>);
}
+ }
- /// <inheritdoc />
- public void Dispose()
- {
- }
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ }
- object IEnumerator.Current
+ object IEnumerator.Current
+ {
+ get
{
- get
- {
- return Current;
- }
+ return Current;
}
+ }
- void IEnumerator.Reset()
+ void IEnumerator.Reset()
+ {
+ if (_notEmpty)
{
- if (_notEmpty)
- {
- ((IEnumerator)_dictionaryEnumerator).Reset();
- }
+ ((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
diff --git a/src/Http/Http/src/HttpContextAccessor.cs b/src/Http/Http/src/HttpContextAccessor.cs
index 08c43d44d2..3135b582c6 100644
--- a/src/Http/Http/src/HttpContextAccessor.cs
+++ b/src/Http/Http/src/HttpContextAccessor.cs
@@ -3,43 +3,42 @@
using System.Threading;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Provides an implementation of <see cref="IHttpContextAccessor" /> based on the current execution context.
+/// </summary>
+public class HttpContextAccessor : IHttpContextAccessor
{
- /// <summary>
- /// Provides an implementation of <see cref="IHttpContextAccessor" /> based on the current execution context.
- /// </summary>
- public class HttpContextAccessor : IHttpContextAccessor
- {
- private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
+ private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent = new AsyncLocal<HttpContextHolder>();
- /// <inheritdoc/>
- public HttpContext? HttpContext
+ /// <inheritdoc/>
+ public HttpContext? HttpContext
+ {
+ get
{
- get
+ return _httpContextCurrent.Value?.Context;
+ }
+ set
+ {
+ var holder = _httpContextCurrent.Value;
+ if (holder != null)
{
- return _httpContextCurrent.Value?.Context;
+ // Clear current HttpContext trapped in the AsyncLocals, as its done.
+ holder.Context = null;
}
- set
- {
- var holder = _httpContextCurrent.Value;
- if (holder != null)
- {
- // Clear current HttpContext trapped in the AsyncLocals, as its done.
- holder.Context = null;
- }
- if (value != null)
- {
- // Use an object indirection to hold the HttpContext in the AsyncLocal,
- // so it can be cleared in all ExecutionContexts when its cleared.
- _httpContextCurrent.Value = new HttpContextHolder { Context = value };
- }
+ if (value != null)
+ {
+ // Use an object indirection to hold the HttpContext in the AsyncLocal,
+ // so it can be cleared in all ExecutionContexts when its cleared.
+ _httpContextCurrent.Value = new HttpContextHolder { Context = value };
}
}
+ }
- private class HttpContextHolder
- {
- public HttpContext? Context;
- }
+ private class HttpContextHolder
+ {
+ public HttpContext? Context;
}
}
diff --git a/src/Http/Http/src/HttpServiceCollectionExtensions.cs b/src/Http/Http/src/HttpServiceCollectionExtensions.cs
index 22b2800d80..9955f48696 100644
--- a/src/Http/Http/src/HttpServiceCollectionExtensions.cs
+++ b/src/Http/Http/src/HttpServiceCollectionExtensions.cs
@@ -5,27 +5,26 @@ using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;
-namespace Microsoft.Extensions.DependencyInjection
+namespace Microsoft.Extensions.DependencyInjection;
+
+/// <summary>
+/// Extension methods for configuring HttpContext services.
+/// </summary>
+public static class HttpServiceCollectionExtensions
{
/// <summary>
- /// Extension methods for configuring HttpContext services.
+ /// Adds a default implementation for the <see cref="IHttpContextAccessor"/> service.
/// </summary>
- public static class HttpServiceCollectionExtensions
+ /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+ /// <returns>The service collection.</returns>
+ public static IServiceCollection AddHttpContextAccessor(this IServiceCollection services)
{
- /// <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)
{
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
-
- services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
- return services;
+ throw new ArgumentNullException(nameof(services));
}
+
+ services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+ return services;
}
}
diff --git a/src/Http/Http/src/Internal/BufferingHelper.cs b/src/Http/Http/src/Internal/BufferingHelper.cs
index db224aed8e..7f8c9d32e0 100644
--- a/src/Http/Http/src/Internal/BufferingHelper.cs
+++ b/src/Http/Http/src/Internal/BufferingHelper.cs
@@ -5,49 +5,48 @@ using System;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.WebUtilities;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal static class BufferingHelper
{
- internal static class BufferingHelper
- {
- internal const int DefaultBufferThreshold = 1024 * 30;
+ internal const int DefaultBufferThreshold = 1024 * 30;
- public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
+ public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
+ {
+ if (request == null)
{
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
+ throw new ArgumentNullException(nameof(request));
+ }
- var body = request.Body;
- if (!body.CanSeek)
- {
- var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
- request.Body = fileStream;
- request.HttpContext.Response.RegisterForDispose(fileStream);
- }
- return request;
+ var body = request.Body;
+ if (!body.CanSeek)
+ {
+ var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
+ 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)
+ 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)
{
- if (section == null)
- {
- throw new ArgumentNullException(nameof(section));
- }
- if (registerForDispose == null)
- {
- throw new ArgumentNullException(nameof(registerForDispose));
- }
+ throw new ArgumentNullException(nameof(registerForDispose));
+ }
- var body = section.Body;
- if (!body.CanSeek)
- {
- var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
- section.Body = fileStream;
- registerForDispose(fileStream);
- }
- return section;
+ var body = section.Body;
+ if (!body.CanSeek)
+ {
+ var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
+ section.Body = fileStream;
+ registerForDispose(fileStream);
}
+ return section;
}
}
diff --git a/src/Http/Http/src/Internal/DefaultConnectionInfo.cs b/src/Http/Http/src/Internal/DefaultConnectionInfo.cs
index 7aeb898644..aba7af8827 100644
--- a/src/Http/Http/src/Internal/DefaultConnectionInfo.cs
+++ b/src/Http/Http/src/Internal/DefaultConnectionInfo.cs
@@ -6,101 +6,100 @@ using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections.Features;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Http
+internal sealed class DefaultConnectionInfo : ConnectionInfo
{
- internal sealed class DefaultConnectionInfo : ConnectionInfo
+ // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
+ private static readonly Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
+ private static readonly Func<IFeatureCollection, IConnectionLifetimeNotificationFeature> _newConnectionLifetime = f => new DefaultConnectionLifetimeNotificationFeature(f.Get<IHttpResponseFeature>());
+
+ private FeatureReferences<FeatureInterfaces> _features;
+
+ public DefaultConnectionInfo(IFeatureCollection features)
+ {
+ Initialize(features);
+ }
+
+ public void Initialize(IFeatureCollection features)
+ {
+ _features.Initalize(features);
+ }
+
+ public void Initialize(IFeatureCollection features, int revision)
+ {
+ _features.Initalize(features, revision);
+ }
+
+ public void Uninitialize()
+ {
+ _features = default;
+ }
+
+ private IHttpConnectionFeature HttpConnectionFeature =>
+ _features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature)!;
+
+ private ITlsConnectionFeature TlsConnectionFeature =>
+ _features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature)!;
+
+ private IConnectionLifetimeNotificationFeature ConnectionLifetime =>
+ _features.Fetch(ref _features.Cache.ConnectionLifetime, _newConnectionLifetime)!;
+
+ /// <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 = default)
+ {
+ return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken);
+ }
+
+ public override void RequestClose()
+ {
+ ConnectionLifetime.RequestClose();
+ }
+
+ struct FeatureInterfaces
{
- // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
- private static readonly Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
- private static readonly Func<IFeatureCollection, IConnectionLifetimeNotificationFeature> _newConnectionLifetime = f => new DefaultConnectionLifetimeNotificationFeature(f.Get<IHttpResponseFeature>());
-
- private FeatureReferences<FeatureInterfaces> _features;
-
- public DefaultConnectionInfo(IFeatureCollection features)
- {
- Initialize(features);
- }
-
- public void Initialize(IFeatureCollection features)
- {
- _features.Initalize(features);
- }
-
- public void Initialize(IFeatureCollection features, int revision)
- {
- _features.Initalize(features, revision);
- }
-
- public void Uninitialize()
- {
- _features = default;
- }
-
- private IHttpConnectionFeature HttpConnectionFeature =>
- _features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature)!;
-
- private ITlsConnectionFeature TlsConnectionFeature=>
- _features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature)!;
-
- private IConnectionLifetimeNotificationFeature ConnectionLifetime =>
- _features.Fetch(ref _features.Cache.ConnectionLifetime, _newConnectionLifetime)!;
-
- /// <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 = default)
- {
- return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken);
- }
-
- public override void RequestClose()
- {
- ConnectionLifetime.RequestClose();
- }
-
- struct FeatureInterfaces
- {
- public IHttpConnectionFeature? Connection;
- public ITlsConnectionFeature? TlsConnection;
- public IConnectionLifetimeNotificationFeature? ConnectionLifetime;
- }
+ public IHttpConnectionFeature? Connection;
+ public ITlsConnectionFeature? TlsConnection;
+ public IConnectionLifetimeNotificationFeature? ConnectionLifetime;
}
}
diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs
index 6b3a2b730e..1f36aaba55 100644
--- a/src/Http/Http/src/Internal/DefaultHttpRequest.cs
+++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs
@@ -10,183 +10,182 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal sealed class DefaultHttpRequest : HttpRequest
{
- internal sealed class DefaultHttpRequest : HttpRequest
- {
- private const string Http = "http";
- private const string Https = "https";
-
- // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
- private static readonly Func<IFeatureCollection, IQueryFeature?> _newQueryFeature = f => new QueryFeature(f);
- private static readonly Func<DefaultHttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r, r._context.FormOptions ?? FormOptions.Default);
- private static readonly Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
- private static readonly Func<IFeatureCollection, IRouteValuesFeature> _newRouteValuesFeature = f => new RouteValuesFeature();
- private static readonly Func<HttpContext, IRequestBodyPipeFeature> _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context);
-
- private readonly DefaultHttpContext _context;
- private FeatureReferences<FeatureInterfaces> _features;
-
- public DefaultHttpRequest(DefaultHttpContext context)
- {
- _context = context;
- _features.Initalize(context.Features);
- }
-
- public void Initialize()
- {
- _features.Initalize(_context.Features);
- }
-
- public void Initialize(int revision)
- {
- _features.Initalize(_context.Features, revision);
- }
-
- public void Uninitialize()
- {
- _features = default;
- }
-
- 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)!;
-
- private IRouteValuesFeature RouteValuesFeature =>
- _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature)!;
-
- private IRequestBodyPipeFeature RequestBodyPipeFeature =>
- _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newRequestBodyPipeFeature)!;
-
- public override PathString PathBase
- {
- get { return new PathString(HttpRequestFeature.PathBase); }
- set { HttpRequestFeature.PathBase = value.Value ?? string.Empty; }
- }
-
- public override PathString Path
- {
- get { return new PathString(HttpRequestFeature.Path); }
- set { HttpRequestFeature.Path = value.Value ?? string.Empty; }
- }
-
- public override QueryString QueryString
- {
- get { return new QueryString(HttpRequestFeature.QueryString); }
- set { HttpRequestFeature.QueryString = value.Value ?? string.Empty; }
- }
-
- 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(Https, Scheme, StringComparison.OrdinalIgnoreCase); }
- set { Scheme = value ? Https : Http; }
- }
-
- public override HostString Host
- {
- get { return HostString.FromUriComponent(Headers.Host.ToString()); }
- 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.ContentType; }
- set { Headers.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);
- }
-
- public override RouteValueDictionary RouteValues
- {
- get { return RouteValuesFeature.RouteValues; }
- set { RouteValuesFeature.RouteValues = value; }
- }
-
- public override PipeReader BodyReader
- {
- get { return RequestBodyPipeFeature.Reader; }
- }
-
- struct FeatureInterfaces
- {
- public IHttpRequestFeature? Request;
- public IQueryFeature? Query;
- public IFormFeature? Form;
- public IRequestCookiesFeature? Cookies;
- public IRouteValuesFeature? RouteValues;
- public IRequestBodyPipeFeature? BodyPipe;
- }
+ private const string Http = "http";
+ private const string Https = "https";
+
+ // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
+ private static readonly Func<IFeatureCollection, IQueryFeature?> _newQueryFeature = f => new QueryFeature(f);
+ private static readonly Func<DefaultHttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r, r._context.FormOptions ?? FormOptions.Default);
+ private static readonly Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
+ private static readonly Func<IFeatureCollection, IRouteValuesFeature> _newRouteValuesFeature = f => new RouteValuesFeature();
+ private static readonly Func<HttpContext, IRequestBodyPipeFeature> _newRequestBodyPipeFeature = context => new RequestBodyPipeFeature(context);
+
+ private readonly DefaultHttpContext _context;
+ private FeatureReferences<FeatureInterfaces> _features;
+
+ public DefaultHttpRequest(DefaultHttpContext context)
+ {
+ _context = context;
+ _features.Initalize(context.Features);
+ }
+
+ public void Initialize()
+ {
+ _features.Initalize(_context.Features);
+ }
+
+ public void Initialize(int revision)
+ {
+ _features.Initalize(_context.Features, revision);
+ }
+
+ public void Uninitialize()
+ {
+ _features = default;
+ }
+
+ 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)!;
+
+ private IRouteValuesFeature RouteValuesFeature =>
+ _features.Fetch(ref _features.Cache.RouteValues, _newRouteValuesFeature)!;
+
+ private IRequestBodyPipeFeature RequestBodyPipeFeature =>
+ _features.Fetch(ref _features.Cache.BodyPipe, this.HttpContext, _newRequestBodyPipeFeature)!;
+
+ public override PathString PathBase
+ {
+ get { return new PathString(HttpRequestFeature.PathBase); }
+ set { HttpRequestFeature.PathBase = value.Value ?? string.Empty; }
+ }
+
+ public override PathString Path
+ {
+ get { return new PathString(HttpRequestFeature.Path); }
+ set { HttpRequestFeature.Path = value.Value ?? string.Empty; }
+ }
+
+ public override QueryString QueryString
+ {
+ get { return new QueryString(HttpRequestFeature.QueryString); }
+ set { HttpRequestFeature.QueryString = value.Value ?? string.Empty; }
+ }
+
+ 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(Https, Scheme, StringComparison.OrdinalIgnoreCase); }
+ set { Scheme = value ? Https : Http; }
+ }
+
+ public override HostString Host
+ {
+ get { return HostString.FromUriComponent(Headers.Host.ToString()); }
+ 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.ContentType; }
+ set { Headers.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);
+ }
+
+ public override RouteValueDictionary RouteValues
+ {
+ get { return RouteValuesFeature.RouteValues; }
+ set { RouteValuesFeature.RouteValues = value; }
+ }
+
+ public override PipeReader BodyReader
+ {
+ get { return RequestBodyPipeFeature.Reader; }
+ }
+
+ struct FeatureInterfaces
+ {
+ public IHttpRequestFeature? Request;
+ public IQueryFeature? Query;
+ public IFormFeature? Form;
+ public IRequestCookiesFeature? Cookies;
+ public IRouteValuesFeature? RouteValues;
+ public IRequestBodyPipeFeature? BodyPipe;
}
}
diff --git a/src/Http/Http/src/Internal/DefaultHttpResponse.cs b/src/Http/Http/src/Internal/DefaultHttpResponse.cs
index d5a66396e9..f694460f61 100644
--- a/src/Http/Http/src/Internal/DefaultHttpResponse.cs
+++ b/src/Http/Http/src/Internal/DefaultHttpResponse.cs
@@ -9,172 +9,171 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal sealed class DefaultHttpResponse : HttpResponse
{
- internal sealed class DefaultHttpResponse : HttpResponse
- {
- // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IHttpResponseFeature?> _nullResponseFeature = f => null;
- private static readonly Func<IFeatureCollection, IHttpResponseBodyFeature?> _nullResponseBodyFeature = f => null;
- private static readonly Func<IFeatureCollection, IResponseCookiesFeature?> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
+ // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IHttpResponseFeature?> _nullResponseFeature = f => null;
+ private static readonly Func<IFeatureCollection, IHttpResponseBodyFeature?> _nullResponseBodyFeature = f => null;
+ private static readonly Func<IFeatureCollection, IResponseCookiesFeature?> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
- private readonly DefaultHttpContext _context;
- private FeatureReferences<FeatureInterfaces> _features;
+ private readonly DefaultHttpContext _context;
+ private FeatureReferences<FeatureInterfaces> _features;
- public DefaultHttpResponse(DefaultHttpContext context)
- {
- _context = context;
- _features.Initalize(context.Features);
- }
+ public DefaultHttpResponse(DefaultHttpContext context)
+ {
+ _context = context;
+ _features.Initalize(context.Features);
+ }
- public void Initialize()
- {
- _features.Initalize(_context.Features);
- }
+ public void Initialize()
+ {
+ _features.Initalize(_context.Features);
+ }
- public void Initialize(int revision)
- {
- _features.Initalize(_context.Features, revision);
- }
+ public void Initialize(int revision)
+ {
+ _features.Initalize(_context.Features, revision);
+ }
- public void Uninitialize()
- {
- _features = default;
- }
+ public void Uninitialize()
+ {
+ _features = default;
+ }
- private IHttpResponseFeature HttpResponseFeature =>
- _features.Fetch(ref _features.Cache.Response, _nullResponseFeature)!;
+ private IHttpResponseFeature HttpResponseFeature =>
+ _features.Fetch(ref _features.Cache.Response, _nullResponseFeature)!;
- private IHttpResponseBodyFeature HttpResponseBodyFeature =>
- _features.Fetch(ref _features.Cache.ResponseBody, _nullResponseBodyFeature)!;
+ private IHttpResponseBodyFeature HttpResponseBodyFeature =>
+ _features.Fetch(ref _features.Cache.ResponseBody, _nullResponseBodyFeature)!;
- private IResponseCookiesFeature ResponseCookiesFeature =>
- _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature)!;
+ private IResponseCookiesFeature ResponseCookiesFeature =>
+ _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature)!;
- public override HttpContext HttpContext { get { return _context; } }
+ public override HttpContext HttpContext { get { return _context; } }
- public override int StatusCode
- {
- get { return HttpResponseFeature.StatusCode; }
- set { HttpResponseFeature.StatusCode = value; }
- }
+ public override int StatusCode
+ {
+ get { return HttpResponseFeature.StatusCode; }
+ set { HttpResponseFeature.StatusCode = value; }
+ }
- public override IHeaderDictionary Headers
- {
- get { return HttpResponseFeature.Headers; }
- }
+ public override IHeaderDictionary Headers
+ {
+ get { return HttpResponseFeature.Headers; }
+ }
- public override Stream Body
+ public override Stream Body
+ {
+ get { return HttpResponseBodyFeature.Stream; }
+ set
{
- get { return HttpResponseBodyFeature.Stream; }
- set
+ var otherFeature = _features.Collection.Get<IHttpResponseBodyFeature>()!;
+
+ if (otherFeature is StreamResponseBodyFeature streamFeature
+ && streamFeature.PriorFeature != null
+ && object.ReferenceEquals(value, streamFeature.PriorFeature.Stream))
{
- var otherFeature = _features.Collection.Get<IHttpResponseBodyFeature>()!;
-
- if (otherFeature is StreamResponseBodyFeature streamFeature
- && streamFeature.PriorFeature != null
- && object.ReferenceEquals(value, streamFeature.PriorFeature.Stream))
- {
- // They're reverting the stream back to the prior one. Revert the whole feature.
- _features.Collection.Set(streamFeature.PriorFeature);
- return;
- }
-
- _features.Collection.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(value, otherFeature));
+ // They're reverting the stream back to the prior one. Revert the whole feature.
+ _features.Collection.Set(streamFeature.PriorFeature);
+ return;
}
+
+ _features.Collection.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(value, otherFeature));
}
+ }
- public override long? ContentLength
+ public override long? ContentLength
+ {
+ get { return Headers.ContentLength; }
+ set { Headers.ContentLength = value; }
+ }
+
+ public override string? ContentType
+ {
+ get
{
- get { return Headers.ContentLength; }
- set { Headers.ContentLength = value; }
+ return Headers.ContentType;
}
-
- public override string? ContentType
+ set
{
- get
+ if (string.IsNullOrEmpty(value))
{
- return Headers.ContentType;
+ HttpResponseFeature.Headers.ContentType = default;
}
- set
+ else
{
- if (string.IsNullOrEmpty(value))
- {
- HttpResponseFeature.Headers.ContentType = default;
- }
- else
- {
- HttpResponseFeature.Headers.ContentType = value;
- }
+ HttpResponseFeature.Headers.ContentType = value;
}
}
+ }
- public override IResponseCookies Cookies
- {
- get { return ResponseCookiesFeature.Cookies; }
- }
+ public override IResponseCookies Cookies
+ {
+ get { return ResponseCookiesFeature.Cookies; }
+ }
- public override bool HasStarted
- {
- get { return HttpResponseFeature.HasStarted; }
- }
+ public override bool HasStarted
+ {
+ get { return HttpResponseFeature.HasStarted; }
+ }
- public override PipeWriter BodyWriter
- {
- get { return HttpResponseBodyFeature.Writer; }
- }
+ public override PipeWriter BodyWriter
+ {
+ get { return HttpResponseBodyFeature.Writer; }
+ }
- public override void OnStarting(Func<object, Task> callback, object state)
+ public override void OnStarting(Func<object, Task> callback, object state)
+ {
+ if (callback == null)
{
- if (callback == null)
- {
- throw new ArgumentNullException(nameof(callback));
- }
-
- HttpResponseFeature.OnStarting(callback, state);
+ throw new ArgumentNullException(nameof(callback));
}
- public override void OnCompleted(Func<object, Task> callback, object state)
- {
- if (callback == null)
- {
- throw new ArgumentNullException(nameof(callback));
- }
+ HttpResponseFeature.OnStarting(callback, state);
+ }
- HttpResponseFeature.OnCompleted(callback, state);
+ public override void OnCompleted(Func<object, Task> callback, object state)
+ {
+ if (callback == null)
+ {
+ throw new ArgumentNullException(nameof(callback));
}
- public override void Redirect(string location, bool permanent)
- {
- if (permanent)
- {
- HttpResponseFeature.StatusCode = 301;
- }
- else
- {
- HttpResponseFeature.StatusCode = 302;
- }
+ HttpResponseFeature.OnCompleted(callback, state);
+ }
- Headers.Location = location;
+ public override void Redirect(string location, bool permanent)
+ {
+ if (permanent)
+ {
+ HttpResponseFeature.StatusCode = 301;
}
-
- public override Task StartAsync(CancellationToken cancellationToken = default)
+ else
{
- if (HasStarted)
- {
- return Task.CompletedTask;
- }
-
- return HttpResponseBodyFeature.StartAsync(cancellationToken);
+ HttpResponseFeature.StatusCode = 302;
}
- public override Task CompleteAsync() => HttpResponseBodyFeature.CompleteAsync();
+ Headers.Location = location;
+ }
- struct FeatureInterfaces
+ public override Task StartAsync(CancellationToken cancellationToken = default)
+ {
+ if (HasStarted)
{
- public IHttpResponseFeature? Response;
- public IHttpResponseBodyFeature? ResponseBody;
- public IResponseCookiesFeature? Cookies;
+ return Task.CompletedTask;
}
+
+ return HttpResponseBodyFeature.StartAsync(cancellationToken);
+ }
+
+ public override Task CompleteAsync() => HttpResponseBodyFeature.CompleteAsync();
+
+ struct FeatureInterfaces
+ {
+ public IHttpResponseFeature? Response;
+ public IHttpResponseBodyFeature? ResponseBody;
+ public IResponseCookiesFeature? Cookies;
}
}
diff --git a/src/Http/Http/src/Internal/DefaultWebSocketManager.cs b/src/Http/Http/src/Internal/DefaultWebSocketManager.cs
index 3263655f4c..e9b86490a2 100644
--- a/src/Http/Http/src/Internal/DefaultWebSocketManager.cs
+++ b/src/Http/Http/src/Internal/DefaultWebSocketManager.cs
@@ -8,79 +8,78 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal sealed class DefaultWebSocketManager : WebSocketManager
{
- internal sealed class DefaultWebSocketManager : WebSocketManager
- {
- // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
- private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
- private static readonly Func<IFeatureCollection, IHttpWebSocketFeature?> _nullWebSocketFeature = f => null;
+ // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+ private static readonly Func<IFeatureCollection, IHttpRequestFeature?> _nullRequestFeature = f => null;
+ private static readonly Func<IFeatureCollection, IHttpWebSocketFeature?> _nullWebSocketFeature = f => null;
- private FeatureReferences<FeatureInterfaces> _features;
- private static readonly WebSocketAcceptContext _defaultWebSocketAcceptContext = new WebSocketAcceptContext();
+ private FeatureReferences<FeatureInterfaces> _features;
+ private static readonly WebSocketAcceptContext _defaultWebSocketAcceptContext = new WebSocketAcceptContext();
- public DefaultWebSocketManager(IFeatureCollection features)
- {
- Initialize(features);
- }
+ public DefaultWebSocketManager(IFeatureCollection features)
+ {
+ Initialize(features);
+ }
- public void Initialize(IFeatureCollection features)
- {
- _features.Initalize(features);
- }
+ public void Initialize(IFeatureCollection features)
+ {
+ _features.Initalize(features);
+ }
- public void Initialize(IFeatureCollection features, int revision)
- {
- _features.Initalize(features, revision);
- }
+ public void Initialize(IFeatureCollection features, int revision)
+ {
+ _features.Initalize(features, revision);
+ }
- public void Uninitialize()
- {
- _features = default;
- }
+ public void Uninitialize()
+ {
+ _features = default;
+ }
- private IHttpRequestFeature HttpRequestFeature =>
- _features.Fetch(ref _features.Cache.Request, _nullRequestFeature)!;
+ private IHttpRequestFeature HttpRequestFeature =>
+ _features.Fetch(ref _features.Cache.Request, _nullRequestFeature)!;
- private IHttpWebSocketFeature WebSocketFeature =>
- _features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature)!;
+ private IHttpWebSocketFeature WebSocketFeature =>
+ _features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature)!;
- public override bool IsWebSocketRequest
+ public override bool IsWebSocketRequest
+ {
+ get
{
- get
- {
- return WebSocketFeature != null && WebSocketFeature.IsWebSocketRequest;
- }
+ return WebSocketFeature != null && WebSocketFeature.IsWebSocketRequest;
}
+ }
- public override IList<string> WebSocketRequestedProtocols
+ public override IList<string> WebSocketRequestedProtocols
+ {
+ get
{
- get
- {
- return HttpRequestFeature.Headers.GetCommaSeparatedValues(HeaderNames.WebSocketSubProtocols);
- }
+ return HttpRequestFeature.Headers.GetCommaSeparatedValues(HeaderNames.WebSocketSubProtocols);
}
+ }
- public override Task<WebSocket> AcceptWebSocketAsync(string? subProtocol)
- {
- var acceptContext = subProtocol is null ? _defaultWebSocketAcceptContext :
- new WebSocketAcceptContext() { SubProtocol = subProtocol };
- return AcceptWebSocketAsync(acceptContext);
- }
+ public override Task<WebSocket> AcceptWebSocketAsync(string? subProtocol)
+ {
+ var acceptContext = subProtocol is null ? _defaultWebSocketAcceptContext :
+ new WebSocketAcceptContext() { SubProtocol = subProtocol };
+ return AcceptWebSocketAsync(acceptContext);
+ }
- public override Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext acceptContext)
+ public override Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext acceptContext)
+ {
+ if (WebSocketFeature == null)
{
- if (WebSocketFeature == null)
- {
- throw new NotSupportedException("WebSockets are not supported");
- }
- return WebSocketFeature.AcceptAsync(acceptContext);
+ throw new NotSupportedException("WebSockets are not supported");
}
+ return WebSocketFeature.AcceptAsync(acceptContext);
+ }
- struct FeatureInterfaces
- {
- public IHttpRequestFeature? Request;
- public IHttpWebSocketFeature? WebSockets;
- }
+ struct FeatureInterfaces
+ {
+ public IHttpRequestFeature? Request;
+ public IHttpWebSocketFeature? WebSockets;
}
}
diff --git a/src/Http/Http/src/Internal/ItemsDictionary.cs b/src/Http/Http/src/Internal/ItemsDictionary.cs
index 2ad6a2d3aa..ac58b7356d 100644
--- a/src/Http/Http/src/Internal/ItemsDictionary.cs
+++ b/src/Http/Http/src/Internal/ItemsDictionary.cs
@@ -5,160 +5,159 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal class ItemsDictionary : IDictionary<object, object?>
{
- internal class ItemsDictionary : IDictionary<object, object?>
- {
- private IDictionary<object, object?>? _items;
+ private IDictionary<object, object?>? _items;
- public ItemsDictionary()
- {}
+ public ItemsDictionary()
+ { }
- public ItemsDictionary(IDictionary<object, object?> items)
- {
- _items = items;
- }
+ public ItemsDictionary(IDictionary<object, object?> items)
+ {
+ _items = items;
+ }
- public IDictionary<object, object?> Items => this;
+ public IDictionary<object, object?> Items => this;
- // Replace the indexer with one that returns null for missing values
- object? IDictionary<object, object?>.this[object key]
+ // Replace the indexer with one that returns null for missing values
+ object? IDictionary<object, object?>.this[object key]
+ {
+ get
{
- get
- {
- if (_items != null && _items.TryGetValue(key, out var value))
- {
- return value;
- }
- return null;
- }
- set
+ if (_items != null && _items.TryGetValue(key, out var value))
{
- EnsureDictionary();
- _items[key] = value;
+ return value;
}
+ return null;
}
-
- void IDictionary<object, object?>.Add(object key, object? value)
+ set
{
EnsureDictionary();
- _items.Add(key, value);
+ _items[key] = value;
}
+ }
+
+ void IDictionary<object, object?>.Add(object key, object? value)
+ {
+ EnsureDictionary();
+ _items.Add(key, value);
+ }
- bool IDictionary<object, object?>.ContainsKey(object key)
- => _items != null && _items.ContainsKey(key);
+ bool IDictionary<object, object?>.ContainsKey(object key)
+ => _items != null && _items.ContainsKey(key);
- ICollection<object> IDictionary<object, object?>.Keys
+ ICollection<object> IDictionary<object, object?>.Keys
+ {
+ get
{
- get
+ if (_items == null)
{
- if (_items == null)
- {
- return EmptyDictionary.Dictionary.Keys;
- }
-
- return _items.Keys;
+ return EmptyDictionary.Dictionary.Keys;
}
+
+ return _items.Keys;
}
+ }
- bool IDictionary<object, object?>.Remove(object key)
- => _items != null && _items.Remove(key);
+ bool IDictionary<object, object?>.Remove(object key)
+ => _items != null && _items.Remove(key);
- bool IDictionary<object, object?>.TryGetValue(object key, out object? value)
- {
- value = null;
- return _items != null && _items.TryGetValue(key, out value);
- }
+ bool IDictionary<object, object?>.TryGetValue(object key, out object? value)
+ {
+ value = null;
+ return _items != null && _items.TryGetValue(key, out value);
+ }
- ICollection<object?> IDictionary<object, object?>.Values
+ ICollection<object?> IDictionary<object, object?>.Values
+ {
+ get
{
- get
+ if (_items == null)
{
- if (_items == null)
- {
- return EmptyDictionary.Dictionary.Values;
- }
-
- return _items.Values;
+ return EmptyDictionary.Dictionary.Values;
}
- }
- void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item)
- {
- EnsureDictionary();
- _items.Add(item);
+ return _items.Values;
}
+ }
- void ICollection<KeyValuePair<object, object?>>.Clear() => _items?.Clear();
+ void ICollection<KeyValuePair<object, object?>>.Add(KeyValuePair<object, object?> item)
+ {
+ EnsureDictionary();
+ _items.Add(item);
+ }
- bool ICollection<KeyValuePair<object, object?>>.Contains(KeyValuePair<object, object?> item)
- => _items != null && _items.Contains(item);
+ void ICollection<KeyValuePair<object, object?>>.Clear() => _items?.Clear();
- void ICollection<KeyValuePair<object, object?>>.CopyTo(KeyValuePair<object, object?>[] array, int arrayIndex)
- {
- if (_items == null)
- {
- //Delegate to Empty Dictionary to do the argument checking.
- EmptyDictionary.Collection.CopyTo(array, arrayIndex);
- }
+ bool ICollection<KeyValuePair<object, object?>>.Contains(KeyValuePair<object, object?> item)
+ => _items != null && _items.Contains(item);
- _items?.CopyTo(array, arrayIndex);
+ void ICollection<KeyValuePair<object, object?>>.CopyTo(KeyValuePair<object, object?>[] array, int arrayIndex)
+ {
+ if (_items == null)
+ {
+ //Delegate to Empty Dictionary to do the argument checking.
+ EmptyDictionary.Collection.CopyTo(array, arrayIndex);
}
- int ICollection<KeyValuePair<object, object?>>.Count => _items?.Count ?? 0;
+ _items?.CopyTo(array, arrayIndex);
+ }
- bool ICollection<KeyValuePair<object, object?>>.IsReadOnly => _items?.IsReadOnly ?? false;
+ int ICollection<KeyValuePair<object, object?>>.Count => _items?.Count ?? 0;
- bool ICollection<KeyValuePair<object, object?>>.Remove(KeyValuePair<object, object?> item)
- {
- if (_items == null)
- {
- return false;
- }
+ bool ICollection<KeyValuePair<object, object?>>.IsReadOnly => _items?.IsReadOnly ?? false;
- if (_items.TryGetValue(item.Key, out var value) && Equals(item.Value, value))
- {
- return _items.Remove(item.Key);
- }
+ bool ICollection<KeyValuePair<object, object?>>.Remove(KeyValuePair<object, object?> item)
+ {
+ if (_items == null)
+ {
return false;
}
- [MemberNotNull(nameof(_items))]
- private void EnsureDictionary()
+ if (_items.TryGetValue(item.Key, out var value) && Equals(item.Value, value))
{
- if (_items == null)
- {
- _items = new Dictionary<object, object?>();
- }
+ return _items.Remove(item.Key);
}
+ return false;
+ }
- IEnumerator<KeyValuePair<object, object?>> IEnumerable<KeyValuePair<object, object?>>.GetEnumerator()
- => _items?.GetEnumerator() ?? EmptyEnumerator.Instance;
+ [MemberNotNull(nameof(_items))]
+ private void EnsureDictionary()
+ {
+ if (_items == null)
+ {
+ _items = new Dictionary<object, object?>();
+ }
+ }
- IEnumerator IEnumerable.GetEnumerator() => _items?.GetEnumerator() ?? EmptyEnumerator.Instance;
+ IEnumerator<KeyValuePair<object, object?>> IEnumerable<KeyValuePair<object, object?>>.GetEnumerator()
+ => _items?.GetEnumerator() ?? EmptyEnumerator.Instance;
- private class EmptyEnumerator : IEnumerator<KeyValuePair<object, object?>>
- {
- // In own class so only initalized if GetEnumerator is called on an empty ItemsDictionary
- public static readonly IEnumerator<KeyValuePair<object, object?>> Instance = new EmptyEnumerator();
- public KeyValuePair<object, object?> Current => default;
+ IEnumerator IEnumerable.GetEnumerator() => _items?.GetEnumerator() ?? EmptyEnumerator.Instance;
- object? IEnumerator.Current => null;
+ private class EmptyEnumerator : IEnumerator<KeyValuePair<object, object?>>
+ {
+ // In own class so only initalized if GetEnumerator is called on an empty ItemsDictionary
+ public static readonly IEnumerator<KeyValuePair<object, object?>> Instance = new EmptyEnumerator();
+ public KeyValuePair<object, object?> Current => default;
- public void Dispose()
- { }
+ object? IEnumerator.Current => null;
- public bool MoveNext() => false;
+ public void Dispose()
+ { }
- public void Reset()
- { }
- }
+ public bool MoveNext() => false;
- private static class EmptyDictionary
- {
- // In own class so only initalized if CopyTo is called on an empty ItemsDictionary
- public static readonly IDictionary<object, object?> Dictionary = new Dictionary<object, object?>();
- public static ICollection<KeyValuePair<object, object?>> Collection => Dictionary;
- }
+ public void Reset()
+ { }
+ }
+
+ private static class EmptyDictionary
+ {
+ // In own class so only initalized if CopyTo is called on an empty ItemsDictionary
+ public static readonly IDictionary<object, object?> Dictionary = new Dictionary<object, object?>();
+ public static ICollection<KeyValuePair<object, object?>> Collection => Dictionary;
}
}
diff --git a/src/Http/Http/src/Internal/ReferenceReadStream.cs b/src/Http/Http/src/Internal/ReferenceReadStream.cs
index a68658c0d8..4766e3286a 100644
--- a/src/Http/Http/src/Internal/ReferenceReadStream.cs
+++ b/src/Http/Http/src/Internal/ReferenceReadStream.cs
@@ -6,156 +6,155 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
-{
- /// <summary>
- /// A Stream that wraps another stream starting at a certain offset and reading for the given length.
- /// </summary>
- internal sealed class ReferenceReadStream : Stream
- {
- private readonly Stream _inner;
- private readonly long _innerOffset;
- private readonly long _length;
- private long _position;
-
- private bool _disposed;
+namespace Microsoft.AspNetCore.Http;
- public ReferenceReadStream(Stream inner, long offset, long length)
- {
- if (inner == null)
- {
- throw new ArgumentNullException(nameof(inner));
- }
+/// <summary>
+/// A Stream that wraps another stream starting at a certain offset and reading for the given length.
+/// </summary>
+internal sealed class ReferenceReadStream : Stream
+{
+ private readonly Stream _inner;
+ private readonly long _innerOffset;
+ private readonly long _length;
+ private long _position;
- _inner = inner;
- _innerOffset = offset;
- _length = length;
- _inner.Position = offset;
- }
+ private bool _disposed;
- public override bool CanRead
+ public ReferenceReadStream(Stream inner, long offset, long length)
+ {
+ if (inner == null)
{
- get { return true; }
+ throw new ArgumentNullException(nameof(inner));
}
- public override bool CanSeek
- {
- get { return _inner.CanSeek; }
- }
+ _inner = inner;
+ _innerOffset = offset;
+ _length = length;
+ _inner.Position = offset;
+ }
- public override bool CanWrite
- {
- get { return false; }
- }
+ public override bool CanRead
+ {
+ get { return true; }
+ }
- public override long Length
- {
- get { return _length; }
- }
+ public override bool CanSeek
+ {
+ get { return _inner.CanSeek; }
+ }
- 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}");
- }
- VerifyPosition();
- _position = value;
- _inner.Position = _innerOffset + _position;
- }
- }
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
- // 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 Length
+ {
+ get { return _length; }
+ }
- public override long Seek(long offset, SeekOrigin origin)
+ public override long Position
+ {
+ get { return _position; }
+ set
{
- if (origin == SeekOrigin.Begin)
- {
- Position = offset;
- }
- else if (origin == SeekOrigin.End)
- {
- Position = Length + offset;
- }
- else // if (origin == SeekOrigin.Current)
+ ThrowIfDisposed();
+ if (value < 0 || value > Length)
{
- Position = Position + offset;
+ throw new ArgumentOutOfRangeException(nameof(value), value, $"The Position must be within the length of the Stream: {Length}");
}
- 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;
+ _position = value;
+ _inner.Position = _innerOffset + _position;
}
+ }
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- => ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
-
- public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
+ // 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)
{
- ThrowIfDisposed();
- VerifyPosition();
- var toRead = (int)Math.Min(buffer.Length, _length - _position);
- var read = await _inner.ReadAsync(buffer.Slice(0, toRead), cancellationToken);
- _position += read;
- return read;
+ throw new InvalidOperationException("The inner stream position has changed unexpectedly.");
}
+ }
- public override void Write(byte[] buffer, int offset, int count)
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
{
- throw new NotSupportedException();
+ Position = offset;
}
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ else if (origin == SeekOrigin.End)
{
- throw new NotSupportedException();
+ Position = Length + offset;
}
-
- public override void SetLength(long value)
+ else // if (origin == SeekOrigin.Current)
{
- throw new NotSupportedException();
+ Position = Position + offset;
}
+ return Position;
+ }
- public override void Flush()
- {
- }
+ 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 Task FlushAsync(CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ => ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+
+ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
+ VerifyPosition();
+ var toRead = (int)Math.Min(buffer.Length, _length - _position);
+ var read = await _inner.ReadAsync(buffer.Slice(0, 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()
+ {
+ }
- protected override void Dispose(bool disposing)
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
{
- if (disposing)
- {
- _disposed = true;
- }
+ _disposed = true;
}
+ }
- private void ThrowIfDisposed()
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(ReferenceReadStream));
- }
+ throw new ObjectDisposedException(nameof(ReferenceReadStream));
}
}
}
diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs
index 2901940073..c1d648a4ab 100644
--- a/src/Http/Http/src/Internal/RequestCookieCollection.cs
+++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs
@@ -5,229 +5,228 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
-using Microsoft.Extensions.Primitives;
+using System.Linq;
using Microsoft.AspNetCore.Internal;
+using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-using System.Linq;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+internal class RequestCookieCollection : IRequestCookieCollection
{
- internal class RequestCookieCollection : IRequestCookieCollection
- {
- public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
- private static readonly string[] EmptyKeys = Array.Empty<string>();
+ public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
+ private static readonly string[] EmptyKeys = Array.Empty<string>();
- // Pre-box
- private static readonly IEnumerator<KeyValuePair<string, string>> EmptyIEnumeratorType = default(Enumerator);
- private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
+ // Pre-box
+ private static readonly IEnumerator<KeyValuePair<string, string>> EmptyIEnumeratorType = default(Enumerator);
+ private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
- private AdaptiveCapacityDictionary<string, string> Store { get; set; }
+ private AdaptiveCapacityDictionary<string, string> Store { get; set; }
- public RequestCookieCollection()
- {
- Store = new AdaptiveCapacityDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- }
+ public RequestCookieCollection()
+ {
+ Store = new AdaptiveCapacityDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ }
- public RequestCookieCollection(int capacity)
- {
- Store = new AdaptiveCapacityDictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
- }
+ public RequestCookieCollection(int capacity)
+ {
+ Store = new AdaptiveCapacityDictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
+ }
- // For tests
- public RequestCookieCollection(Dictionary<string, string> store)
- {
- Store = new AdaptiveCapacityDictionary<string, string>(store);
- }
+ // For tests
+ public RequestCookieCollection(Dictionary<string, string> store)
+ {
+ Store = new AdaptiveCapacityDictionary<string, string>(store);
+ }
- public string? this[string key]
+ public string? this[string key]
+ {
+ get
{
- get
+ if (key == null)
{
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
-
- if (Store == null)
- {
- return null;
- }
+ throw new ArgumentNullException(nameof(key));
+ }
- if (TryGetValue(key, out var value))
- {
- return value;
- }
+ if (Store == null)
+ {
return null;
}
- }
- public static RequestCookieCollection Parse(StringValues values)
- => ParseInternal(values, AppContext.TryGetSwitch(ResponseCookies.EnableCookieNameEncoding, out var enabled) && enabled);
-
- internal static RequestCookieCollection ParseInternal(StringValues values, bool enableCookieNameEncoding)
- {
- if (values.Count == 0)
+ if (TryGetValue(key, out var value))
{
- return Empty;
+ return value;
}
+ return null;
+ }
+ }
- // Do not set the collection capacity based on StringValues.Count, the Cookie header is supposed to be a single combined value.
- var collection = new RequestCookieCollection();
- var store = collection.Store!;
+ public static RequestCookieCollection Parse(StringValues values)
+ => ParseInternal(values, AppContext.TryGetSwitch(ResponseCookies.EnableCookieNameEncoding, out var enabled) && enabled);
- if (CookieHeaderParserShared.TryParseValues(values, store, enableCookieNameEncoding, supportsMultipleValues: true))
- {
- if (store.Count == 0)
- {
- return Empty;
- }
-
- return collection;
- }
+ internal static RequestCookieCollection ParseInternal(StringValues values, bool enableCookieNameEncoding)
+ {
+ if (values.Count == 0)
+ {
return Empty;
}
- public int Count
+ // Do not set the collection capacity based on StringValues.Count, the Cookie header is supposed to be a single combined value.
+ var collection = new RequestCookieCollection();
+ var store = collection.Store!;
+
+ if (CookieHeaderParserShared.TryParseValues(values, store, enableCookieNameEncoding, supportsMultipleValues: true))
{
- get
+ if (store.Count == 0)
{
- if (Store == null)
- {
- return 0;
- }
- return Store.Count;
+ return Empty;
}
+
+ return collection;
}
+ return Empty;
+ }
- public ICollection<string> Keys
+ public int Count
+ {
+ get
{
- get
+ if (Store == null)
{
- if (Store == null)
- {
- return EmptyKeys;
- }
- return Store.Keys;
+ return 0;
}
+ return Store.Count;
}
+ }
- public bool ContainsKey(string key)
+ public ICollection<string> Keys
+ {
+ get
{
if (Store == null)
{
- return false;
+ return EmptyKeys;
}
- return Store.ContainsKey(key);
+ return Store.Keys;
}
+ }
- public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value)
+ public bool ContainsKey(string key)
+ {
+ if (Store == null)
{
- if (Store == null)
- {
- value = null;
- return false;
- }
+ return false;
+ }
+ return Store.ContainsKey(key);
+ }
- return Store.TryGetValue(key, out value);
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value)
+ {
+ if (Store == null)
+ {
+ value = null;
+ return false;
}
- /// <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()
+ 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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return default;
- }
// Non-boxed Enumerator
- return new Enumerator(Store.GetEnumerator());
+ return default;
}
+ // 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()
+ /// <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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return EmptyIEnumeratorType;
- }
- // Boxed Enumerator
- return GetEnumerator();
+ // 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()
+ /// <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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return EmptyIEnumerator;
- }
- // Boxed Enumerator
- return GetEnumerator();
+ // 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 AdaptiveCapacityDictionary<string, string>.Enumerator _dictionaryEnumerator;
+ private readonly bool _notEmpty;
- public struct Enumerator : IEnumerator<KeyValuePair<string, string>>
+ internal Enumerator(AdaptiveCapacityDictionary<string, string>.Enumerator dictionaryEnumerator)
{
- // Do NOT make this readonly, or MoveNext will not work
- private AdaptiveCapacityDictionary<string, string>.Enumerator _dictionaryEnumerator;
- private readonly bool _notEmpty;
+ _dictionaryEnumerator = dictionaryEnumerator;
+ _notEmpty = true;
+ }
- internal Enumerator(AdaptiveCapacityDictionary<string, string>.Enumerator dictionaryEnumerator)
+ public bool MoveNext()
+ {
+ if (_notEmpty)
{
- _dictionaryEnumerator = dictionaryEnumerator;
- _notEmpty = true;
+ return _dictionaryEnumerator.MoveNext();
}
+ return false;
+ }
- public bool MoveNext()
+ public KeyValuePair<string, string> Current
+ {
+ get
{
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, (string)current.Value!);
- }
- return default(KeyValuePair<string, string>);
+ var current = _dictionaryEnumerator.Current;
+ return new KeyValuePair<string, string>(current.Key, (string)current.Value!);
}
+ return default(KeyValuePair<string, string>);
}
+ }
- object IEnumerator.Current
+ object IEnumerator.Current
+ {
+ get
{
- get
- {
- return Current;
- }
+ return Current;
}
+ }
- public void Dispose()
- {
- }
+ public void Dispose()
+ {
+ }
- public void Reset()
+ public void Reset()
+ {
+ if (_notEmpty)
{
- if (_notEmpty)
- {
- ((IEnumerator)_dictionaryEnumerator).Reset();
- }
+ ((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
diff --git a/src/Http/Http/src/Internal/ResponseCookies.cs b/src/Http/Http/src/Internal/ResponseCookies.cs
index 2e326cb2e9..80ba3fae57 100644
--- a/src/Http/Http/src/Internal/ResponseCookies.cs
+++ b/src/Http/Http/src/Internal/ResponseCookies.cs
@@ -9,214 +9,213 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// A wrapper for the response Set-Cookie header.
+/// </summary>
+internal partial class ResponseCookies : IResponseCookies
{
+ internal const string EnableCookieNameEncoding = "Microsoft.AspNetCore.Http.EnableCookieNameEncoding";
+ internal bool _enableCookieNameEncoding = AppContext.TryGetSwitch(EnableCookieNameEncoding, out var enabled) && enabled;
+
+ private readonly IFeatureCollection _features;
+ private ILogger? _logger;
+
/// <summary>
- /// A wrapper for the response Set-Cookie header.
+ /// Create a new wrapper.
/// </summary>
- internal partial class ResponseCookies : IResponseCookies
+ internal ResponseCookies(IFeatureCollection features)
{
- internal const string EnableCookieNameEncoding = "Microsoft.AspNetCore.Http.EnableCookieNameEncoding";
- internal bool _enableCookieNameEncoding = AppContext.TryGetSwitch(EnableCookieNameEncoding, out var enabled) && enabled;
+ _features = features;
+ Headers = _features.Get<IHttpResponseFeature>()!.Headers;
+ }
- private readonly IFeatureCollection _features;
- private ILogger? _logger;
+ private IHeaderDictionary Headers { get; set; }
- /// <summary>
- /// Create a new wrapper.
- /// </summary>
- internal ResponseCookies(IFeatureCollection features)
+ /// <inheritdoc />
+ public void Append(string key, string value)
+ {
+ var setCookieHeaderValue = new SetCookieHeaderValue(
+ _enableCookieNameEncoding ? Uri.EscapeDataString(key) : key,
+ Uri.EscapeDataString(value))
{
- _features = features;
- Headers = _features.Get<IHttpResponseFeature>()!.Headers;
- }
+ Path = "/"
+ };
+ var cookieValue = setCookieHeaderValue.ToString();
- private IHeaderDictionary Headers { get; set; }
+ Headers.SetCookie = StringValues.Concat(Headers.SetCookie, cookieValue);
+ }
- /// <inheritdoc />
- public void Append(string key, string value)
+ /// <inheritdoc />
+ public void Append(string key, string value, CookieOptions options)
+ {
+ if (options == null)
{
- var setCookieHeaderValue = new SetCookieHeaderValue(
- _enableCookieNameEncoding ? Uri.EscapeDataString(key) : key,
- Uri.EscapeDataString(value))
- {
- Path = "/"
- };
- var cookieValue = setCookieHeaderValue.ToString();
-
- Headers.SetCookie = StringValues.Concat(Headers.SetCookie, cookieValue);
+ throw new ArgumentNullException(nameof(options));
}
- /// <inheritdoc />
- public void Append(string key, string value, CookieOptions options)
+ // SameSite=None cookies must be marked as Secure.
+ if (!options.Secure && options.SameSite == SameSiteMode.None)
{
- if (options == null)
+ if (_logger == null)
{
- throw new ArgumentNullException(nameof(options));
+ var services = _features.Get<Features.IServiceProvidersFeature>()?.RequestServices;
+ _logger = services?.GetService<ILogger<ResponseCookies>>();
}
- // SameSite=None cookies must be marked as Secure.
- if (!options.Secure && options.SameSite == SameSiteMode.None)
+ if (_logger != null)
{
- if (_logger == null)
- {
- var services = _features.Get<Features.IServiceProvidersFeature>()?.RequestServices;
- _logger = services?.GetService<ILogger<ResponseCookies>>();
- }
-
- if (_logger != null)
- {
- Log.SameSiteCookieNotSecure(_logger, key);
- }
+ Log.SameSiteCookieNotSecure(_logger, key);
}
+ }
- var setCookieHeaderValue = new SetCookieHeaderValue(
- _enableCookieNameEncoding ? Uri.EscapeDataString(key) : 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.SetCookie = StringValues.Concat(Headers.SetCookie, cookieValue);
+ var setCookieHeaderValue = new SetCookieHeaderValue(
+ _enableCookieNameEncoding ? Uri.EscapeDataString(key) : 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.SetCookie = StringValues.Concat(Headers.SetCookie, cookieValue);
+ }
+
+ /// <inheritdoc />
+ public void Append(ReadOnlySpan<KeyValuePair<string, string>> keyValuePairs, CookieOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
}
- /// <inheritdoc />
- public void Append(ReadOnlySpan<KeyValuePair<string, string>> keyValuePairs, CookieOptions options)
+ // SameSite=None cookies must be marked as Secure.
+ if (!options.Secure && options.SameSite == SameSiteMode.None)
{
- if (options == null)
+ if (_logger == null)
{
- throw new ArgumentNullException(nameof(options));
+ var services = _features.Get<IServiceProvidersFeature>()?.RequestServices;
+ _logger = services?.GetService<ILogger<ResponseCookies>>();
}
- // SameSite=None cookies must be marked as Secure.
- if (!options.Secure && options.SameSite == SameSiteMode.None)
+ if (_logger != null)
{
- if (_logger == null)
- {
- var services = _features.Get<IServiceProvidersFeature>()?.RequestServices;
- _logger = services?.GetService<ILogger<ResponseCookies>>();
- }
-
- if (_logger != null)
+ foreach (var keyValuePair in keyValuePairs)
{
- foreach (var keyValuePair in keyValuePairs)
- {
- Log.SameSiteCookieNotSecure(_logger, keyValuePair.Key);
- }
+ Log.SameSiteCookieNotSecure(_logger, keyValuePair.Key);
}
}
-
- var setCookieHeaderValue = new SetCookieHeaderValue(string.Empty)
- {
- 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 cookierHeaderValue = setCookieHeaderValue.ToString()[1..];
- var cookies = new string[keyValuePairs.Length];
- var position = 0;
-
- foreach (var keyValuePair in keyValuePairs)
- {
- var key = _enableCookieNameEncoding ? Uri.EscapeDataString(keyValuePair.Key) : keyValuePair.Key;
- cookies[position] = string.Concat(key, "=", Uri.EscapeDataString(keyValuePair.Value), cookierHeaderValue);
- position++;
- }
-
- // Can't use += as StringValues does not override operator+
- // and the implict conversions will cause an incorrect string concat https://github.com/dotnet/runtime/issues/52507
- Headers.SetCookie = StringValues.Concat(Headers.SetCookie, cookies);
}
- /// <inheritdoc />
- public void Delete(string key)
+ var setCookieHeaderValue = new SetCookieHeaderValue(string.Empty)
+ {
+ 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 cookierHeaderValue = setCookieHeaderValue.ToString()[1..];
+ var cookies = new string[keyValuePairs.Length];
+ var position = 0;
+
+ foreach (var keyValuePair in keyValuePairs)
{
- Delete(key, new CookieOptions() { Path = "/" });
+ var key = _enableCookieNameEncoding ? Uri.EscapeDataString(keyValuePair.Key) : keyValuePair.Key;
+ cookies[position] = string.Concat(key, "=", Uri.EscapeDataString(keyValuePair.Value), cookierHeaderValue);
+ position++;
}
- /// <inheritdoc />
- public void Delete(string key, CookieOptions options)
+ // Can't use += as StringValues does not override operator+
+ // and the implict conversions will cause an incorrect string concat https://github.com/dotnet/runtime/issues/52507
+ Headers.SetCookie = StringValues.Concat(Headers.SetCookie, cookies);
+ }
+
+ /// <inheritdoc />
+ public void Delete(string key)
+ {
+ Delete(key, new CookieOptions() { Path = "/" });
+ }
+
+ /// <inheritdoc />
+ public void Delete(string key, CookieOptions options)
+ {
+ if (options == null)
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ throw new ArgumentNullException(nameof(options));
+ }
- var encodedKeyPlusEquals = (_enableCookieNameEncoding ? Uri.EscapeDataString(key) : key) + "=";
- var domainHasValue = !string.IsNullOrEmpty(options.Domain);
- var pathHasValue = !string.IsNullOrEmpty(options.Path);
+ var encodedKeyPlusEquals = (_enableCookieNameEncoding ? Uri.EscapeDataString(key) : key) + "=";
+ var domainHasValue = !string.IsNullOrEmpty(options.Domain);
+ var pathHasValue = !string.IsNullOrEmpty(options.Path);
- Func<string, string, CookieOptions, bool> rejectPredicate;
- if (domainHasValue && pathHasValue)
- {
- rejectPredicate = (value, encKeyPlusEquals, opts) =>
- value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
- value.IndexOf($"domain={opts.Domain}", StringComparison.OrdinalIgnoreCase) != -1 &&
- value.IndexOf($"path={opts.Path}", StringComparison.OrdinalIgnoreCase) != -1;
- }
- else 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);
- }
+ Func<string, string, CookieOptions, bool> rejectPredicate;
+ if (domainHasValue && pathHasValue)
+ {
+ rejectPredicate = (value, encKeyPlusEquals, opts) =>
+ value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
+ value.IndexOf($"domain={opts.Domain}", StringComparison.OrdinalIgnoreCase) != -1 &&
+ value.IndexOf($"path={opts.Path}", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+ else 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.SetCookie;
- if (!StringValues.IsNullOrEmpty(existingValues))
- {
- var values = existingValues.ToArray();
- var newValues = new List<string>();
+ var existingValues = Headers.SetCookie;
+ if (!StringValues.IsNullOrEmpty(existingValues))
+ {
+ var values = existingValues.ToArray();
+ var newValues = new List<string>();
- for (var i = 0; i < values.Length; i++)
+ for (var i = 0; i < values.Length; i++)
+ {
+ var value = values[i] ?? string.Empty;
+ if (!rejectPredicate(value, encodedKeyPlusEquals, options))
{
- var value = values[i] ?? string.Empty;
- if (!rejectPredicate(value, encodedKeyPlusEquals, options))
- {
- newValues.Add(value);
- }
+ newValues.Add(value);
}
-
- Headers.SetCookie = new StringValues(newValues.ToArray());
}
- Append(key, string.Empty, new CookieOptions
- {
- Path = options.Path,
- Domain = options.Domain,
- Expires = DateTimeOffset.UnixEpoch,
- Secure = options.Secure,
- HttpOnly = options.HttpOnly,
- SameSite = options.SameSite
- });
+ Headers.SetCookie = new StringValues(newValues.ToArray());
}
- private static partial class Log
+ Append(key, string.Empty, new CookieOptions
{
- [LoggerMessage(1, LogLevel.Warning, "The cookie '{name}' has set 'SameSite=None' and must also set 'Secure'.", EventName = "SameSiteNotSecure")]
- public static partial void SameSiteCookieNotSecure(ILogger logger, string name);
- }
+ Path = options.Path,
+ Domain = options.Domain,
+ Expires = DateTimeOffset.UnixEpoch,
+ Secure = options.Secure,
+ HttpOnly = options.HttpOnly,
+ SameSite = options.SameSite
+ });
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Warning, "The cookie '{name}' has set 'SameSite=None' and must also set 'Secure'.", EventName = "SameSiteNotSecure")]
+ public static partial void SameSiteCookieNotSecure(ILogger logger, string name);
}
}
diff --git a/src/Http/Http/src/MiddlewareFactory.cs b/src/Http/Http/src/MiddlewareFactory.cs
index c1b28f4feb..83a7ddd8a1 100644
--- a/src/Http/Http/src/MiddlewareFactory.cs
+++ b/src/Http/Http/src/MiddlewareFactory.cs
@@ -4,37 +4,36 @@
using System;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Default implementation for <see cref="IMiddlewareFactory"/>.
+/// </summary>
+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;
+
/// <summary>
- /// Default implementation for <see cref="IMiddlewareFactory"/>.
+ /// Initializes a new instance of <see cref="MiddlewareFactory"/>.
/// </summary>
- public class MiddlewareFactory : IMiddlewareFactory
+ /// <param name="serviceProvider">The application services.</param>
+ public MiddlewareFactory(IServiceProvider serviceProvider)
{
- // 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;
-
- /// <summary>
- /// Initializes a new instance of <see cref="MiddlewareFactory"/>.
- /// </summary>
- /// <param name="serviceProvider">The application services.</param>
- public MiddlewareFactory(IServiceProvider serviceProvider)
- {
- _serviceProvider = serviceProvider;
- }
+ _serviceProvider = serviceProvider;
+ }
- /// <inheritdoc/>
- public IMiddleware? Create(Type middlewareType)
- {
- return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware;
- }
+ /// <inheritdoc/>
+ public IMiddleware? Create(Type middlewareType)
+ {
+ return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware;
+ }
- /// <inheritdoc/>
- public void Release(IMiddleware middleware)
- {
- // The container owns the lifetime of the service
- }
+ /// <inheritdoc/>
+ public void Release(IMiddleware middleware)
+ {
+ // The container owns the lifetime of the service
}
}
diff --git a/src/Http/Http/src/QueryCollection.cs b/src/Http/Http/src/QueryCollection.cs
index 41328aa3ee..a8941c7163 100644
--- a/src/Http/Http/src/QueryCollection.cs
+++ b/src/Http/Http/src/QueryCollection.cs
@@ -6,246 +6,245 @@ using System.Collections;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// The HttpRequest query string collection
+/// </summary>
+public class QueryCollection : IQueryCollection
{
/// <summary>
- /// The HttpRequest query string collection
+ /// Gets an empty <see cref="QueryCollection"/>.
/// </summary>
- public class QueryCollection : IQueryCollection
- {
- /// <summary>
- /// Gets an empty <see cref="QueryCollection"/>.
- /// </summary>
- public static readonly QueryCollection Empty = new QueryCollection();
- private static readonly string[] EmptyKeys = Array.Empty<string>();
- // Pre-box
- private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = default(Enumerator);
- private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
+ public static readonly QueryCollection Empty = new QueryCollection();
+ private static readonly string[] EmptyKeys = Array.Empty<string>();
+ // Pre-box
+ private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = default(Enumerator);
+ private static readonly IEnumerator EmptyIEnumerator = default(Enumerator);
- private Dictionary<string, StringValues>? Store { get; }
+ private Dictionary<string, StringValues>? Store { get; }
- /// <summary>
- /// Initializes a new instance of <see cref="QueryCollection"/>.
- /// </summary>
- public QueryCollection()
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="QueryCollection"/>.
+ /// </summary>
+ public QueryCollection()
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="QueryCollection"/>.
- /// </summary>
- /// <param name="store">The backing store.</param>
- public QueryCollection(Dictionary<string, StringValues> store)
- {
- Store = store;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="QueryCollection"/>.
+ /// </summary>
+ /// <param name="store">The backing store.</param>
+ public QueryCollection(Dictionary<string, StringValues> store)
+ {
+ Store = store;
+ }
- /// <summary>
- /// Creates a shallow copy of the specified <paramref name="store"/>.
- /// </summary>
- /// <param name="store">The <see cref="QueryCollection"/> to clone.</param>
- public QueryCollection(QueryCollection store)
- {
- Store = store.Store;
- }
+ /// <summary>
+ /// Creates a shallow copy of the specified <paramref name="store"/>.
+ /// </summary>
+ /// <param name="store">The <see cref="QueryCollection"/> to clone.</param>
+ public QueryCollection(QueryCollection store)
+ {
+ Store = store.Store;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="QueryCollection"/>.
- /// </summary>
- /// <param name="capacity">The initial number of query items that this instance can contain.</param>
- public QueryCollection(int capacity)
- {
- Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="QueryCollection"/>.
+ /// </summary>
+ /// <param name="capacity">The initial number of query items that this instance can contain.</param>
+ public QueryCollection(int capacity)
+ {
+ Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
+ }
- /// <summary>
- /// Gets the associated set of values from the collection.
- /// </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]
+ /// <summary>
+ /// Gets the associated set of values from the collection.
+ /// </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
{
- get
+ if (Store == null)
{
- if (Store == null)
- {
- return StringValues.Empty;
- }
-
- if (TryGetValue(key, out var 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 (TryGetValue(key, out var value))
{
- if (Store == null)
- {
- return 0;
- }
- return Store.Count;
+ return value;
}
+ return StringValues.Empty;
}
+ }
- /// <summary>
- /// Gets the collection of query names in this instance.
- /// </summary>
- public ICollection<string> Keys
+ /// <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
{
- get
+ if (Store == null)
{
- if (Store == null)
- {
- return EmptyKeys;
- }
- return Store.Keys;
+ return 0;
}
+ return Store.Count;
}
+ }
- /// <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)
+ /// <summary>
+ /// Gets the collection of query names in this instance.
+ /// </summary>
+ public ICollection<string> Keys
+ {
+ get
{
if (Store == null)
{
- return false;
+ return EmptyKeys;
}
- return Store.ContainsKey(key);
+ return Store.Keys;
}
+ }
- /// <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)
+ /// <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)
{
- if (Store == null)
- {
- value = default(StringValues);
- return false;
- }
- return Store.TryGetValue(key, out value);
+ return false;
}
+ return Store.ContainsKey(key);
+ }
- /// <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()
+ /// <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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return default;
- }
- return new Enumerator(Store.GetEnumerator());
+ 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 default;
}
+ 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()
+ /// <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)
{
- if (Store == null || Store.Count == 0)
- {
- // Non-boxed Enumerator
- return EmptyIEnumeratorType;
- }
- return Store.GetEnumerator();
+ // 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();
+ }
+
+ /// <summary>
+ /// Enumerates a <see cref="QueryCollection"/>.
+ /// </summary>
+ public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ {
+ // Do NOT make this readonly, or MoveNext will not work
+ private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+ private readonly bool _notEmpty;
+
+ internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+ {
+ _dictionaryEnumerator = dictionaryEnumerator;
+ _notEmpty = true;
}
/// <summary>
- /// Returns an enumerator that iterates through a collection.
+ /// Advances the enumerator to the next element of the <see cref="HeaderDictionary"/>.
/// </summary>
- /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
- IEnumerator IEnumerable.GetEnumerator()
+ /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
+ /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
+ public bool MoveNext()
{
- if (Store == null || Store.Count == 0)
+ if (_notEmpty)
{
- // Non-boxed Enumerator
- return EmptyIEnumerator;
+ return _dictionaryEnumerator.MoveNext();
}
- return Store.GetEnumerator();
+ return false;
}
/// <summary>
- /// Enumerates a <see cref="QueryCollection"/>.
+ /// Gets the element at the current position of the enumerator.
/// </summary>
- public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ public KeyValuePair<string, StringValues> Current
{
- // Do NOT make this readonly, or MoveNext will not work
- private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
- private readonly bool _notEmpty;
-
- internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
- {
- _dictionaryEnumerator = dictionaryEnumerator;
- _notEmpty = true;
- }
-
- /// <summary>
- /// Advances the enumerator to the next element of the <see cref="HeaderDictionary"/>.
- /// </summary>
- /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
- /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
- public bool MoveNext()
+ get
{
if (_notEmpty)
{
- return _dictionaryEnumerator.MoveNext();
- }
- return false;
- }
-
- /// <summary>
- /// Gets the element at the current position of the enumerator.
- /// </summary>
- public KeyValuePair<string, StringValues> Current
- {
- get
- {
- if (_notEmpty)
- {
- return _dictionaryEnumerator.Current;
- }
- return default(KeyValuePair<string, StringValues>);
+ return _dictionaryEnumerator.Current;
}
+ return default(KeyValuePair<string, StringValues>);
}
+ }
- /// <inheritdoc />
- public void Dispose()
- {
- }
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ }
- object IEnumerator.Current
+ object IEnumerator.Current
+ {
+ get
{
- get
- {
- return Current;
- }
+ return Current;
}
+ }
- void IEnumerator.Reset()
+ void IEnumerator.Reset()
+ {
+ if (_notEmpty)
{
- if (_notEmpty)
- {
- ((IEnumerator)_dictionaryEnumerator).Reset();
- }
+ ((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
diff --git a/src/Http/Http/src/QueryCollectionInternal.cs b/src/Http/Http/src/QueryCollectionInternal.cs
index fa553fad8b..3e49233165 100644
--- a/src/Http/Http/src/QueryCollectionInternal.cs
+++ b/src/Http/Http/src/QueryCollectionInternal.cs
@@ -6,123 +6,122 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// The HttpRequest query string collection
+/// </summary>
+internal class QueryCollectionInternal : IQueryCollection
{
+ private AdaptiveCapacityDictionary<string, StringValues> Store { get; }
+
/// <summary>
- /// The HttpRequest query string collection
+ /// Initializes a new instance of <see cref="QueryCollection"/>.
/// </summary>
- internal class QueryCollectionInternal : IQueryCollection
+ /// <param name="store">The backing store.</param>
+ internal QueryCollectionInternal(AdaptiveCapacityDictionary<string, StringValues> store)
{
- private AdaptiveCapacityDictionary<string, StringValues> Store { get; }
+ Store = store;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="QueryCollection"/>.
- /// </summary>
- /// <param name="store">The backing store.</param>
- internal QueryCollectionInternal(AdaptiveCapacityDictionary<string, StringValues> store)
- {
- Store = store;
- }
+ /// <summary>
+ /// Gets the associated set of values from the collection.
+ /// </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] => TryGetValue(key, out var value) ? value : StringValues.Empty;
- /// <summary>
- /// Gets the associated set of values from the collection.
- /// </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] => TryGetValue(key, out var value) ? value : 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 => Store.Count;
- /// <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 => Store.Count;
+ /// <summary>
+ /// Gets the collection of query names in this instance.
+ /// </summary>
+ public ICollection<string> Keys => Store.Keys;
- /// <summary>
- /// Gets the collection of query names in this instance.
- /// </summary>
- public ICollection<string> Keys => 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) => Store.ContainsKey(key);
- /// <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) => 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) => Store.TryGetValue(key, out value);
- /// <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) => 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() => new Enumerator(Store.GetEnumerator());
- /// <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() => 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()
+ => 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()
- => 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() => 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() => Store.GetEnumerator();
+ /// <summary>
+ /// Enumerates a <see cref="QueryCollection"/>.
+ /// </summary>
+ public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ {
+ // Do NOT make this readonly, or MoveNext will not work
+ private AdaptiveCapacityDictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+ private readonly bool _notEmpty;
+
+ internal Enumerator(AdaptiveCapacityDictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+ {
+ _dictionaryEnumerator = dictionaryEnumerator;
+ _notEmpty = true;
+ }
/// <summary>
- /// Enumerates a <see cref="QueryCollection"/>.
+ /// Advances the enumerator to the next element of the <see cref="HeaderDictionary"/>.
/// </summary>
- public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+ /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
+ /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
+ public bool MoveNext()
{
- // Do NOT make this readonly, or MoveNext will not work
- private AdaptiveCapacityDictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
- private readonly bool _notEmpty;
-
- internal Enumerator(AdaptiveCapacityDictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+ if (_notEmpty)
{
- _dictionaryEnumerator = dictionaryEnumerator;
- _notEmpty = true;
- }
-
- /// <summary>
- /// Advances the enumerator to the next element of the <see cref="HeaderDictionary"/>.
- /// </summary>
- /// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element;
- /// <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
- public bool MoveNext()
- {
- if (_notEmpty)
- {
- return _dictionaryEnumerator.MoveNext();
- }
- return false;
+ return _dictionaryEnumerator.MoveNext();
}
+ return false;
+ }
- /// <summary>
- /// Gets the element at the current position of the enumerator.
- /// </summary>
- public KeyValuePair<string, StringValues> Current => _notEmpty ? _dictionaryEnumerator.Current : default;
+ /// <summary>
+ /// Gets the element at the current position of the enumerator.
+ /// </summary>
+ public KeyValuePair<string, StringValues> Current => _notEmpty ? _dictionaryEnumerator.Current : default;
- /// <inheritdoc />
- public void Dispose()
- {
- }
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ }
- object IEnumerator.Current => Current;
+ object IEnumerator.Current => Current;
- void IEnumerator.Reset()
+ void IEnumerator.Reset()
+ {
+ if (_notEmpty)
{
- if (_notEmpty)
- {
- ((IEnumerator)_dictionaryEnumerator).Reset();
- }
+ ((IEnumerator)_dictionaryEnumerator).Reset();
}
}
}
diff --git a/src/Http/Http/src/RequestFormReaderExtensions.cs b/src/Http/Http/src/RequestFormReaderExtensions.cs
index e1cd094650..c3819858e1 100644
--- a/src/Http/Http/src/RequestFormReaderExtensions.cs
+++ b/src/Http/Http/src/RequestFormReaderExtensions.cs
@@ -6,46 +6,45 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension for <see cref="HttpRequest"/>.
+/// </summary>
+public static class RequestFormReaderExtensions
{
/// <summary>
- /// Extension for <see cref="HttpRequest"/>.
+ /// 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>
- public static class RequestFormReaderExtensions
+ /// <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())
{
- /// <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)
{
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ throw new ArgumentNullException(nameof(options));
+ }
- if (!request.HasFormContentType)
- {
- throw new InvalidOperationException("Incorrect Content-Type: " + request.ContentType);
- }
+ 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);
+ 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/SendFileFallback.cs b/src/Http/Http/src/SendFileFallback.cs
index 67c2910149..22ae53c101 100644
--- a/src/Http/Http/src/SendFileFallback.cs
+++ b/src/Http/Http/src/SendFileFallback.cs
@@ -6,55 +6,54 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Helper type that allows copying a file to a Stream.
+/// <para>
+/// This type is part of ASP.NET Core's infrastructure and should not used by application code.
+/// </para>
+/// </summary>
+public static class SendFileFallback
{
/// <summary>
- /// Helper type that allows copying a file to a Stream.
- /// <para>
- /// This type is part of ASP.NET Core's infrastructure and should not used by application code.
- /// </para>
+ /// Copies the segment of the file to the destination stream.
/// </summary>
- public static class SendFileFallback
+ /// <param name="destination">The stream to write the file segment to.</param>
+ /// <param name="filePath">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="cancellationToken">A <see cref="CancellationToken"/> used to abort the transmission.</param>
+ /// <returns></returns>
+ public static async Task SendFileAsync(Stream destination, string filePath, long offset, long? count, CancellationToken cancellationToken)
{
- /// <summary>
- /// Copies the segment of the file to the destination stream.
- /// </summary>
- /// <param name="destination">The stream to write the file segment to.</param>
- /// <param name="filePath">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="cancellationToken">A <see cref="CancellationToken"/> used to abort the transmission.</param>
- /// <returns></returns>
- public static async Task SendFileAsync(Stream destination, string filePath, long offset, long? count, CancellationToken cancellationToken)
+ var fileInfo = new FileInfo(filePath);
+ if (offset < 0 || offset > fileInfo.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
+ }
+ if (count.HasValue &&
+ (count.Value < 0 || count.Value > fileInfo.Length - offset))
{
- var fileInfo = new FileInfo(filePath);
- if (offset < 0 || offset > fileInfo.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
- }
- if (count.HasValue &&
- (count.Value < 0 || count.Value > fileInfo.Length - offset))
- {
- throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
- }
+ throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
+ }
- cancellationToken.ThrowIfCancellationRequested();
+ cancellationToken.ThrowIfCancellationRequested();
- const int bufferSize = 1024 * 16;
+ const int bufferSize = 1024 * 16;
- var fileStream = new FileStream(
- filePath,
- FileMode.Open,
- FileAccess.Read,
- FileShare.ReadWrite,
- bufferSize: bufferSize,
- options: FileOptions.Asynchronous | FileOptions.SequentialScan);
+ var fileStream = new FileStream(
+ filePath,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ bufferSize: bufferSize,
+ options: FileOptions.Asynchronous | FileOptions.SequentialScan);
- using (fileStream)
- {
- fileStream.Seek(offset, SeekOrigin.Begin);
- await StreamCopyOperationInternal.CopyToAsync(fileStream, destination, count, bufferSize, cancellationToken);
- }
+ using (fileStream)
+ {
+ fileStream.Seek(offset, SeekOrigin.Begin);
+ await StreamCopyOperationInternal.CopyToAsync(fileStream, destination, count, bufferSize, cancellationToken);
}
}
}
diff --git a/src/Http/Http/src/StreamResponseBodyFeature.cs b/src/Http/Http/src/StreamResponseBodyFeature.cs
index 74c42cebab..fd162d953a 100644
--- a/src/Http/Http/src/StreamResponseBodyFeature.cs
+++ b/src/Http/Http/src/StreamResponseBodyFeature.cs
@@ -8,146 +8,145 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// An implementation of <see cref="IHttpResponseBodyFeature"/> that aproximates all of the APIs over the given Stream.
+/// </summary>
+public class StreamResponseBodyFeature : IHttpResponseBodyFeature
{
+ private PipeWriter? _pipeWriter;
+ private bool _started;
+ private bool _completed;
+ private bool _disposed;
+
/// <summary>
- /// An implementation of <see cref="IHttpResponseBodyFeature"/> that aproximates all of the APIs over the given Stream.
+ /// Wraps the given stream.
/// </summary>
- public class StreamResponseBodyFeature : IHttpResponseBodyFeature
+ /// <param name="stream"></param>
+ public StreamResponseBodyFeature(Stream stream)
{
- private PipeWriter? _pipeWriter;
- private bool _started;
- private bool _completed;
- private bool _disposed;
-
- /// <summary>
- /// Wraps the given stream.
- /// </summary>
- /// <param name="stream"></param>
- public StreamResponseBodyFeature(Stream stream)
- {
- Stream = stream ?? throw new ArgumentNullException(nameof(stream));
- }
+ Stream = stream ?? throw new ArgumentNullException(nameof(stream));
+ }
- /// <summary>
- /// Wraps the given stream and tracks the prior feature instance.
- /// </summary>
- /// <param name="stream"></param>
- /// <param name="priorFeature"></param>
- public StreamResponseBodyFeature(Stream stream, IHttpResponseBodyFeature priorFeature)
- {
- Stream = stream ?? throw new ArgumentNullException(nameof(stream));
- PriorFeature = priorFeature;
- }
+ /// <summary>
+ /// Wraps the given stream and tracks the prior feature instance.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <param name="priorFeature"></param>
+ public StreamResponseBodyFeature(Stream stream, IHttpResponseBodyFeature priorFeature)
+ {
+ Stream = stream ?? throw new ArgumentNullException(nameof(stream));
+ PriorFeature = priorFeature;
+ }
- /// <summary>
- /// The original response body stream.
- /// </summary>
- public Stream Stream { get; }
+ /// <summary>
+ /// The original response body stream.
+ /// </summary>
+ public Stream Stream { get; }
- /// <summary>
- /// The prior feature, if any.
- /// </summary>
- public IHttpResponseBodyFeature? PriorFeature { get; }
+ /// <summary>
+ /// The prior feature, if any.
+ /// </summary>
+ public IHttpResponseBodyFeature? PriorFeature { get; }
- /// <summary>
- /// A PipeWriter adapted over the given stream.
- /// </summary>
- public PipeWriter Writer
+ /// <summary>
+ /// A PipeWriter adapted over the given stream.
+ /// </summary>
+ public PipeWriter Writer
+ {
+ get
{
- get
+ if (_pipeWriter == null)
{
- if (_pipeWriter == null)
+ _pipeWriter = PipeWriter.Create(Stream, new StreamPipeWriterOptions(leaveOpen: true));
+ if (_completed)
{
- _pipeWriter = PipeWriter.Create(Stream, new StreamPipeWriterOptions(leaveOpen: true));
- if (_completed)
- {
- _pipeWriter.Complete();
- }
+ _pipeWriter.Complete();
}
-
- return _pipeWriter;
}
+
+ return _pipeWriter;
}
+ }
- /// <summary>
- /// Opts out of write buffering for the response.
- /// </summary>
- public virtual void DisableBuffering()
+ /// <summary>
+ /// Opts out of write buffering for the response.
+ /// </summary>
+ public virtual void DisableBuffering()
+ {
+ PriorFeature?.DisableBuffering();
+ }
+
+ /// <summary>
+ /// Copies the specified file segment to the given response stream.
+ /// This calls StartAsync if it has not previously been called.
+ /// </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="cancellationToken">A <see cref="CancellationToken"/> used to abort the transmission.</param>
+ /// <returns></returns>
+ public virtual async Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken)
+ {
+ if (!_started)
{
- PriorFeature?.DisableBuffering();
+ await StartAsync(cancellationToken);
}
+ await SendFileFallback.SendFileAsync(Stream, path, offset, count, cancellationToken);
+ }
- /// <summary>
- /// Copies the specified file segment to the given response stream.
- /// This calls StartAsync if it has not previously been called.
- /// </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="cancellationToken">A <see cref="CancellationToken"/> used to abort the transmission.</param>
- /// <returns></returns>
- public virtual async Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellationToken)
+ /// <summary>
+ /// Flushes the given stream if this has not previously been called.
+ /// </summary>
+ /// <param name="cancellationToken"></param>
+ /// <returns></returns>
+ public virtual Task StartAsync(CancellationToken cancellationToken = default)
+ {
+ if (!_started)
{
- if (!_started)
- {
- await StartAsync(cancellationToken);
- }
- await SendFileFallback.SendFileAsync(Stream, path, offset, count, cancellationToken);
+ _started = true;
+ return Stream.FlushAsync(cancellationToken);
}
+ return Task.CompletedTask;
+ }
- /// <summary>
- /// Flushes the given stream if this has not previously been called.
- /// </summary>
- /// <param name="cancellationToken"></param>
- /// <returns></returns>
- public virtual Task StartAsync(CancellationToken cancellationToken = default)
+ /// <summary>
+ /// This calls StartAsync if it has not previously been called.
+ /// It will complete the adapted pipe if it exists.
+ /// </summary>
+ /// <returns></returns>
+ public virtual async Task CompleteAsync()
+ {
+ // CompleteAsync is registered with HttpResponse.OnCompleted and there's no way to unregister it.
+ // Prevent it from running by marking as disposed.
+ if (_disposed)
{
- if (!_started)
- {
- _started = true;
- return Stream.FlushAsync(cancellationToken);
- }
- return Task.CompletedTask;
+ return;
}
-
- /// <summary>
- /// This calls StartAsync if it has not previously been called.
- /// It will complete the adapted pipe if it exists.
- /// </summary>
- /// <returns></returns>
- public virtual async Task CompleteAsync()
+ if (_completed)
{
- // CompleteAsync is registered with HttpResponse.OnCompleted and there's no way to unregister it.
- // Prevent it from running by marking as disposed.
- if (_disposed)
- {
- return;
- }
- if (_completed)
- {
- return;
- }
-
- if (!_started)
- {
- await StartAsync();
- }
-
- _completed = true;
+ return;
+ }
- if (_pipeWriter != null)
- {
- await _pipeWriter.CompleteAsync();
- }
+ if (!_started)
+ {
+ await StartAsync();
}
- /// <summary>
- /// Prevents CompleteAsync from operating.
- /// </summary>
- public void Dispose()
+ _completed = true;
+
+ if (_pipeWriter != null)
{
- _disposed = true;
+ await _pipeWriter.CompleteAsync();
}
}
+
+ /// <summary>
+ /// Prevents CompleteAsync from operating.
+ /// </summary>
+ public void Dispose()
+ {
+ _disposed = true;
+ }
}
diff --git a/src/Http/Http/test/ApplicationBuilderTests.cs b/src/Http/Http/test/ApplicationBuilderTests.cs
index bde96e5b5b..0a6d08d178 100644
--- a/src/Http/Http/test/ApplicationBuilderTests.cs
+++ b/src/Http/Http/test/ApplicationBuilderTests.cs
@@ -6,90 +6,89 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Builder.Internal
+namespace Microsoft.AspNetCore.Builder.Internal;
+
+public class ApplicationBuilderTests
{
- public class ApplicationBuilderTests
+ [Fact]
+ public void BuildReturnsCallableDelegate()
{
- [Fact]
- public void BuildReturnsCallableDelegate()
- {
- var builder = new ApplicationBuilder(null);
- var app = builder.Build();
+ var builder = new ApplicationBuilder(null);
+ var app = builder.Build();
- var httpContext = new DefaultHttpContext();
+ var httpContext = new DefaultHttpContext();
- app.Invoke(httpContext);
- Assert.Equal(404, httpContext.Response.StatusCode);
- }
+ app.Invoke(httpContext);
+ Assert.Equal(404, httpContext.Response.StatusCode);
+ }
- [Fact]
- public async Task BuildImplicitlyThrowsForMatchedEndpointAsLastStep()
- {
- var builder = new ApplicationBuilder(null);
- var app = builder.Build();
-
- var endpointCalled = false;
- var endpoint = new Endpoint(
- context =>
- {
- endpointCalled = true;
- return Task.CompletedTask;
- },
- EndpointMetadataCollection.Empty,
- "Test endpoint");
-
- var httpContext = new DefaultHttpContext();
- httpContext.SetEndpoint(endpoint);
-
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => app.Invoke(httpContext));
-
- var expected =
- "The request reached the end of the pipeline without executing the endpoint: 'Test endpoint'. " +
- "Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if " +
- "using routing.";
- Assert.Equal(expected, ex.Message);
- Assert.False(endpointCalled);
- }
-
- [Fact]
- public void BuildDoesNotCallMatchedEndpointWhenTerminated()
- {
- var builder = new ApplicationBuilder(null);
- builder.Run(context =>
+ [Fact]
+ public async Task BuildImplicitlyThrowsForMatchedEndpointAsLastStep()
+ {
+ var builder = new ApplicationBuilder(null);
+ var app = builder.Build();
+
+ var endpointCalled = false;
+ var endpoint = new Endpoint(
+ context =>
{
+ endpointCalled = true;
+ return Task.CompletedTask;
+ },
+ EndpointMetadataCollection.Empty,
+ "Test endpoint");
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.SetEndpoint(endpoint);
+
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => app.Invoke(httpContext));
+
+ var expected =
+ "The request reached the end of the pipeline without executing the endpoint: 'Test endpoint'. " +
+ "Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if " +
+ "using routing.";
+ Assert.Equal(expected, ex.Message);
+ Assert.False(endpointCalled);
+ }
+
+ [Fact]
+ public void BuildDoesNotCallMatchedEndpointWhenTerminated()
+ {
+ var builder = new ApplicationBuilder(null);
+ builder.Run(context =>
+ {
// Do not call next
return Task.CompletedTask;
- });
- var app = builder.Build();
+ });
+ var app = builder.Build();
- var endpointCalled = false;
- var endpoint = new Endpoint(
- context =>
- {
- endpointCalled = true;
- return Task.CompletedTask;
- },
- EndpointMetadataCollection.Empty,
- "Test endpoint");
+ var endpointCalled = false;
+ var endpoint = new Endpoint(
+ context =>
+ {
+ endpointCalled = true;
+ return Task.CompletedTask;
+ },
+ EndpointMetadataCollection.Empty,
+ "Test endpoint");
- var httpContext = new DefaultHttpContext();
- httpContext.SetEndpoint(endpoint);
+ var httpContext = new DefaultHttpContext();
+ httpContext.SetEndpoint(endpoint);
- app.Invoke(httpContext);
+ app.Invoke(httpContext);
- Assert.False(endpointCalled);
- }
+ Assert.False(endpointCalled);
+ }
- [Fact]
- public void PropertiesDictionaryIsDistinctAfterNew()
- {
- var builder1 = new ApplicationBuilder(null);
- builder1.Properties["test"] = "value1";
+ [Fact]
+ public void PropertiesDictionaryIsDistinctAfterNew()
+ {
+ var builder1 = new ApplicationBuilder(null);
+ builder1.Properties["test"] = "value1";
- var builder2 = builder1.New();
- builder2.Properties["test"] = "value2";
+ var builder2 = builder1.New();
+ builder2.Properties["test"] = "value2";
- Assert.Equal("value1", builder1.Properties["test"]);
- }
+ Assert.Equal("value1", builder1.Properties["test"]);
}
}
diff --git a/src/Http/Http/test/BindingAddressTests.cs b/src/Http/Http/test/BindingAddressTests.cs
index edf5b1e044..37ad03919f 100644
--- a/src/Http/Http/test/BindingAddressTests.cs
+++ b/src/Http/Http/test/BindingAddressTests.cs
@@ -5,109 +5,108 @@ using System;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Tests
+namespace Microsoft.AspNetCore.Http.Tests;
+
+public class BindingAddressTests
{
- public class BindingAddressTests
+ [Theory]
+ [InlineData("")]
+ [InlineData("5000")]
+ [InlineData("//noscheme")]
+ public void FromUriThrowsForUrlsWithoutSchemeDelimiter(string url)
{
- [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));
- }
+ Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
+ }
- [ConditionalTheory]
- [InlineData("http://unix:/")]
- [InlineData("http://unix:/c")]
- [InlineData("http://unix:/wrong.path")]
- [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
- public void FromUriThrowsForUrlsWithWrongFilePathOnWindows(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")]
- public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
- {
- var serverAddress = BindingAddress.Parse(url);
+ [ConditionalTheory]
+ [InlineData("http://unix:/")]
+ [InlineData("http://unix:/c")]
+ [InlineData("http://unix:/wrong.path")]
+ [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
+ public void FromUriThrowsForUrlsWithWrongFilePathOnWindows(string url)
+ {
+ Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
+ }
- Assert.Equal(scheme, serverAddress.Scheme);
- Assert.Equal(host, serverAddress.Host);
- Assert.Equal(port, serverAddress.Port);
- Assert.Equal(pathBase, serverAddress.PathBase);
+ [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")]
+ public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
+ {
+ var serverAddress = BindingAddress.Parse(url);
- Assert.Equal(toString ?? url, serverAddress.ToString());
- }
+ Assert.Equal(scheme, serverAddress.Scheme);
+ Assert.Equal(host, serverAddress.Host);
+ Assert.Equal(port, serverAddress.Port);
+ Assert.Equal(pathBase, serverAddress.PathBase);
- [ConditionalTheory]
- [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")]
- [OSSkipCondition(OperatingSystems.Windows)]
- public void UnixSocketUrlsAreParsedCorrectlyOnUnix(string url, string scheme, string host, int port, string pathBase, string toString)
- {
- var serverAddress = BindingAddress.Parse(url);
+ Assert.Equal(toString ?? url, serverAddress.ToString());
+ }
- Assert.Equal(scheme, serverAddress.Scheme);
- Assert.Equal(host, serverAddress.Host);
- Assert.Equal(port, serverAddress.Port);
- Assert.Equal(pathBase, serverAddress.PathBase);
+ [ConditionalTheory]
+ [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")]
+ [OSSkipCondition(OperatingSystems.Windows)]
+ public void UnixSocketUrlsAreParsedCorrectlyOnUnix(string url, string scheme, string host, int port, string pathBase, string toString)
+ {
+ var serverAddress = BindingAddress.Parse(url);
- Assert.Equal(toString ?? url, serverAddress.ToString());
- }
+ Assert.Equal(scheme, serverAddress.Scheme);
+ Assert.Equal(host, serverAddress.Host);
+ Assert.Equal(port, serverAddress.Port);
+ Assert.Equal(pathBase, serverAddress.PathBase);
- [ConditionalTheory]
- [InlineData("http://unix:/c:/foo/bar/pipe.socket", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", null)]
- [InlineData("http://unix:/c:/foo/bar/pipe.socket:", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", "http://unix:/c:/foo/bar/pipe.socket")]
- [InlineData("http://unix:/c:/foo/bar/pipe.socket:/", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", "http://unix:/c:/foo/bar/pipe.socket")]
- [InlineData("http://unix:/c:/foo/bar/pipe.socket:5000/doesn't/matter", "http", "unix:/c:/foo/bar/pipe.socket", 0, "5000/doesn't/matter", "http://unix:/c:/foo/bar/pipe.socket")]
- [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
- public void UnixSocketUrlsAreParsedCorrectlyOnWindows(string url, string scheme, string host, int port, string pathBase, string toString)
- {
- var serverAddress = BindingAddress.Parse(url);
+ Assert.Equal(toString ?? url, serverAddress.ToString());
+ }
- Assert.Equal(scheme, serverAddress.Scheme);
- Assert.Equal(host, serverAddress.Host);
- Assert.Equal(port, serverAddress.Port);
- Assert.Equal(pathBase, serverAddress.PathBase);
+ [ConditionalTheory]
+ [InlineData("http://unix:/c:/foo/bar/pipe.socket", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", null)]
+ [InlineData("http://unix:/c:/foo/bar/pipe.socket:", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", "http://unix:/c:/foo/bar/pipe.socket")]
+ [InlineData("http://unix:/c:/foo/bar/pipe.socket:/", "http", "unix:/c:/foo/bar/pipe.socket", 0, "", "http://unix:/c:/foo/bar/pipe.socket")]
+ [InlineData("http://unix:/c:/foo/bar/pipe.socket:5000/doesn't/matter", "http", "unix:/c:/foo/bar/pipe.socket", 0, "5000/doesn't/matter", "http://unix:/c:/foo/bar/pipe.socket")]
+ [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Windows has drive letters and volume separator (c:), testing this url on unix or osx provides completely different output.")]
+ public void UnixSocketUrlsAreParsedCorrectlyOnWindows(string url, string scheme, string host, int port, string pathBase, string toString)
+ {
+ var serverAddress = BindingAddress.Parse(url);
- Assert.Equal(toString ?? url, serverAddress.ToString());
- }
+ 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/DefaultHttpContextTests.cs b/src/Http/Http/test/DefaultHttpContextTests.cs
index ce59fa8043..6a3561eaad 100644
--- a/src/Http/Http/test/DefaultHttpContextTests.cs
+++ b/src/Http/Http/test/DefaultHttpContextTests.cs
@@ -15,518 +15,517 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class DefaultHttpContextTests
{
- public class DefaultHttpContextTests
+ [Fact]
+ public void GetOnSessionProperty_ThrowsOnMissingSessionFeature()
{
- [Fact]
- public void GetOnSessionProperty_ThrowsOnMissingSessionFeature()
- {
- // Arrange
- var context = new DefaultHttpContext();
+ // 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);
- }
+ // 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 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();
+ [Fact]
+ public void AllowsSettingSession_WithoutSettingUpSessionFeature_Upfront()
+ {
+ // Arrange
+ var session = new TestSession();
+ var context = new DefaultHttpContext();
- // Act
- context.Session = session;
+ // Act
+ context.Session = session;
- // Assert
- Assert.Same(session, context.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 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 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_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 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 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<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
- features.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
-
- // FeatureCollection is set. all cached interfaces are null.
- var context = new DefaultHttpContext(features);
- TestAllCachedFeaturesAreNull(context, features);
- Assert.Equal(4, features.Count());
-
- // getting feature properties populates feature collection with defaults
- TestAllCachedFeaturesAreSet(context, features);
- Assert.NotEqual(4, 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<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
- newFeatures.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
-
- // FeatureCollection is set to newFeatures. all cached interfaces are null.
- context.Initialize(newFeatures);
- TestAllCachedFeaturesAreNull(context, newFeatures);
- Assert.Equal(4, newFeatures.Count());
-
- // getting feature properties populates new feature collection with defaults
- TestAllCachedFeaturesAreSet(context, newFeatures);
- Assert.NotEqual(4, newFeatures.Count());
- }
+ [Fact]
+ public void UpdateFeatures_ClearsCachedFeatures()
+ {
+ var features = new FeatureCollection();
+ features.Set<IHttpRequestFeature>(new HttpRequestFeature());
+ features.Set<IHttpResponseFeature>(new HttpResponseFeature());
+ features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
+ features.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
+
+ // FeatureCollection is set. all cached interfaces are null.
+ var context = new DefaultHttpContext(features);
+ TestAllCachedFeaturesAreNull(context, features);
+ Assert.Equal(4, features.Count());
+
+ // getting feature properties populates feature collection with defaults
+ TestAllCachedFeaturesAreSet(context, features);
+ Assert.NotEqual(4, 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<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
+ newFeatures.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
+
+ // FeatureCollection is set to newFeatures. all cached interfaces are null.
+ context.Initialize(newFeatures);
+ TestAllCachedFeaturesAreNull(context, newFeatures);
+ Assert.Equal(4, newFeatures.Count());
+
+ // getting feature properties populates new feature collection with defaults
+ TestAllCachedFeaturesAreSet(context, newFeatures);
+ Assert.NotEqual(4, newFeatures.Count());
+ }
- [Fact]
- public void RequestServicesAreNotOverwrittenIfAlreadySet()
- {
- var serviceProvider = new ServiceCollection()
- .BuildServiceProvider();
+ [Fact]
+ public void RequestServicesAreNotOverwrittenIfAlreadySet()
+ {
+ var serviceProvider = new ServiceCollection()
+ .BuildServiceProvider();
- var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+ var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
- var context = new DefaultHttpContext();
- context.ServiceScopeFactory = scopeFactory;
- context.RequestServices = serviceProvider;
+ var context = new DefaultHttpContext();
+ context.ServiceScopeFactory = scopeFactory;
+ context.RequestServices = serviceProvider;
- Assert.Same(serviceProvider, context.RequestServices);
- }
+ Assert.Same(serviceProvider, context.RequestServices);
+ }
- [Fact]
- public async Task RequestServicesAreDisposedOnCompleted()
- {
- var serviceProvider = new ServiceCollection()
- .AddTransient<DisposableThing>()
- .BuildServiceProvider();
+ [Fact]
+ public async Task RequestServicesAreDisposedOnCompleted()
+ {
+ var serviceProvider = new ServiceCollection()
+ .AddTransient<DisposableThing>()
+ .BuildServiceProvider();
- var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
- DisposableThing instance = null;
+ var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+ DisposableThing instance = null;
- var context = new DefaultHttpContext();
- context.ServiceScopeFactory = scopeFactory;
- var responseFeature = new TestHttpResponseFeature();
- context.Features.Set<IHttpResponseFeature>(responseFeature);
+ var context = new DefaultHttpContext();
+ context.ServiceScopeFactory = scopeFactory;
+ var responseFeature = new TestHttpResponseFeature();
+ context.Features.Set<IHttpResponseFeature>(responseFeature);
- Assert.NotNull(context.RequestServices);
- Assert.Single(responseFeature.CompletedCallbacks);
+ Assert.NotNull(context.RequestServices);
+ Assert.Single(responseFeature.CompletedCallbacks);
- instance = context.RequestServices.GetRequiredService<DisposableThing>();
+ instance = context.RequestServices.GetRequiredService<DisposableThing>();
- var callback = responseFeature.CompletedCallbacks[0];
- await callback.callback(callback.state);
+ var callback = responseFeature.CompletedCallbacks[0];
+ await callback.callback(callback.state);
- Assert.Null(context.RequestServices);
- Assert.True(instance.Disposed);
- }
+ Assert.Null(context.RequestServices);
+ Assert.True(instance.Disposed);
+ }
- [Fact]
- public async Task RequestServicesAreDisposedAsynOnCompleted()
- {
- var serviceProvider = new AsyncDisposableServiceProvider(new ServiceCollection()
- .AddTransient<DisposableThing>()
- .BuildServiceProvider());
+ [Fact]
+ public async Task RequestServicesAreDisposedAsynOnCompleted()
+ {
+ var serviceProvider = new AsyncDisposableServiceProvider(new ServiceCollection()
+ .AddTransient<DisposableThing>()
+ .BuildServiceProvider());
- var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
- DisposableThing instance = null;
+ var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+ DisposableThing instance = null;
- var context = new DefaultHttpContext();
- context.ServiceScopeFactory = scopeFactory;
- var responseFeature = new TestHttpResponseFeature();
- context.Features.Set<IHttpResponseFeature>(responseFeature);
+ var context = new DefaultHttpContext();
+ context.ServiceScopeFactory = scopeFactory;
+ var responseFeature = new TestHttpResponseFeature();
+ context.Features.Set<IHttpResponseFeature>(responseFeature);
- Assert.NotNull(context.RequestServices);
- Assert.Single(responseFeature.CompletedCallbacks);
+ Assert.NotNull(context.RequestServices);
+ Assert.Single(responseFeature.CompletedCallbacks);
- instance = context.RequestServices.GetRequiredService<DisposableThing>();
+ instance = context.RequestServices.GetRequiredService<DisposableThing>();
- var callback = responseFeature.CompletedCallbacks[0];
- await callback.callback(callback.state);
+ var callback = responseFeature.CompletedCallbacks[0];
+ await callback.callback(callback.state);
- Assert.Null(context.RequestServices);
- Assert.True(instance.Disposed);
- var scope = Assert.Single(serviceProvider.Scopes);
- Assert.True(scope.DisposeAsyncCalled);
- Assert.False(scope.DisposeCalled);
- }
+ Assert.Null(context.RequestServices);
+ Assert.True(instance.Disposed);
+ var scope = Assert.Single(serviceProvider.Scopes);
+ Assert.True(scope.DisposeAsyncCalled);
+ Assert.False(scope.DisposeCalled);
+ }
- [Fact]
- public void InternalActiveFlagIsSetAndUnset()
- {
- var context = new DefaultHttpContext();
+ [Fact]
+ public void InternalActiveFlagIsSetAndUnset()
+ {
+ var context = new DefaultHttpContext();
- Assert.False(context._active);
+ Assert.False(context._active);
- context.Initialize(new FeatureCollection());
+ context.Initialize(new FeatureCollection());
- Assert.True(context._active);
+ Assert.True(context._active);
- context.Uninitialize();
+ context.Uninitialize();
- Assert.False(context._active);
- }
+ Assert.False(context._active);
+ }
- void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features)
- {
- TestCachedFeaturesAreNull(context, features);
- TestCachedFeaturesAreNull(context.Request, features);
- TestCachedFeaturesAreNull(context.Response, features);
- TestCachedFeaturesAreNull(context.Connection, features);
- TestCachedFeaturesAreNull(context.WebSockets, features);
- }
+ void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features)
+ {
+ TestCachedFeaturesAreNull(context, features);
+ TestCachedFeaturesAreNull(context.Request, features);
+ TestCachedFeaturesAreNull(context.Response, features);
+ TestCachedFeaturesAreNull(context.Connection, features);
+ TestCachedFeaturesAreNull(context.WebSockets, features);
+ }
- void TestCachedFeaturesAreNull(object value, IFeatureCollection features)
- {
- var type = value.GetType();
+ 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 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 boxedExpectedStruct = features == null ?
+ Activator.CreateInstance(field.FieldType) :
+ Activator.CreateInstance(field.FieldType, features);
- var boxedActualStruct = field.GetValue(value);
+ var boxedActualStruct = field.GetValue(value);
- Assert.Equal(boxedExpectedStruct, boxedActualStruct);
- }
+ Assert.Equal(boxedExpectedStruct, boxedActualStruct);
+ }
- void TestAllCachedFeaturesAreSet(HttpContext context, IFeatureCollection features)
- {
- TestCachedFeaturesAreSet(context, features);
- TestCachedFeaturesAreSet(context.Request, features);
- TestCachedFeaturesAreSet(context.Response, features);
- TestCachedFeaturesAreSet(context.Connection, features);
- TestCachedFeaturesAreSet(context.WebSockets, features);
- }
+ void TestAllCachedFeaturesAreSet(HttpContext context, IFeatureCollection features)
+ {
+ TestCachedFeaturesAreSet(context, features);
+ TestCachedFeaturesAreSet(context.Request, features);
+ TestCachedFeaturesAreSet(context.Response, features);
+ TestCachedFeaturesAreSet(context.Connection, features);
+ TestCachedFeaturesAreSet(context.WebSockets, features);
+ }
- void TestCachedFeaturesAreSet(object value, IFeatureCollection features)
- {
- var type = value.GetType();
+ 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);
+ var properties = type
+ .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
+ .Where(p => p.PropertyType.GetTypeInfo().IsInterface);
- TestFeatureProperties(value, features, properties);
+ TestFeatureProperties(value, features, properties);
- var fields = type
- .GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
- .Where(f => f.FieldType.GetTypeInfo().IsInterface && f.GetCustomAttribute<CompilerGeneratedAttribute>() == null);
+ var fields = type
+ .GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+ .Where(f => f.FieldType.GetTypeInfo().IsInterface && f.GetCustomAttribute<CompilerGeneratedAttribute>() == null);
- foreach (var field in fields)
+ foreach (var field in fields)
+ {
+ if (field.FieldType == typeof(IFeatureCollection))
{
- 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);
- }
+ 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)
+ }
+
+ private static void TestFeatureProperties(object value, IFeatureCollection features, IEnumerable<PropertyInfo> properties)
+ {
+ foreach (var property in properties)
{
- foreach (var property in properties)
+ if (property.PropertyType == typeof(IFeatureCollection))
{
- if (property.PropertyType == typeof(IFeatureCollection))
- {
- Assert.Same(features, property.GetValue(value));
- }
- else
+ Assert.Same(features, property.GetValue(value));
+ }
+ else
+ {
+ if (property.Name.Contains("Feature"))
{
- if (property.Name.Contains("Feature"))
- {
- var v = property.GetValue(value);
- Assert.Same(features[property.PropertyType], v);
- Assert.NotNull(v);
- }
+ var v = property.GetValue(value);
+ Assert.Same(features[property.PropertyType], v);
+ Assert.NotNull(v);
}
}
}
+ }
+
+ private HttpContext CreateContext()
+ {
+ var context = new DefaultHttpContext();
+ return context;
+ }
- private HttpContext CreateContext()
+ private class DisposableThing : IDisposable
+ {
+ public bool Disposed { get; set; }
+ public void Dispose()
{
- var context = new DefaultHttpContext();
- return context;
+ Disposed = true;
}
+ }
- private class DisposableThing : IDisposable
+ 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)
{
- public bool Disposed { get; set; }
- public void Dispose()
- {
- Disposed = true;
- }
+ CompletedCallbacks.Add((callback, state));
}
- private class TestHttpResponseFeature : IHttpResponseFeature
+ public void OnStarting(Func<object, Task> callback, object state)
{
- public List<(Func<object, Task> callback, object state)> CompletedCallbacks = new List<(Func<object, Task> callback, object state)>();
+ }
+ }
+
+ private class TestSession : ISession
+ {
+ private readonly Dictionary<string, byte[]> _store
+ = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
- public int StatusCode { get; set; }
- public string ReasonPhrase { get; set; }
- public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
- public Stream Body { get; set; }
+ public string Id { get; set; }
- public bool HasStarted => false;
+ public bool IsAvailable { get; } = true;
- public void OnCompleted(Func<object, Task> callback, object state)
- {
- CompletedCallbacks.Add((callback, state));
- }
+ public IEnumerable<string> Keys { get { return _store.Keys; } }
- public void OnStarting(Func<object, Task> callback, object state)
- {
- }
+ public void Clear()
+ {
+ _store.Clear();
}
- private class TestSession : ISession
+ public Task CommitAsync(CancellationToken cancellationToken)
{
- private readonly Dictionary<string, byte[]> _store
- = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
+ return Task.FromResult(0);
+ }
- public string Id { get; set; }
+ public Task LoadAsync(CancellationToken cancellationToken)
+ {
+ return Task.FromResult(0);
+ }
- public bool IsAvailable { get; } = true;
+ public void Remove(string key)
+ {
+ _store.Remove(key);
+ }
- public IEnumerable<string> Keys { get { return _store.Keys; } }
+ public void Set(string key, byte[] value)
+ {
+ _store[key] = value;
+ }
- public void Clear()
- {
- _store.Clear();
- }
+ public bool TryGetValue(string key, out byte[] value)
+ {
+ return _store.TryGetValue(key, out value);
+ }
+ }
- public Task CommitAsync(CancellationToken cancellationToken)
- {
- return Task.FromResult(0);
- }
+ private class BlahSessionFeature : ISessionFeature
+ {
+ public ISession Session { get; set; }
+ }
- public Task LoadAsync(CancellationToken cancellationToken)
+ private class TestHttpWebSocketFeature : IHttpWebSocketFeature
+ {
+ public bool IsWebSocketRequest
+ {
+ get
{
- return Task.FromResult(0);
+ throw new NotImplementedException();
}
+ }
- public void Remove(string key)
- {
- _store.Remove(key);
- }
+ public Task<WebSocket> AcceptAsync(WebSocketAcceptContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
- public void Set(string key, byte[] value)
- {
- _store[key] = value;
- }
+ private class AsyncDisposableServiceProvider : IServiceProvider, IDisposable, IServiceScopeFactory
+ {
+ private readonly ServiceProvider _serviceProvider;
+
+ public AsyncDisposableServiceProvider(ServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+ }
- public bool TryGetValue(string key, out byte[] value)
+ public List<AsyncServiceScope> Scopes { get; } = new List<AsyncServiceScope>();
+
+ public object GetService(Type serviceType)
+ {
+ if (serviceType == typeof(IServiceScopeFactory))
{
- return _store.TryGetValue(key, out value);
+ return this;
}
+
+ return _serviceProvider.GetService(serviceType);
}
- private class BlahSessionFeature : ISessionFeature
+ public void Dispose()
{
- public ISession Session { get; set; }
+ _serviceProvider.Dispose();
}
- private class TestHttpWebSocketFeature : IHttpWebSocketFeature
+ public IServiceScope CreateScope()
{
- public bool IsWebSocketRequest
- {
- get
- {
- throw new NotImplementedException();
- }
- }
-
- public Task<WebSocket> AcceptAsync(WebSocketAcceptContext context)
- {
- throw new NotImplementedException();
- }
+ var scope = new AsyncServiceScope(_serviceProvider.GetService<IServiceScopeFactory>().CreateScope());
+ Scopes.Add(scope);
+ return scope;
}
- private class AsyncDisposableServiceProvider : IServiceProvider, IDisposable, IServiceScopeFactory
+ internal class AsyncServiceScope : IServiceScope, IAsyncDisposable
{
- private readonly ServiceProvider _serviceProvider;
+ private readonly IServiceScope _scope;
- public AsyncDisposableServiceProvider(ServiceProvider serviceProvider)
+ public AsyncServiceScope(IServiceScope scope)
{
- _serviceProvider = serviceProvider;
+ _scope = scope;
}
- public List<AsyncServiceScope> Scopes { get; } = new List<AsyncServiceScope>();
-
- public object GetService(Type serviceType)
- {
- if (serviceType == typeof(IServiceScopeFactory))
- {
- return this;
- }
+ public bool DisposeCalled { get; set; }
- return _serviceProvider.GetService(serviceType);
- }
+ public bool DisposeAsyncCalled { get; set; }
public void Dispose()
{
- _serviceProvider.Dispose();
+ DisposeCalled = true;
+ _scope.Dispose();
}
- public IServiceScope CreateScope()
+ public ValueTask DisposeAsync()
{
- var scope = new AsyncServiceScope(_serviceProvider.GetService<IServiceScopeFactory>().CreateScope());
- Scopes.Add(scope);
- return scope;
+ DisposeAsyncCalled = true;
+ _scope.Dispose();
+ return default;
}
- internal class AsyncServiceScope : IServiceScope, IAsyncDisposable
- {
- private readonly IServiceScope _scope;
-
- public AsyncServiceScope(IServiceScope scope)
- {
- _scope = scope;
- }
-
- public bool DisposeCalled { get; set; }
-
- public bool DisposeAsyncCalled { get; set; }
-
- public void Dispose()
- {
- DisposeCalled = true;
- _scope.Dispose();
- }
-
- public ValueTask DisposeAsync()
- {
- DisposeAsyncCalled = true;
- _scope.Dispose();
- return default;
- }
-
- public IServiceProvider ServiceProvider => _scope.ServiceProvider;
- }
+ public IServiceProvider ServiceProvider => _scope.ServiceProvider;
}
}
}
diff --git a/src/Http/Http/test/Features/FakeResponseFeature.cs b/src/Http/Http/test/Features/FakeResponseFeature.cs
index 854d745ffc..be4239ce4d 100644
--- a/src/Http/Http/test/Features/FakeResponseFeature.cs
+++ b/src/Http/Http/test/Features/FakeResponseFeature.cs
@@ -5,25 +5,24 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class FakeResponseFeature : HttpResponseFeature
{
- public class FakeResponseFeature : HttpResponseFeature
- {
- List<Tuple<Func<object, Task>, object>> _onCompletedCallbacks = new List<Tuple<Func<object, Task>, object>>();
+ 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 override void OnCompleted(Func<object, Task> callback, object state)
+ {
+ _onCompletedCallbacks.Add(new Tuple<Func<object, Task>, object>(callback, state));
+ }
- public async Task CompleteAsync()
+ public async Task CompleteAsync()
+ {
+ var callbacks = _onCompletedCallbacks;
+ _onCompletedCallbacks = null;
+ foreach (var callback in callbacks)
{
- var callbacks = _onCompletedCallbacks;
- _onCompletedCallbacks = null;
- foreach (var callback in callbacks)
- {
- await callback.Item1(callback.Item2);
- }
+ await callback.Item1(callback.Item2);
}
}
}
diff --git a/src/Http/Http/test/Features/FormFeatureTests.cs b/src/Http/Http/test/Features/FormFeatureTests.cs
index 9426ce6dd1..b9a3498485 100644
--- a/src/Http/Http/test/Features/FormFeatureTests.cs
+++ b/src/Http/Http/test/Features/FormFeatureTests.cs
@@ -9,621 +9,620 @@ using System.Text;
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class FormFeatureTests
{
- public class FormFeatureTests
+ [Fact]
+ public async Task ReadFormAsync_0ContentLength_ReturnsEmptyForm()
{
- [Fact]
- public async Task ReadFormAsync_0ContentLength_ReturnsEmptyForm()
- {
- var context = new DefaultHttpContext();
- var responseFeature = new FakeResponseFeature();
- context.Features.Set<IHttpResponseFeature>(responseFeature);
- context.Request.ContentType = MultipartContentType;
- context.Request.ContentLength = 0;
+ var context = new DefaultHttpContext();
+ var responseFeature = new FakeResponseFeature();
+ context.Features.Set<IHttpResponseFeature>(responseFeature);
+ context.Request.ContentType = MultipartContentType;
+ context.Request.ContentLength = 0;
- var formFeature = new FormFeature(context.Request, new FormOptions());
- context.Features.Set<IFormFeature>(formFeature);
+ var formFeature = new FormFeature(context.Request, new FormOptions());
+ context.Features.Set<IFormFeature>(formFeature);
- var formCollection = await context.Request.ReadFormAsync();
+ var formCollection = await context.Request.ReadFormAsync();
- Assert.Same(FormCollection.Empty, formCollection);
- }
+ Assert.Same(FormCollection.Empty, formCollection);
+ }
- [Fact]
- public async Task FormFeatureReadsOptionsFromDefaultHttpContext()
+ [Fact]
+ public async Task FormFeatureReadsOptionsFromDefaultHttpContext()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
+ context.FormOptions = new FormOptions
{
- var context = new DefaultHttpContext();
- context.Request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
- context.FormOptions = new FormOptions
- {
- ValueCountLimit = 1
- };
+ ValueCountLimit = 1
+ };
- var formContent = Encoding.UTF8.GetBytes("foo=bar&baz=2");
- context.Request.Body = new NonSeekableReadStream(formContent);
+ var formContent = Encoding.UTF8.GetBytes("foo=bar&baz=2");
+ context.Request.Body = new NonSeekableReadStream(formContent);
- var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
- Assert.Equal("Form value count limit 1 exceeded.", exception.Message);
- }
+ Assert.Equal("Form value count limit 1 exceeded.", exception.Message);
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_SimpleData_ReturnsParsedFormCollection(bool bufferRequest)
+ [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)
{
- 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);
- }
+ 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);
+ // Cached
+ formFeature = context.Features.Get<IFormFeature>();
+ Assert.NotNull(formFeature);
+ Assert.NotNull(formFeature.Form);
+ Assert.Same(formFeature.Form, formCollection);
- // Cleanup
- await responseFeature.CompleteAsync();
- }
+ // Cleanup
+ await responseFeature.CompleteAsync();
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_SimpleData_ReplacePipeReader_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";
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadFormAsync_SimpleData_ReplacePipeReader_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";
- var pipe = new Pipe();
- await pipe.Writer.WriteAsync(formContent);
- pipe.Writer.Complete();
+ var pipe = new Pipe();
+ await pipe.Writer.WriteAsync(formContent);
+ pipe.Writer.Complete();
- var mockFeature = new MockRequestBodyPipeFeature();
- mockFeature.Reader = pipe.Reader;
- context.Features.Set<IRequestBodyPipeFeature>(mockFeature);
+ var mockFeature = new MockRequestBodyPipeFeature();
+ mockFeature.Reader = pipe.Reader;
+ context.Features.Set<IRequestBodyPipeFeature>(mockFeature);
- IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
- context.Features.Set<IFormFeature>(formFeature);
+ IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+ context.Features.Set<IFormFeature>(formFeature);
- var formCollection = await context.Request.ReadFormAsync();
+ var formCollection = await context.Request.ReadFormAsync();
- Assert.Equal("bar", formCollection["foo"]);
- Assert.Equal("2", formCollection["baz"]);
+ Assert.Equal("bar", formCollection["foo"]);
+ Assert.Equal("2", formCollection["baz"]);
- // Cached
- formFeature = context.Features.Get<IFormFeature>();
- Assert.NotNull(formFeature);
- Assert.NotNull(formFeature.Form);
- Assert.Same(formFeature.Form, formCollection);
+ // Cached
+ formFeature = context.Features.Get<IFormFeature>();
+ Assert.NotNull(formFeature);
+ Assert.NotNull(formFeature.Form);
+ Assert.Same(formFeature.Form, formCollection);
- // Cleanup
- await responseFeature.CompleteAsync();
- }
+ // Cleanup
+ await responseFeature.CompleteAsync();
+ }
- private class MockRequestBodyPipeFeature : IRequestBodyPipeFeature
- {
- public PipeReader Reader { get; set; }
- }
+ private class MockRequestBodyPipeFeature : IRequestBodyPipeFeature
+ {
+ public PipeReader Reader { get; set; }
+ }
- private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
+ private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
- private const string MultipartContentTypeWithSpecialCharacters = "multipart/form-data; boundary=\"WebKitFormBoundary/:5pDRpGheQXaM8k3T\"";
+ private const string MultipartContentTypeWithSpecialCharacters = "multipart/form-data; boundary=\"WebKitFormBoundary/:5pDRpGheQXaM8k3T\"";
- private const string EmptyMultipartForm = "--WebKitFormBoundary5pDRpGheQXaM8k3T--";
+ 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";
+ // 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 MultipartFormEndWithSpecialCharacters = "--WebKitFormBoundary/:5pDRpGheQXaM8k3T--\r\n";
- private const string MultipartFormField = "--WebKitFormBoundary5pDRpGheQXaM8k3T\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" +
+ 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" +
+ 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" +
+ private const string MultipartFormFileSpecialCharacters = "--WebKitFormBoundary/:5pDRpGheQXaM8k3T\r\n" +
"Content-Disposition: form-data; name=\"description\"\r\n" +
"\r\n" +
"Foo\r\n";
- private const string InvalidContentDispositionValue = "form-data; name=\"description\" - filename=\"temp.html\"";
+ private const string InvalidContentDispositionValue = "form-data; name=\"description\" - filename=\"temp.html\"";
- private const string MultipartFormFileInvalidContentDispositionValue = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
+ private const string MultipartFormFileInvalidContentDispositionValue = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
"Content-Disposition: " +
InvalidContentDispositionValue +
"\r\n" +
"\r\n" +
"Foo\r\n";
- private const string MultipartFormWithField =
- MultipartFormField +
- MultipartFormEnd;
+ private const string MultipartFormWithField =
+ MultipartFormField +
+ MultipartFormEnd;
- private const string MultipartFormWithFile =
- MultipartFormFile +
- MultipartFormEnd;
+ private const string MultipartFormWithFile =
+ MultipartFormFile +
+ MultipartFormEnd;
- private const string MultipartFormWithFieldAndFile =
- MultipartFormField +
- MultipartFormFile +
- MultipartFormEnd;
+ private const string MultipartFormWithFieldAndFile =
+ MultipartFormField +
+ MultipartFormFile +
+ MultipartFormEnd;
- private const string MultipartFormWithEncodedFilename =
- MultipartFormEncodedFilename +
- MultipartFormEnd;
+ private const string MultipartFormWithEncodedFilename =
+ MultipartFormEncodedFilename +
+ MultipartFormEnd;
- private const string MultipartFormWithSpecialCharacters =
- MultipartFormFileSpecialCharacters +
- MultipartFormEndWithSpecialCharacters;
+ private const string MultipartFormWithSpecialCharacters =
+ MultipartFormFileSpecialCharacters +
+ MultipartFormEndWithSpecialCharacters;
- private const string MultipartFormWithInvalidContentDispositionValue =
- MultipartFormFileInvalidContentDispositionValue +
- MultipartFormEnd;
+ private const string MultipartFormWithInvalidContentDispositionValue =
+ MultipartFormFileInvalidContentDispositionValue +
+ MultipartFormEnd;
- [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_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);
+ [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);
+ IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+ context.Features.Set<IFormFeature>(formFeature);
- var formCollection = context.Request.Form;
+ var formCollection = context.Request.Form;
- Assert.NotNull(formCollection);
+ 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());
+ // 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"]);
+ // Content
+ Assert.Equal(1, formCollection.Count);
+ Assert.Equal("Foo", formCollection["description"]);
- Assert.NotNull(formCollection.Files);
- Assert.Equal(0, formCollection.Files.Count);
+ Assert.NotNull(formCollection.Files);
+ Assert.Equal(0, formCollection.Files.Count);
- // Cleanup
- await responseFeature.CompleteAsync();
- }
+ // Cleanup
+ await responseFeature.CompleteAsync();
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_MultipartWithFile_ReturnsParsedFormCollection(bool bufferRequest)
+ [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))
{
- 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();
+ Assert.True(body.CanSeek);
+ var content = reader.ReadToEnd();
+ Assert.Equal("<html><body>Hello World</body></html>", content);
}
- [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);
+ await responseFeature.CompleteAsync();
+ }
- var formCollection = context.Request.Form;
+ [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);
- Assert.NotNull(formCollection);
+ IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+ context.Features.Set<IFormFeature>(formFeature);
- // 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());
+ var formCollection = context.Request.Form;
- // Content
- Assert.Equal(1, formCollection.Count);
- Assert.Equal("Foo", formCollection["description"]);
+ Assert.NotNull(formCollection);
- Assert.NotNull(formCollection.Files);
- Assert.Equal(0, formCollection.Files.Count);
+ // 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());
- // Cleanup
- await responseFeature.CompleteAsync();
- }
+ // Content
+ Assert.Equal(1, formCollection.Count);
+ Assert.Equal("Foo", formCollection["description"]);
- [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);
- }
+ Assert.NotNull(formCollection.Files);
+ Assert.Equal(0, formCollection.Files.Count);
- await responseFeature.CompleteAsync();
- }
+ // Cleanup
+ await responseFeature.CompleteAsync();
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_MultipartWithFieldAndFile_ReturnsParsedFormCollection(bool bufferRequest)
+ [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))
{
- 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();
+ Assert.True(body.CanSeek);
+ var content = reader.ReadToEnd();
+ Assert.Equal("<html><body>Hello World</body></html>", content);
}
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
+ 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))
{
- 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);
+ Assert.True(body.CanSeek);
+ var content = reader.ReadToEnd();
+ Assert.Equal("<html><body>Hello World</body></html>", content);
}
- [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));
+ 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);
+ }
- var context = new DefaultHttpContext();
- var responseFeature = new FakeResponseFeature();
- context.Features.Set<IHttpResponseFeature>(responseFeature);
- context.Request.ContentType = MultipartContentType;
- context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
+ [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));
- 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);
- }
+ var context = new DefaultHttpContext();
+ var responseFeature = new FakeResponseFeature();
+ context.Features.Set<IHttpResponseFeature>(responseFeature);
+ context.Request.ContentType = MultipartContentType;
+ context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
- [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);
- }
+ IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
+ context.Features.Set<IFormFeature>(formFeature);
- await responseFeature.CompleteAsync();
- }
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
+ Assert.Equal("Form value count limit 2 exceeded.", exception.Message);
+ }
- [Fact]
- public async Task ReadFormAsync_MultipartWithInvalidContentDisposition_Throw()
+ [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())
{
- var formContent = Encoding.UTF8.GetBytes(MultipartFormWithInvalidContentDispositionValue);
- var context = new DefaultHttpContext();
- var responseFeature = new FakeResponseFeature();
- context.Features.Set<IHttpResponseFeature>(responseFeature);
- context.Request.ContentType = MultipartContentType;
- context.Request.Body = new NonSeekableReadStream(formContent);
+ Assert.True(body.CanSeek);
+ CompareStreams(fileContents, body);
+ }
- IFormFeature formFeature = new FormFeature(context.Request, new FormOptions());
- context.Features.Set<IFormFeature>(formFeature);
+ await responseFeature.CompleteAsync();
+ }
- var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
+ [Fact]
+ public async Task ReadFormAsync_MultipartWithInvalidContentDisposition_Throw()
+ {
+ var formContent = Encoding.UTF8.GetBytes(MultipartFormWithInvalidContentDispositionValue);
+ var context = new DefaultHttpContext();
+ var responseFeature = new FakeResponseFeature();
+ context.Features.Set<IHttpResponseFeature>(responseFeature);
+ context.Request.ContentType = MultipartContentType;
+ context.Request.Body = new NonSeekableReadStream(formContent);
- Assert.Equal("Form section has invalid Content-Disposition value: " + InvalidContentDispositionValue, exception.Message);
- }
+ IFormFeature formFeature = new FormFeature(context.Request, new FormOptions());
+ context.Features.Set<IFormFeature>(formFeature);
+
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(() => context.Request.ReadFormAsync());
- private Stream CreateFile(int size)
+ Assert.Equal("Form section has invalid Content-Disposition value: " + InvalidContentDispositionValue, exception.Message);
+ }
+
+ 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 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;
+ 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 =
+ 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 =
+ var footer =
"\r\n--WebKitFormBoundary5pDRpGheQXaM8k3T--";
- var bytes = Encoding.ASCII.GetBytes(header);
- stream.Write(bytes, 0, bytes.Length);
+ var bytes = Encoding.ASCII.GetBytes(header);
+ stream.Write(bytes, 0, bytes.Length);
- fileContents.CopyTo(stream);
- fileContents.Position = 0;
+ fileContents.CopyTo(stream);
+ fileContents.Position = 0;
- bytes = Encoding.ASCII.GetBytes(footer);
- stream.Write(bytes, 0, bytes.Length);
- stream.Position = 0;
- return stream;
- }
+ bytes = Encoding.ASCII.GetBytes(footer);
+ stream.Write(bytes, 0, bytes.Length);
+ stream.Position = 0;
+ return stream;
+ }
- private void CompareStreams(Stream streamA, Stream streamB)
+ 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)
{
- 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++)
{
- for (int i = 0; i < readA; i++)
+ if (bytesA[i] != bytesB[i])
{
- if (bytesA[i] != bytesB[i])
- {
- throw new Exception($"Value mismatch at loop {loops}, index {i}; A:{bytesA[i]}, B:{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++;
}
+
+ 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
index 4d148f17d8..1bd917115e 100644
--- a/src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs
+++ b/src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs
@@ -3,41 +3,40 @@
using Xunit;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class HttpRequestIdentifierFeatureTests
{
- public class HttpRequestIdentifierFeatureTests
+ [Fact]
+ public void TraceIdentifier_ReturnsId()
{
- [Fact]
- public void TraceIdentifier_ReturnsId()
- {
- var feature = new HttpRequestIdentifierFeature();
+ var feature = new HttpRequestIdentifierFeature();
- var id = feature.TraceIdentifier;
+ var id = feature.TraceIdentifier;
- Assert.NotNull(id);
- }
+ Assert.NotNull(id);
+ }
- [Fact]
- public void TraceIdentifier_ReturnsStableId()
- {
- var feature = new HttpRequestIdentifierFeature();
+ [Fact]
+ public void TraceIdentifier_ReturnsStableId()
+ {
+ var feature = new HttpRequestIdentifierFeature();
- var id1 = feature.TraceIdentifier;
- var id2 = feature.TraceIdentifier;
+ var id1 = feature.TraceIdentifier;
+ var id2 = feature.TraceIdentifier;
- Assert.Equal(id1, id2);
- }
+ Assert.Equal(id1, id2);
+ }
- [Fact]
- public void TraceIdentifier_ReturnsUniqueIdForDifferentInstances()
- {
- var feature1 = new HttpRequestIdentifierFeature();
- var feature2 = new HttpRequestIdentifierFeature();
+ [Fact]
+ public void TraceIdentifier_ReturnsUniqueIdForDifferentInstances()
+ {
+ var feature1 = new HttpRequestIdentifierFeature();
+ var feature2 = new HttpRequestIdentifierFeature();
- var id1 = feature1.TraceIdentifier;
- var id2 = feature2.TraceIdentifier;
+ var id1 = feature1.TraceIdentifier;
+ var id2 = feature2.TraceIdentifier;
- Assert.NotEqual(id1, id2);
- }
+ 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
index 39cc8f5a25..c31f0b204e 100644
--- a/src/Http/Http/test/Features/NonSeekableReadStream.cs
+++ b/src/Http/Http/test/Features/NonSeekableReadStream.cs
@@ -6,67 +6,66 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class NonSeekableReadStream : Stream
{
- public class NonSeekableReadStream : Stream
+ private readonly 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)
{
- private readonly 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);
- }
+ 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
index 6c6849b105..926fbf2085 100644
--- a/src/Http/Http/test/Features/QueryFeatureTests.cs
+++ b/src/Http/Http/test/Features/QueryFeatureTests.cs
@@ -4,255 +4,254 @@
using System.Linq;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class QueryFeatureTests
{
- public class QueryFeatureTests
+ [Fact]
+ public void QueryReturnsParsedQueryCollection()
{
- [Fact]
- public void QueryReturnsParsedQueryCollection()
- {
- // Arrange
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "foo=bar" };
+ // Arrange
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "foo=bar" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- // Act
- var queryCollection = provider.Query;
+ // Act
+ var queryCollection = provider.Query;
- // Assert
- Assert.Equal("bar", queryCollection["foo"]);
- }
+ // Assert
+ Assert.Equal("bar", queryCollection["foo"]);
+ }
- [Theory]
- [InlineData("?key1=value1&key2=value2")]
- [InlineData("key1=value1&key2=value2")]
- public void ParseQueryWithUniqueKeysWorks(string queryString)
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
+ [Theory]
+ [InlineData("?key1=value1&key2=value2")]
+ [InlineData("key1=value1&key2=value2")]
+ public void ParseQueryWithUniqueKeysWorks(string queryString)
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(2, queryCollection.Count);
- Assert.Equal("value1", queryCollection["key1"].FirstOrDefault());
- Assert.Equal("value2", queryCollection["key2"].FirstOrDefault());
- }
+ Assert.Equal(2, queryCollection.Count);
+ Assert.Equal("value1", queryCollection["key1"].FirstOrDefault());
+ Assert.Equal("value2", queryCollection["key2"].FirstOrDefault());
+ }
- [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();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
+ [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();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.True(queryCollection.Keys.Contains(emptyParam));
- Assert.Equal(string.Empty, queryCollection[emptyParam]);
- }
+ 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();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
+ [Theory]
+ [InlineData("?&&")]
+ [InlineData("?&")]
+ [InlineData("&&")]
+ public void EmptyKeysNotAddedToQueryCollection(string queryString)
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(0, queryCollection.Count);
- }
+ Assert.Equal(0, queryCollection.Count);
+ }
- [Fact]
- public void ParseQueryWithEmptyKeyWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?=value1&=" };
+ [Fact]
+ public void ParseQueryWithEmptyKeyWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?=value1&=" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Single(queryCollection);
- Assert.Equal(new[] { "value1", "" }, queryCollection[""]);
- }
+ Assert.Single(queryCollection);
+ Assert.Equal(new[] { "value1", "" }, queryCollection[""]);
+ }
- [Fact]
- public void ParseQueryWithDuplicateKeysGroups()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key1=valueA&key2=valueB&key1=valueC" };
+ [Fact]
+ public void ParseQueryWithDuplicateKeysGroups()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key1=valueA&key2=valueB&key1=valueC" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(2, queryCollection.Count);
- Assert.Equal(new[] { "valueA", "valueC" }, queryCollection["key1"]);
- Assert.Equal("valueB", queryCollection["key2"].FirstOrDefault());
- }
+ Assert.Equal(2, queryCollection.Count);
+ Assert.Equal(new[] { "valueA", "valueC" }, queryCollection["key1"]);
+ Assert.Equal("valueB", queryCollection["key2"].FirstOrDefault());
+ }
- [Fact]
- public void ParseQueryWithThreefoldKeysGroups()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key1=valueA&key2=valueB&key1=valueC&key1=valueD" };
+ [Fact]
+ public void ParseQueryWithThreefoldKeysGroups()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key1=valueA&key2=valueB&key1=valueC&key1=valueD" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(2, queryCollection.Count);
- Assert.Equal(new[] { "valueA", "valueC", "valueD" }, queryCollection["key1"]);
- Assert.Equal("valueB", queryCollection["key2"].FirstOrDefault());
- }
+ Assert.Equal(2, queryCollection.Count);
+ Assert.Equal(new[] { "valueA", "valueC", "valueD" }, queryCollection["key1"]);
+ Assert.Equal("valueB", queryCollection["key2"].FirstOrDefault());
+ }
- [Fact]
- public void ParseQueryWithEmptyValuesWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key1=&key2=" };
+ [Fact]
+ public void ParseQueryWithEmptyValuesWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key1=&key2=" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(2, queryCollection.Count);
- Assert.Equal(string.Empty, queryCollection["key1"].FirstOrDefault());
- Assert.Equal(string.Empty, queryCollection["key2"].FirstOrDefault());
- }
+ Assert.Equal(2, queryCollection.Count);
+ Assert.Equal(string.Empty, queryCollection["key1"].FirstOrDefault());
+ Assert.Equal(string.Empty, queryCollection["key2"].FirstOrDefault());
+ }
- [Theory]
- [InlineData("?")]
- [InlineData("")]
- [InlineData(null)]
- public void ParseEmptyOrNullQueryWorks(string queryString)
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
+ [Theory]
+ [InlineData("?")]
+ [InlineData("")]
+ [InlineData(null)]
+ public void ParseEmptyOrNullQueryWorks(string queryString)
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = queryString };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Empty(queryCollection);
- }
+ Assert.Empty(queryCollection);
+ }
- [Fact]
- public void ParseQueryWithEncodedKeyWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D" };
+ [Fact]
+ public void ParseQueryWithEncodedKeyWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Single(queryCollection);
- Assert.Equal("", queryCollection["fields [todoItems]"].FirstOrDefault());
- }
+ Assert.Single(queryCollection);
+ Assert.Equal("", queryCollection["fields [todoItems]"].FirstOrDefault());
+ }
- [Fact]
- public void ParseQueryWithEncodedValueWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?=fields+%5BtodoItems%5D" };
+ [Fact]
+ public void ParseQueryWithEncodedValueWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?=fields+%5BtodoItems%5D" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Single(queryCollection);
- Assert.Equal("fields [todoItems]", queryCollection[""].FirstOrDefault());
- }
+ Assert.Single(queryCollection);
+ Assert.Equal("fields [todoItems]", queryCollection[""].FirstOrDefault());
+ }
- [Fact]
- public void ParseQueryWithEncodedKeyEmptyValueWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D=" };
+ [Fact]
+ public void ParseQueryWithEncodedKeyEmptyValueWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D=" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Single(queryCollection);
- Assert.Equal("", queryCollection["fields [todoItems]"].FirstOrDefault());
- }
+ Assert.Single(queryCollection);
+ Assert.Equal("", queryCollection["fields [todoItems]"].FirstOrDefault());
+ }
- [Fact]
- public void ParseQueryWithEncodedKeyEncodedValueWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D=%5B+1+%5D" };
+ [Fact]
+ public void ParseQueryWithEncodedKeyEncodedValueWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D=%5B+1+%5D" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Single(queryCollection);
- Assert.Equal("[ 1 ]", queryCollection["fields [todoItems]"].FirstOrDefault());
- }
+ Assert.Single(queryCollection);
+ Assert.Equal("[ 1 ]", queryCollection["fields [todoItems]"].FirstOrDefault());
+ }
- [Fact]
- public void ParseQueryWithEncodedKeyEncodedValuesWorks()
- {
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D=%5B+1+%5D&fields+%5BtodoItems%5D=%5B+2+%5D" };
+ [Fact]
+ public void ParseQueryWithEncodedKeyEncodedValuesWorks()
+ {
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?fields+%5BtodoItems%5D=%5B+1+%5D&fields+%5BtodoItems%5D=%5B+2+%5D" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Single(queryCollection);
- Assert.Equal(new[] { "[ 1 ]", "[ 2 ]" }, queryCollection["fields [todoItems]"]);
- }
+ Assert.Single(queryCollection);
+ Assert.Equal(new[] { "[ 1 ]", "[ 2 ]" }, queryCollection["fields [todoItems]"]);
+ }
- [Fact]
- public void CaseInsensitiveWithManyKeys()
+ [Fact]
+ public void CaseInsensitiveWithManyKeys()
+ {
+ // need to use over 10 keys to test dictionary storage code path
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature
{
- // need to use over 10 keys to test dictionary storage code path
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature
- {
- QueryString = "?a=0&b=0&c=1&d=2&e=3&f=4&g=5&h=6&i=7&j=8&k=9&" +
- "key=1&Key=2&key=3&Key=4&KEy=5&KEY=6&kEY=7&KeY=8&kEy=9&keY=10"
- };
+ QueryString = "?a=0&b=0&c=1&d=2&e=3&f=4&g=5&h=6&i=7&j=8&k=9&" +
+ "key=1&Key=2&key=3&Key=4&KEy=5&KEY=6&kEY=7&KeY=8&kEy=9&keY=10"
+ };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(12, queryCollection.Count);
- Assert.Equal(new[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }, queryCollection["KEY"]);
- }
+ Assert.Equal(12, queryCollection.Count);
+ Assert.Equal(new[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }, queryCollection["KEY"]);
+ }
- [Fact]
- public void CaseInsensitiveWithFewKeys()
- {
- // need to use less than 10 keys to test array storage code path
- var features = new FeatureCollection();
- features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key=1&Key=2&key=3&Key=4&KEy=5" };
+ [Fact]
+ public void CaseInsensitiveWithFewKeys()
+ {
+ // need to use less than 10 keys to test array storage code path
+ var features = new FeatureCollection();
+ features[typeof(IHttpRequestFeature)] = new HttpRequestFeature { QueryString = "?key=1&Key=2&key=3&Key=4&KEy=5" };
- var provider = new QueryFeature(features);
+ var provider = new QueryFeature(features);
- var queryCollection = provider.Query;
+ var queryCollection = provider.Query;
- Assert.Equal(1, queryCollection.Count);
- Assert.Equal(new[] { "1", "2", "3", "4", "5" }, queryCollection["KEY"]);
- }
+ Assert.Equal(1, queryCollection.Count);
+ Assert.Equal(new[] { "1", "2", "3", "4", "5" }, queryCollection["KEY"]);
}
}
diff --git a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs
index fac9f76027..842c6f02e5 100644
--- a/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs
+++ b/src/Http/Http/test/Features/RequestBodyPipeFeatureTests.cs
@@ -8,41 +8,40 @@ using System.Text;
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class RequestBodyPipeFeatureTests
{
- public class RequestBodyPipeFeatureTests
+ [Fact]
+ public void RequestBodyReturnsStreamPipeReader()
+ {
+ var context = new DefaultHttpContext();
+ var expectedStream = new MemoryStream();
+ context.Request.Body = expectedStream;
+
+ var feature = new RequestBodyPipeFeature(context);
+
+ var pipeBody = feature.Reader;
+
+ Assert.NotNull(pipeBody);
+ }
+
+ [Fact]
+ public async Task RequestBodyGetsDataFromSecondStream()
+ {
+ var context = new DefaultHttpContext();
+ context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes("hahaha"));
+ var feature = new RequestBodyPipeFeature(context);
+ var _ = feature.Reader;
+
+ var expectedString = "abcdef";
+ context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes(expectedString));
+ var data = await feature.Reader.ReadAsync();
+ Assert.Equal(expectedString, GetStringFromReadResult(data));
+ }
+
+ private static string GetStringFromReadResult(ReadResult data)
{
- [Fact]
- public void RequestBodyReturnsStreamPipeReader()
- {
- var context = new DefaultHttpContext();
- var expectedStream = new MemoryStream();
- context.Request.Body = expectedStream;
-
- var feature = new RequestBodyPipeFeature(context);
-
- var pipeBody = feature.Reader;
-
- Assert.NotNull(pipeBody);
- }
-
- [Fact]
- public async Task RequestBodyGetsDataFromSecondStream()
- {
- var context = new DefaultHttpContext();
- context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes("hahaha"));
- var feature = new RequestBodyPipeFeature(context);
- var _ = feature.Reader;
-
- var expectedString = "abcdef";
- context.Request.Body = new MemoryStream(Encoding.ASCII.GetBytes(expectedString));
- var data = await feature.Reader.ReadAsync();
- Assert.Equal(expectedString, GetStringFromReadResult(data));
- }
-
- private static string GetStringFromReadResult(ReadResult data)
- {
- return Encoding.ASCII.GetString(data.Buffer.ToArray());
- }
+ return Encoding.ASCII.GetString(data.Buffer.ToArray());
}
}
diff --git a/src/Http/Http/test/Features/StreamResponseBodyFeatureTests.cs b/src/Http/Http/test/Features/StreamResponseBodyFeatureTests.cs
index afc8ff4520..8e16560bf1 100644
--- a/src/Http/Http/test/Features/StreamResponseBodyFeatureTests.cs
+++ b/src/Http/Http/test/Features/StreamResponseBodyFeatureTests.cs
@@ -9,85 +9,84 @@ using System.Threading;
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Features
+namespace Microsoft.AspNetCore.Http.Features;
+
+public class StreamResponseBodyFeatureTests
{
- public class StreamResponseBodyFeatureTests
+ [Fact]
+ public async Task CompleteAsyncCallsStartAsync()
{
- [Fact]
- public async Task CompleteAsyncCallsStartAsync()
- {
- // Arrange
- var stream = new MemoryStream();
- var streamResponseBodyFeature = new TestStreamResponseBodyFeature(stream);
-
- // Act
- await streamResponseBodyFeature.CompleteAsync();
-
- //Assert
- Assert.Equal(1, streamResponseBodyFeature.StartCalled);
- }
-
- [Fact]
- public async Task CompleteAsyncWontCallsStartAsyncIfAlreadyStarted()
- {
- // Arrange
- var stream = new MemoryStream();
- var streamResponseBodyFeature = new TestStreamResponseBodyFeature(stream);
- await streamResponseBodyFeature.StartAsync();
-
- // Act
- await streamResponseBodyFeature.CompleteAsync();
-
- //Assert
- Assert.Equal(1, streamResponseBodyFeature.StartCalled);
- }
-
- [Fact]
- public void DisableBufferingCallsInnerFeature()
- {
- // Arrange
- var stream = new MemoryStream();
-
- var innerFeature = new InnerDisableBufferingFeature(stream, null);
- var streamResponseBodyFeature = new StreamResponseBodyFeature(stream, innerFeature);
-
- // Act
- streamResponseBodyFeature.DisableBuffering();
-
- //Assert
- Assert.True(innerFeature.DisableBufferingCalled);
- }
+ // Arrange
+ var stream = new MemoryStream();
+ var streamResponseBodyFeature = new TestStreamResponseBodyFeature(stream);
+
+ // Act
+ await streamResponseBodyFeature.CompleteAsync();
+
+ //Assert
+ Assert.Equal(1, streamResponseBodyFeature.StartCalled);
}
- public class TestStreamResponseBodyFeature : StreamResponseBodyFeature
+ [Fact]
+ public async Task CompleteAsyncWontCallsStartAsyncIfAlreadyStarted()
{
- public TestStreamResponseBodyFeature(Stream stream)
- : base(stream)
- {
+ // Arrange
+ var stream = new MemoryStream();
+ var streamResponseBodyFeature = new TestStreamResponseBodyFeature(stream);
+ await streamResponseBodyFeature.StartAsync();
+
+ // Act
+ await streamResponseBodyFeature.CompleteAsync();
+
+ //Assert
+ Assert.Equal(1, streamResponseBodyFeature.StartCalled);
+ }
- }
+ [Fact]
+ public void DisableBufferingCallsInnerFeature()
+ {
+ // Arrange
+ var stream = new MemoryStream();
+
+ var innerFeature = new InnerDisableBufferingFeature(stream, null);
+ var streamResponseBodyFeature = new StreamResponseBodyFeature(stream, innerFeature);
+
+ // Act
+ streamResponseBodyFeature.DisableBuffering();
+
+ //Assert
+ Assert.True(innerFeature.DisableBufferingCalled);
+ }
+}
- public override Task StartAsync(CancellationToken cancellationToken = default)
- {
- StartCalled++;
- return base.StartAsync(cancellationToken);
- }
+public class TestStreamResponseBodyFeature : StreamResponseBodyFeature
+{
+ public TestStreamResponseBodyFeature(Stream stream)
+ : base(stream)
+ {
- public int StartCalled { get; private set; }
}
- public class InnerDisableBufferingFeature : StreamResponseBodyFeature
+ public override Task StartAsync(CancellationToken cancellationToken = default)
{
- public InnerDisableBufferingFeature(Stream stream, IHttpResponseBodyFeature priorFeature)
- : base(stream, priorFeature)
- {
- }
+ StartCalled++;
+ return base.StartAsync(cancellationToken);
+ }
- public override void DisableBuffering()
- {
- DisableBufferingCalled = true;
- }
+ public int StartCalled { get; private set; }
+}
- public bool DisableBufferingCalled { get; set; }
+public class InnerDisableBufferingFeature : StreamResponseBodyFeature
+{
+ public InnerDisableBufferingFeature(Stream stream, IHttpResponseBodyFeature priorFeature)
+ : base(stream, priorFeature)
+ {
}
+
+ public override void DisableBuffering()
+ {
+ DisableBufferingCalled = true;
+ }
+
+ public bool DisableBufferingCalled { get; set; }
}
diff --git a/src/Http/Http/test/HeaderDictionaryTests.cs b/src/Http/Http/test/HeaderDictionaryTests.cs
index f4fc5f776e..27de61aec5 100644
--- a/src/Http/Http/test/HeaderDictionaryTests.cs
+++ b/src/Http/Http/test/HeaderDictionaryTests.cs
@@ -7,11 +7,11 @@ using System.Linq;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class HeaderDictionaryTests
{
- public class HeaderDictionaryTests
- {
- public static TheoryData HeaderSegmentData => new TheoryData<IEnumerable<string>>
+ public static TheoryData HeaderSegmentData => new TheoryData<IEnumerable<string>>
{
new[] { "Value1", "Value2", "Value3", "Value4" },
new[] { "Value1", "", "Value3", "Value4" },
@@ -21,100 +21,99 @@ namespace Microsoft.AspNetCore.Http
new[] { "", null, "", null },
};
- [Fact]
- public void PropertiesAreAccessible()
- {
- var headers = new HeaderDictionary(
- new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
- {
+ [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);
+ });
+
+ 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)
- {
+ 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));
+ var result = headers.GetCommaSeparatedValues("Header1");
+ var expectedResult = segments.Where(s => !string.IsNullOrEmpty(s));
- Assert.Equal(expectedResult, result);
- }
+ Assert.Equal(expectedResult, result);
+ }
- [Fact]
- public void EmptyQuotedHeaderSegmentsAreIgnored()
- {
- var headers = new HeaderDictionary(
- new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
- {
+ [Fact]
+ public void EmptyQuotedHeaderSegmentsAreIgnored()
+ {
+ var headers = new HeaderDictionary(
+ new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
+ {
{ "Header1", "Value1,\"\",,Value2" },
- });
+ });
- var result = headers.GetCommaSeparatedValues("Header1");
- Assert.Equal(new[] { "Value1", "Value2" }, result);
- }
+ 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)
- {
+ [Fact]
+ public void ReadActionsWorkWhenReadOnly()
+ {
+ var headers = new HeaderDictionary(
+ new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
+ {
{ "Header1", "Value1" }
- });
+ });
- headers.IsReadOnly = true;
+ 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());
- }
+ 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"));
- }
-
- [Fact]
- public void GetCommaSeparatedValues_WorksForUnquotedHeaderValuesEndingWithSpace()
- {
- var headers = new HeaderDictionary
+ [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"));
+ }
+
+ [Fact]
+ public void GetCommaSeparatedValues_WorksForUnquotedHeaderValuesEndingWithSpace()
+ {
+ var headers = new HeaderDictionary
{
{ "Via", "value " },
};
- var result = headers.GetCommaSeparatedValues("Via");
+ var result = headers.GetCommaSeparatedValues("Via");
- Assert.Equal(new[]{"value "}, result);
- }
+ Assert.Equal(new[] { "value " }, result);
}
}
diff --git a/src/Http/Http/test/HttpContextAccessorTests.cs b/src/Http/Http/test/HttpContextAccessorTests.cs
index 9cce449171..b3df46ef5b 100644
--- a/src/Http/Http/test/HttpContextAccessorTests.cs
+++ b/src/Http/Http/test/HttpContextAccessorTests.cs
@@ -12,177 +12,176 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class HttpContextAccessorTests
{
- public class HttpContextAccessorTests
+ [Fact]
+ public async Task HttpContextAccessor_GettingHttpContextReturnsHttpContext()
{
- [Fact]
- public async Task HttpContextAccessor_GettingHttpContextReturnsHttpContext()
- {
- var accessor = new HttpContextAccessor();
+ var accessor = new HttpContextAccessor();
- var context = new DefaultHttpContext();
- context.TraceIdentifier = "1";
- accessor.HttpContext = context;
+ var context = new DefaultHttpContext();
+ context.TraceIdentifier = "1";
+ accessor.HttpContext = context;
- await Task.Delay(100);
+ await Task.Delay(100);
- Assert.Same(context, accessor.HttpContext);
- }
+ Assert.Same(context, accessor.HttpContext);
+ }
- [Fact]
- public void HttpContextAccessor_GettingHttpContextWithOutSettingReturnsNull()
- {
- var accessor = new HttpContextAccessor();
+ [Fact]
+ public void HttpContextAccessor_GettingHttpContextWithOutSettingReturnsNull()
+ {
+ var accessor = new HttpContextAccessor();
- Assert.Null(accessor.HttpContext);
- }
+ Assert.Null(accessor.HttpContext);
+ }
- [Fact]
- public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfSetToNull()
- {
- var accessor = new HttpContextAccessor();
+ [Fact]
+ public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfSetToNull()
+ {
+ var accessor = new HttpContextAccessor();
- var context = new DefaultHttpContext();
- accessor.HttpContext = context;
+ var context = new DefaultHttpContext();
+ accessor.HttpContext = context;
- var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- var waitForNullTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- var afterNullCheckTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var waitForNullTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var afterNullCheckTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- ThreadPool.QueueUserWorkItem(async _ =>
- {
+ ThreadPool.QueueUserWorkItem(async _ =>
+ {
// The HttpContext flows with the execution context
Assert.Same(context, accessor.HttpContext);
- checkAsyncFlowTcs.SetResult(null);
+ checkAsyncFlowTcs.SetResult(null);
- await waitForNullTcs.Task;
+ await waitForNullTcs.Task;
- try
- {
- Assert.Null(accessor.HttpContext);
+ try
+ {
+ Assert.Null(accessor.HttpContext);
- afterNullCheckTcs.SetResult(null);
- }
- catch (Exception ex)
- {
- afterNullCheckTcs.SetException(ex);
- }
- });
+ afterNullCheckTcs.SetResult(null);
+ }
+ catch (Exception ex)
+ {
+ afterNullCheckTcs.SetException(ex);
+ }
+ });
- await checkAsyncFlowTcs.Task;
+ await checkAsyncFlowTcs.Task;
- // Null out the accessor
- accessor.HttpContext = null;
+ // Null out the accessor
+ accessor.HttpContext = null;
- waitForNullTcs.SetResult(null);
+ waitForNullTcs.SetResult(null);
- Assert.Null(accessor.HttpContext);
+ Assert.Null(accessor.HttpContext);
- await afterNullCheckTcs.Task;
- }
+ await afterNullCheckTcs.Task;
+ }
- [Fact]
- public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfChanged()
- {
- var accessor = new HttpContextAccessor();
+ [Fact]
+ public async Task HttpContextAccessor_GettingHttpContextReturnsNullHttpContextIfChanged()
+ {
+ var accessor = new HttpContextAccessor();
- var context = new DefaultHttpContext();
- accessor.HttpContext = context;
+ var context = new DefaultHttpContext();
+ accessor.HttpContext = context;
- var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- var waitForNullTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- var afterNullCheckTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var waitForNullTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var afterNullCheckTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- ThreadPool.QueueUserWorkItem(async _ =>
- {
+ ThreadPool.QueueUserWorkItem(async _ =>
+ {
// The HttpContext flows with the execution context
Assert.Same(context, accessor.HttpContext);
- checkAsyncFlowTcs.SetResult(null);
+ checkAsyncFlowTcs.SetResult(null);
- await waitForNullTcs.Task;
+ await waitForNullTcs.Task;
- try
- {
- Assert.Null(accessor.HttpContext);
+ try
+ {
+ Assert.Null(accessor.HttpContext);
- afterNullCheckTcs.SetResult(null);
- }
- catch (Exception ex)
- {
- afterNullCheckTcs.SetException(ex);
- }
- });
+ afterNullCheckTcs.SetResult(null);
+ }
+ catch (Exception ex)
+ {
+ afterNullCheckTcs.SetException(ex);
+ }
+ });
- await checkAsyncFlowTcs.Task;
+ await checkAsyncFlowTcs.Task;
- // Set a new http context
- var context2 = new DefaultHttpContext();
- accessor.HttpContext = context2;
+ // Set a new http context
+ var context2 = new DefaultHttpContext();
+ accessor.HttpContext = context2;
- waitForNullTcs.SetResult(null);
+ waitForNullTcs.SetResult(null);
- Assert.Same(context2, accessor.HttpContext);
+ Assert.Same(context2, accessor.HttpContext);
- await afterNullCheckTcs.Task;
- }
+ await afterNullCheckTcs.Task;
+ }
- [Fact]
- public async Task HttpContextAccessor_GettingHttpContextDoesNotFlowIfAccessorSetToNull()
- {
- var accessor = new HttpContextAccessor();
+ [Fact]
+ public async Task HttpContextAccessor_GettingHttpContextDoesNotFlowIfAccessorSetToNull()
+ {
+ var accessor = new HttpContextAccessor();
- var context = new DefaultHttpContext();
- accessor.HttpContext = context;
+ var context = new DefaultHttpContext();
+ accessor.HttpContext = context;
- var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- accessor.HttpContext = null;
+ accessor.HttpContext = null;
- ThreadPool.QueueUserWorkItem(_ =>
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ try
{
- try
- {
// The HttpContext flows with the execution context
Assert.Null(accessor.HttpContext);
- checkAsyncFlowTcs.SetResult(null);
- }
- catch (Exception ex)
- {
- checkAsyncFlowTcs.SetException(ex);
- }
- });
-
- await checkAsyncFlowTcs.Task;
- }
-
- [Fact]
- public async Task HttpContextAccessor_GettingHttpContextDoesNotFlowIfExecutionContextDoesNotFlow()
- {
- var accessor = new HttpContextAccessor();
+ checkAsyncFlowTcs.SetResult(null);
+ }
+ catch (Exception ex)
+ {
+ checkAsyncFlowTcs.SetException(ex);
+ }
+ });
+
+ await checkAsyncFlowTcs.Task;
+ }
+
+ [Fact]
+ public async Task HttpContextAccessor_GettingHttpContextDoesNotFlowIfExecutionContextDoesNotFlow()
+ {
+ var accessor = new HttpContextAccessor();
- var context = new DefaultHttpContext();
- accessor.HttpContext = context;
+ var context = new DefaultHttpContext();
+ accessor.HttpContext = context;
- var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var checkAsyncFlowTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
- ThreadPool.UnsafeQueueUserWorkItem(_ =>
+ ThreadPool.UnsafeQueueUserWorkItem(_ =>
+ {
+ try
{
- try
- {
// The HttpContext flows with the execution context
Assert.Null(accessor.HttpContext);
- checkAsyncFlowTcs.SetResult(null);
- }
- catch (Exception ex)
- {
- checkAsyncFlowTcs.SetException(ex);
- }
- }, null);
-
- await checkAsyncFlowTcs.Task;
- }
+ checkAsyncFlowTcs.SetResult(null);
+ }
+ catch (Exception ex)
+ {
+ checkAsyncFlowTcs.SetException(ex);
+ }
+ }, null);
+
+ await checkAsyncFlowTcs.Task;
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs b/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs
index 2f06064f92..367384b6f5 100644
--- a/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs
+++ b/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs
@@ -5,29 +5,28 @@ using System;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Tests
+namespace Microsoft.AspNetCore.Http.Tests;
+
+public class HttpServiceCollectionExtensionsTests
{
- public class HttpServiceCollectionExtensionsTests
+ [Fact]
+ public void AddHttpContextAccessor_AddsWithCorrectLifetime()
{
- [Fact]
- public void AddHttpContextAccessor_AddsWithCorrectLifetime()
- {
- // Arrange
- var services = new ServiceCollection();
+ // Arrange
+ var services = new ServiceCollection();
- // Act
- services.AddHttpContextAccessor();
+ // Act
+ services.AddHttpContextAccessor();
- // Assert
- var descriptor = services[0];
- Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
- Assert.Equal(typeof(HttpContextAccessor), descriptor.ImplementationType);
- }
+ // 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));
- }
+ [Fact]
+ public void AddHttpContextAccessor_ThrowsWithoutServices()
+ {
+ Assert.Throws<ArgumentNullException>("services", () => HttpServiceCollectionExtensions.AddHttpContextAccessor(null));
}
}
diff --git a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs
index 2475f87b23..9924c8117c 100644
--- a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs
+++ b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs
@@ -11,287 +11,286 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class DefaultHttpRequestTests
{
- public class DefaultHttpRequestTests
+ [Theory]
+ [InlineData(0)]
+ [InlineData(9001)]
+ [InlineData(65535)]
+ public void GetContentLength_ReturnsParsedHeader(long value)
{
- [Theory]
- [InlineData(0)]
- [InlineData(9001)]
- [InlineData(65535)]
- public void GetContentLength_ReturnsParsedHeader(long value)
- {
- // Arrange
- var request = GetRequestWithContentLength(value.ToString(CultureInfo.InvariantCulture));
+ // Arrange
+ var request = GetRequestWithContentLength(value.ToString(CultureInfo.InvariantCulture));
- // Act and Assert
- Assert.Equal(value, request.ContentLength);
- }
+ // Act and Assert
+ Assert.Equal(value, request.ContentLength);
+ }
- [Fact]
- public void GetContentLength_ReturnsNullIfHeaderDoesNotExist()
- {
- // Arrange
- var request = GetRequestWithContentLength(contentLength: null);
+ [Fact]
+ public void GetContentLength_ReturnsNullIfHeaderDoesNotExist()
+ {
+ // Arrange
+ var request = GetRequestWithContentLength(contentLength: null);
- // Act and Assert
- Assert.Null(request.ContentLength);
- }
+ // 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);
+ [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);
- }
+ // Act and Assert
+ Assert.Null(request.ContentLength);
+ }
- [Fact]
- public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
- {
- // Arrange
- var request = GetRequestWithContentType(contentType: null);
+ [Fact]
+ public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
+ {
+ // Arrange
+ var request = GetRequestWithContentType(contentType: null);
- // Act and Assert
- Assert.Null(request.ContentType);
- }
+ // Act and Assert
+ Assert.Null(request.ContentType);
+ }
- [Fact]
- public void Host_GetsHostFromHeaders()
- {
- // Arrange
- const string expected = "localhost:9001";
+ [Fact]
+ public void Host_GetsHostFromHeaders()
+ {
+ // Arrange
+ const string expected = "localhost:9001";
- var headers = new HeaderDictionary()
+ var headers = new HeaderDictionary()
{
{ "Host", expected },
};
- var request = CreateRequest(headers);
+ var request = CreateRequest(headers);
- // Act
- var host = request.Host;
+ // Act
+ var host = request.Host;
- // Assert
- Assert.Equal(expected, host.Value);
- }
+ // Assert
+ Assert.Equal(expected, host.Value);
+ }
- [Fact]
- public void Host_DecodesPunyCode()
- {
- // Arrange
- const string expected = "löcalhöst";
+ [Fact]
+ public void Host_DecodesPunyCode()
+ {
+ // Arrange
+ const string expected = "löcalhöst";
- var headers = new HeaderDictionary()
+ var headers = new HeaderDictionary()
{
{ "Host", "xn--lcalhst-90ae" },
};
- var request = CreateRequest(headers);
+ var request = CreateRequest(headers);
- // Act
- var host = request.Host;
+ // Act
+ var host = request.Host;
- // Assert
- Assert.Equal(expected, host.Value);
- }
+ // Assert
+ Assert.Equal(expected, host.Value);
+ }
- [Fact]
- public void Host_EncodesPunyCode()
- {
- // Arrange
- const string expected = "xn--lcalhst-90ae";
+ [Fact]
+ public void Host_EncodesPunyCode()
+ {
+ // Arrange
+ const string expected = "xn--lcalhst-90ae";
- var headers = new HeaderDictionary();
+ var headers = new HeaderDictionary();
- var request = CreateRequest(headers);
+ var request = CreateRequest(headers);
- // Act
- request.Host = new HostString("löcalhöst");
+ // Act
+ request.Host = new HostString("löcalhöst");
- // Assert
- Assert.Equal(expected, headers["Host"][0]);
- }
+ // 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 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>()
+ [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);
- }
+ 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", "name1=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>()
+ [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", "name1=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);
- }
+ request.Cookies = cookies2;
+ Assert.Equal(cookies2, request.Cookies);
+ Assert.Equal("value2", request.Cookies["name2"]);
+ cookieHeaders = request.Headers["Cookie"];
+ Assert.Equal(new[] { "name2=value2" }, cookieHeaders);
+ }
- [Fact]
- public void RouteValues_GetAndSet()
- {
- var context = new DefaultHttpContext();
- var request = context.Request;
+ [Fact]
+ public void RouteValues_GetAndSet()
+ {
+ var context = new DefaultHttpContext();
+ var request = context.Request;
- var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
- // No feature set for initial DefaultHttpRequest
- Assert.Null(routeValuesFeature);
+ var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
+ // No feature set for initial DefaultHttpRequest
+ Assert.Null(routeValuesFeature);
- // Route values returns empty collection by default
- Assert.Empty(request.RouteValues);
+ // Route values returns empty collection by default
+ Assert.Empty(request.RouteValues);
- // Get and set value on request route values
- request.RouteValues["new"] = "setvalue";
- Assert.Equal("setvalue", request.RouteValues["new"]);
+ // Get and set value on request route values
+ request.RouteValues["new"] = "setvalue";
+ Assert.Equal("setvalue", request.RouteValues["new"]);
- routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
- // Accessing DefaultHttpRequest.RouteValues creates feature
- Assert.NotNull(routeValuesFeature);
+ routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
+ // Accessing DefaultHttpRequest.RouteValues creates feature
+ Assert.NotNull(routeValuesFeature);
- request.RouteValues = new RouteValueDictionary(new { key = "value" });
- // Can set DefaultHttpRequest.RouteValues
- Assert.NotNull(request.RouteValues);
- Assert.Equal("value", request.RouteValues["key"]);
+ request.RouteValues = new RouteValueDictionary(new { key = "value" });
+ // Can set DefaultHttpRequest.RouteValues
+ Assert.NotNull(request.RouteValues);
+ Assert.Equal("value", request.RouteValues["key"]);
- // DefaultHttpRequest.RouteValues uses feature
- Assert.Equal(routeValuesFeature.RouteValues, request.RouteValues);
+ // DefaultHttpRequest.RouteValues uses feature
+ Assert.Equal(routeValuesFeature.RouteValues, request.RouteValues);
- // Setting route values to null sets empty collection on request
- routeValuesFeature.RouteValues = null;
- Assert.Empty(request.RouteValues);
+ // Setting route values to null sets empty collection on request
+ routeValuesFeature.RouteValues = null;
+ Assert.Empty(request.RouteValues);
- var customRouteValuesFeature = new CustomRouteValuesFeature
- {
- RouteValues = new RouteValueDictionary(new { key = "customvalue" })
- };
- context.Features.Set<IRouteValuesFeature>(customRouteValuesFeature);
- // Can override DefaultHttpRequest.RouteValues with custom feature
- Assert.Equal(customRouteValuesFeature.RouteValues, request.RouteValues);
+ var customRouteValuesFeature = new CustomRouteValuesFeature
+ {
+ RouteValues = new RouteValueDictionary(new { key = "customvalue" })
+ };
+ context.Features.Set<IRouteValuesFeature>(customRouteValuesFeature);
+ // Can override DefaultHttpRequest.RouteValues with custom feature
+ Assert.Equal(customRouteValuesFeature.RouteValues, request.RouteValues);
+
+ // Can clear feature
+ context.Features.Set<IRouteValuesFeature>(null);
+ Assert.Empty(request.RouteValues);
+ }
- // Can clear feature
- context.Features.Set<IRouteValuesFeature>(null);
- Assert.Empty(request.RouteValues);
- }
+ [Fact]
+ public void BodyReader_CanGet()
+ {
+ var context = new DefaultHttpContext();
+ var bodyPipe = context.Request.BodyReader;
+ Assert.NotNull(bodyPipe);
+ }
- [Fact]
- public void BodyReader_CanGet()
- {
- var context = new DefaultHttpContext();
- var bodyPipe = context.Request.BodyReader;
- Assert.NotNull(bodyPipe);
- }
+ private class CustomRouteValuesFeature : IRouteValuesFeature
+ {
+ public RouteValueDictionary RouteValues { get; set; }
+ }
- private class CustomRouteValuesFeature : IRouteValuesFeature
- {
- public RouteValueDictionary RouteValues { get; set; }
- }
+ private static HttpRequest CreateRequest(IHeaderDictionary headers)
+ {
+ var context = new DefaultHttpContext();
+ context.Features.Get<IHttpRequestFeature>().Headers = headers;
+ return context.Request;
+ }
- 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 GetRequestWithContentLength(string contentLength = null)
- {
- return GetRequestWithHeader("Content-Length", contentLength);
- }
+ private static HttpRequest GetRequestWithContentType(string contentType = null)
+ {
+ return GetRequestWithHeader("Content-Type", contentType);
+ }
- 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 GetRequestWithAcceptHeader(string acceptHeader = null)
- {
- return GetRequestWithHeader("Accept", acceptHeader);
- }
+ private static HttpRequest GetRequestWithAcceptCharsetHeader(string acceptCharset = null)
+ {
+ return GetRequestWithHeader("Accept-Charset", acceptCharset);
+ }
- private static HttpRequest GetRequestWithAcceptCharsetHeader(string acceptCharset = null)
+ private static HttpRequest GetRequestWithHeader(string headerName, string headerValue)
+ {
+ var headers = new HeaderDictionary();
+ if (headerValue != null)
{
- return GetRequestWithHeader("Accept-Charset", acceptCharset);
+ headers.Add(headerName, headerValue);
}
- private static HttpRequest GetRequestWithHeader(string headerName, string headerValue)
- {
- var headers = new HeaderDictionary();
- if (headerValue != null)
- {
- headers.Add(headerName, headerValue);
- }
-
- return CreateRequest(headers);
- }
+ return CreateRequest(headers);
}
}
diff --git a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs
index f061e46153..a576de6794 100644
--- a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs
+++ b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs
@@ -13,273 +13,272 @@ using Microsoft.Extensions.Primitives;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class DefaultHttpResponseTests
{
- public class DefaultHttpResponseTests
+ [Theory]
+ [InlineData(0)]
+ [InlineData(9001)]
+ [InlineData(65535)]
+ public void GetContentLength_ReturnsParsedHeader(long value)
{
- [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);
+ // Arrange
+ var response = GetResponseWithContentLength(value.ToString(CultureInfo.InvariantCulture));
- // Act and Assert
- Assert.Null(response.ContentLength);
- }
+ // Act and Assert
+ Assert.Equal(value, 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);
+ [Fact]
+ public void GetContentLength_ReturnsNullIfHeaderDoesNotExist()
+ {
+ // Arrange
+ var response = GetResponseWithContentLength(contentLength: null);
- // Act and Assert
- Assert.Null(response.ContentLength);
- }
+ // Act and Assert
+ Assert.Null(response.ContentLength);
+ }
- [Fact]
- public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
- {
- // Arrange
- var response = GetResponseWithContentType(contentType: null);
+ [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.ContentType);
- }
+ // Act and Assert
+ Assert.Null(response.ContentLength);
+ }
- [Fact]
- public void BodyWriter_CanGet()
- {
- var response = new DefaultHttpContext();
- var bodyPipe = response.Response.BodyWriter;
+ [Fact]
+ public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
+ {
+ // Arrange
+ var response = GetResponseWithContentType(contentType: null);
- Assert.NotNull(bodyPipe);
- }
+ // Act and Assert
+ Assert.Null(response.ContentType);
+ }
- [Fact]
- public void ReplacingResponseBody_DoesNotCreateOnCompletedRegistration()
- {
- var features = new FeatureCollection();
+ [Fact]
+ public void BodyWriter_CanGet()
+ {
+ var response = new DefaultHttpContext();
+ var bodyPipe = response.Response.BodyWriter;
- var originalStream = new FlushAsyncCheckStream();
- var replacementStream = new FlushAsyncCheckStream();
+ Assert.NotNull(bodyPipe);
+ }
- var responseBodyMock = new Mock<IHttpResponseBodyFeature>();
- responseBodyMock.Setup(o => o.Stream).Returns(originalStream);
- features.Set(responseBodyMock.Object);
+ [Fact]
+ public void ReplacingResponseBody_DoesNotCreateOnCompletedRegistration()
+ {
+ var features = new FeatureCollection();
- var responseMock = new Mock<IHttpResponseFeature>();
- features.Set(responseMock.Object);
+ var originalStream = new FlushAsyncCheckStream();
+ var replacementStream = new FlushAsyncCheckStream();
- var context = new DefaultHttpContext(features);
+ var responseBodyMock = new Mock<IHttpResponseBodyFeature>();
+ responseBodyMock.Setup(o => o.Stream).Returns(originalStream);
+ features.Set(responseBodyMock.Object);
- Assert.Same(originalStream, context.Response.Body);
- Assert.Same(responseBodyMock.Object, context.Features.Get<IHttpResponseBodyFeature>());
+ var responseMock = new Mock<IHttpResponseFeature>();
+ features.Set(responseMock.Object);
- context.Response.Body = replacementStream;
+ var context = new DefaultHttpContext(features);
- Assert.Same(replacementStream, context.Response.Body);
- Assert.NotSame(responseBodyMock.Object, context.Features.Get<IHttpResponseBodyFeature>());
+ Assert.Same(originalStream, context.Response.Body);
+ Assert.Same(responseBodyMock.Object, context.Features.Get<IHttpResponseBodyFeature>());
- context.Response.Body = originalStream;
+ context.Response.Body = replacementStream;
- Assert.Same(originalStream, context.Response.Body);
- Assert.Same(responseBodyMock.Object, context.Features.Get<IHttpResponseBodyFeature>());
+ Assert.Same(replacementStream, context.Response.Body);
+ Assert.NotSame(responseBodyMock.Object, context.Features.Get<IHttpResponseBodyFeature>());
- // The real issue was not that an OnCompleted registration existed, but that it would previously flush
- // the original response body in the OnCompleted callback after the response body was disposed.
- // However, since now there's no longer an OnCompleted registration at all, it's easier to verify that.
- // https://github.com/dotnet/aspnetcore/issues/25342
- responseMock.Verify(m => m.OnCompleted(It.IsAny<Func<object, Task>>(), It.IsAny<object>()), Times.Never);
- }
+ context.Response.Body = originalStream;
- [Fact]
- public async Task ResponseStart_CallsFeatureIfSet()
- {
- var features = new FeatureCollection();
- var mock = new Mock<IHttpResponseBodyFeature>();
- mock.Setup(o => o.StartAsync(It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
- features.Set(mock.Object);
+ Assert.Same(originalStream, context.Response.Body);
+ Assert.Same(responseBodyMock.Object, context.Features.Get<IHttpResponseBodyFeature>());
- var responseMock = new Mock<IHttpResponseFeature>();
- responseMock.Setup(o => o.HasStarted).Returns(false);
- features.Set(responseMock.Object);
+ // The real issue was not that an OnCompleted registration existed, but that it would previously flush
+ // the original response body in the OnCompleted callback after the response body was disposed.
+ // However, since now there's no longer an OnCompleted registration at all, it's easier to verify that.
+ // https://github.com/dotnet/aspnetcore/issues/25342
+ responseMock.Verify(m => m.OnCompleted(It.IsAny<Func<object, Task>>(), It.IsAny<object>()), Times.Never);
+ }
- var context = new DefaultHttpContext(features);
- await context.Response.StartAsync();
+ [Fact]
+ public async Task ResponseStart_CallsFeatureIfSet()
+ {
+ var features = new FeatureCollection();
+ var mock = new Mock<IHttpResponseBodyFeature>();
+ mock.Setup(o => o.StartAsync(It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
+ features.Set(mock.Object);
- mock.Verify(m => m.StartAsync(default), Times.Once());
- }
+ var responseMock = new Mock<IHttpResponseFeature>();
+ responseMock.Setup(o => o.HasStarted).Returns(false);
+ features.Set(responseMock.Object);
- [Fact]
- public async Task ResponseStart_CallsFeatureIfSetWithProvidedCancellationToken()
- {
- var features = new FeatureCollection();
+ var context = new DefaultHttpContext(features);
+ await context.Response.StartAsync();
- var mock = new Mock<IHttpResponseBodyFeature>();
- var ct = new CancellationToken();
- mock.Setup(o => o.StartAsync(It.Is<CancellationToken>((localCt) => localCt.Equals(ct)))).Returns(Task.CompletedTask);
- features.Set(mock.Object);
+ mock.Verify(m => m.StartAsync(default), Times.Once());
+ }
- var responseMock = new Mock<IHttpResponseFeature>();
- responseMock.Setup(o => o.HasStarted).Returns(false);
- features.Set(responseMock.Object);
+ [Fact]
+ public async Task ResponseStart_CallsFeatureIfSetWithProvidedCancellationToken()
+ {
+ var features = new FeatureCollection();
- var context = new DefaultHttpContext(features);
- await context.Response.StartAsync(ct);
+ var mock = new Mock<IHttpResponseBodyFeature>();
+ var ct = new CancellationToken();
+ mock.Setup(o => o.StartAsync(It.Is<CancellationToken>((localCt) => localCt.Equals(ct)))).Returns(Task.CompletedTask);
+ features.Set(mock.Object);
- mock.Verify(m => m.StartAsync(default), Times.Once());
- }
+ var responseMock = new Mock<IHttpResponseFeature>();
+ responseMock.Setup(o => o.HasStarted).Returns(false);
+ features.Set(responseMock.Object);
- [Fact]
- public async Task ResponseStart_DoesNotCallStartIfHasStartedIsTrue()
- {
- var features = new FeatureCollection();
+ var context = new DefaultHttpContext(features);
+ await context.Response.StartAsync(ct);
- var startMock = new Mock<IHttpResponseBodyFeature>();
- startMock.Setup(o => o.StartAsync(It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
- features.Set(startMock.Object);
+ mock.Verify(m => m.StartAsync(default), Times.Once());
+ }
- var responseMock = new Mock<IHttpResponseFeature>();
- responseMock.Setup(o => o.HasStarted).Returns(true);
- features.Set(responseMock.Object);
+ [Fact]
+ public async Task ResponseStart_DoesNotCallStartIfHasStartedIsTrue()
+ {
+ var features = new FeatureCollection();
- var context = new DefaultHttpContext(features);
- await context.Response.StartAsync();
+ var startMock = new Mock<IHttpResponseBodyFeature>();
+ startMock.Setup(o => o.StartAsync(It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
+ features.Set(startMock.Object);
- startMock.Verify(m => m.StartAsync(default), Times.Never());
- }
+ var responseMock = new Mock<IHttpResponseFeature>();
+ responseMock.Setup(o => o.HasStarted).Returns(true);
+ features.Set(responseMock.Object);
- [Fact]
- public async Task ResponseStart_CallsResponseBodyFlushIfNotSet()
- {
- var context = new DefaultHttpContext();
- var mock = new FlushAsyncCheckStream();
- context.Response.Body = mock;
+ var context = new DefaultHttpContext(features);
+ await context.Response.StartAsync();
- await context.Response.StartAsync(default);
+ startMock.Verify(m => m.StartAsync(default), Times.Never());
+ }
- Assert.True(mock.IsCalled);
- }
+ [Fact]
+ public async Task ResponseStart_CallsResponseBodyFlushIfNotSet()
+ {
+ var context = new DefaultHttpContext();
+ var mock = new FlushAsyncCheckStream();
+ context.Response.Body = mock;
- [Fact]
- public async Task RegisterForDisposeHandlesDisposeAsyncIfObjectImplementsIAsyncDisposable()
- {
- var features = new FeatureCollection();
- var response = new ResponseFeature();
- features.Set<IHttpResponseFeature>(response);
+ await context.Response.StartAsync(default);
- var context = new DefaultHttpContext(features);
- var instance = new DisposableClass();
- context.Response.RegisterForDispose(instance);
+ Assert.True(mock.IsCalled);
+ }
- await response.ExecuteOnCompletedCallbacks();
+ [Fact]
+ public async Task RegisterForDisposeHandlesDisposeAsyncIfObjectImplementsIAsyncDisposable()
+ {
+ var features = new FeatureCollection();
+ var response = new ResponseFeature();
+ features.Set<IHttpResponseFeature>(response);
- Assert.True(instance.DisposeAsyncCalled);
- Assert.False(instance.DisposeCalled);
- }
+ var context = new DefaultHttpContext(features);
+ var instance = new DisposableClass();
+ context.Response.RegisterForDispose(instance);
- public class ResponseFeature : IHttpResponseFeature
- {
- private readonly List<(Func<object, Task>, object)> _callbacks = new();
- public int StatusCode { get; set; }
- public string ReasonPhrase { get; set; }
- public IHeaderDictionary Headers { get; set; }
- public Stream Body { get; set; }
+ await response.ExecuteOnCompletedCallbacks();
- public bool HasStarted => false;
+ Assert.True(instance.DisposeAsyncCalled);
+ Assert.False(instance.DisposeCalled);
+ }
- public void OnCompleted(Func<object, Task> callback, object state)
- {
- _callbacks.Add((callback, state));
- }
+ public class ResponseFeature : IHttpResponseFeature
+ {
+ private readonly List<(Func<object, Task>, object)> _callbacks = new();
+ public int StatusCode { get; set; }
+ public string ReasonPhrase { get; set; }
+ public IHeaderDictionary Headers { get; set; }
+ public Stream Body { get; set; }
- public void OnStarting(Func<object, Task> callback, object state)
- {
- throw new NotImplementedException();
- }
+ public bool HasStarted => false;
- public async Task ExecuteOnCompletedCallbacks()
- {
- foreach (var (callback, state) in _callbacks)
- {
- await callback(state);
- }
- }
+ public void OnCompleted(Func<object, Task> callback, object state)
+ {
+ _callbacks.Add((callback, state));
}
- public class DisposableClass : IDisposable, IAsyncDisposable
+ public void OnStarting(Func<object, Task> callback, object state)
{
- public bool DisposeCalled { get; set; }
-
- public bool DisposeAsyncCalled { get; set; }
-
- public void Dispose()
- {
- DisposeCalled = true;
- }
+ throw new NotImplementedException();
+ }
- public ValueTask DisposeAsync()
+ public async Task ExecuteOnCompletedCallbacks()
+ {
+ foreach (var (callback, state) in _callbacks)
{
- DisposeAsyncCalled = true;
- return ValueTask.CompletedTask;
+ await callback(state);
}
}
+ }
+
+ public class DisposableClass : IDisposable, IAsyncDisposable
+ {
+ public bool DisposeCalled { get; set; }
- private static HttpResponse CreateResponse(IHeaderDictionary headers)
+ public bool DisposeAsyncCalled { get; set; }
+
+ public void Dispose()
{
- var context = new DefaultHttpContext();
- context.Features.Get<IHttpResponseFeature>().Headers = headers;
- return context.Response;
+ DisposeCalled = true;
}
- private static HttpResponse GetResponseWithContentLength(string contentLength = null)
+ public ValueTask DisposeAsync()
{
- return GetResponseWithHeader("Content-Length", contentLength);
+ DisposeAsyncCalled = true;
+ return ValueTask.CompletedTask;
}
+ }
+
+ 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 GetResponseWithContentType(string contentType = null)
+ private static HttpResponse GetResponseWithHeader(string headerName, string headerValue)
+ {
+ var headers = new HeaderDictionary();
+ if (headerValue != null)
{
- return GetResponseWithHeader("Content-Type", contentType);
+ headers.Add(headerName, headerValue);
}
- private static HttpResponse GetResponseWithHeader(string headerName, string headerValue)
- {
- var headers = new HeaderDictionary();
- if (headerValue != null)
- {
- headers.Add(headerName, headerValue);
- }
+ return CreateResponse(headers);
+ }
- return CreateResponse(headers);
- }
+ private class FlushAsyncCheckStream : MemoryStream
+ {
+ public bool IsCalled { get; private set; }
- private class FlushAsyncCheckStream : MemoryStream
+ public override Task FlushAsync(CancellationToken cancellationToken)
{
- public bool IsCalled { get; private set; }
-
- public override Task FlushAsync(CancellationToken cancellationToken)
- {
- IsCalled = true;
- return base.FlushAsync(cancellationToken);
- }
+ IsCalled = true;
+ return base.FlushAsync(cancellationToken);
}
}
}
diff --git a/src/Http/Http/test/Internal/ItemsDictionaryTests.cs b/src/Http/Http/test/Internal/ItemsDictionaryTests.cs
index 2ae8b61852..816adc8f1a 100644
--- a/src/Http/Http/test/Internal/ItemsDictionaryTests.cs
+++ b/src/Http/Http/test/Internal/ItemsDictionaryTests.cs
@@ -12,30 +12,30 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class ItemsDictionaryTests
{
- public class ItemsDictionaryTests
+ [Fact]
+ public void GetEnumerator_ShouldResolveWithoutNullReferenceException()
{
- [Fact]
- public void GetEnumerator_ShouldResolveWithoutNullReferenceException()
- {
- // Arrange
- var dict = new ItemsDictionary();
+ // Arrange
+ var dict = new ItemsDictionary();
- // Act and Assert
- IEnumerable en = (IEnumerable) dict;
- Assert.NotNull(en.GetEnumerator());
- }
+ // Act and Assert
+ IEnumerable en = (IEnumerable)dict;
+ Assert.NotNull(en.GetEnumerator());
+ }
- [Fact]
- public void CopyTo_ShouldCopyItemsWithoutNullReferenceException() {
- // Arrange
- var dict = new ItemsDictionary();
- var pairs = new KeyValuePair<object, object>[] { new KeyValuePair<object, object>("first", "value") };
+ [Fact]
+ public void CopyTo_ShouldCopyItemsWithoutNullReferenceException()
+ {
+ // Arrange
+ var dict = new ItemsDictionary();
+ var pairs = new KeyValuePair<object, object>[] { new KeyValuePair<object, object>("first", "value") };
- // Act and Assert
- ICollection<KeyValuePair<object, object>> cl = (ICollection<KeyValuePair<object, object>>) dict;
- cl.CopyTo(pairs, 0);
- }
+ // Act and Assert
+ ICollection<KeyValuePair<object, object>> cl = (ICollection<KeyValuePair<object, object>>)dict;
+ cl.CopyTo(pairs, 0);
}
}
diff --git a/src/Http/Http/test/Internal/ReferenceReadStreamTests.cs b/src/Http/Http/test/Internal/ReferenceReadStreamTests.cs
index 256cdeb81d..59538a29ac 100644
--- a/src/Http/Http/test/Internal/ReferenceReadStreamTests.cs
+++ b/src/Http/Http/test/Internal/ReferenceReadStreamTests.cs
@@ -7,71 +7,70 @@ using System.Threading.Tasks;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+public class ReferenceReadStreamTests
{
- public class ReferenceReadStreamTests
+ [Fact]
+ public void CanRead_ReturnsTrue()
{
- [Fact]
- public void CanRead_ReturnsTrue()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- Assert.True(stream.CanRead);
- }
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ Assert.True(stream.CanRead);
+ }
- [Fact]
- public void CanSeek_ReturnsFalse()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- Assert.False(stream.CanSeek);
- }
+ [Fact]
+ public void CanSeek_ReturnsFalse()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ Assert.False(stream.CanSeek);
+ }
- [Fact]
- public void CanWrite_ReturnsFalse()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- Assert.False(stream.CanWrite);
- }
+ [Fact]
+ public void CanWrite_ReturnsFalse()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ Assert.False(stream.CanWrite);
+ }
- [Fact]
- public void SetLength_Throws()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
- }
+ [Fact]
+ public void SetLength_Throws()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ Assert.Throws<NotSupportedException>(() => stream.SetLength(0));
+ }
- [Fact]
- public void Write_Throws()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1));
- }
+ [Fact]
+ public void Write_Throws()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ Assert.Throws<NotSupportedException>(() => stream.Write(new byte[1], 0, 1));
+ }
- [Fact]
- public void WriteByte_Throws()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- Assert.Throws<NotSupportedException>(() => stream.WriteByte(0));
- }
+ [Fact]
+ public void WriteByte_Throws()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ Assert.Throws<NotSupportedException>(() => stream.WriteByte(0));
+ }
- [Fact]
- public async Task WriteAsync_Throws()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- await Assert.ThrowsAsync<NotSupportedException>(() => stream.WriteAsync(new byte[1], 0, 1));
- }
+ [Fact]
+ public async Task WriteAsync_Throws()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ await Assert.ThrowsAsync<NotSupportedException>(() => stream.WriteAsync(new byte[1], 0, 1));
+ }
- [Fact]
- public void Flush_DoesNotThrow()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- stream.Flush();
- }
+ [Fact]
+ public void Flush_DoesNotThrow()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ stream.Flush();
+ }
- [Fact]
- public async Task FlushAsync_DoesNotThrow()
- {
- var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
- await stream.FlushAsync();
- }
+ [Fact]
+ public async Task FlushAsync_DoesNotThrow()
+ {
+ var stream = new ReferenceReadStream(Mock.Of<Stream>(), 0, 1);
+ await stream.FlushAsync();
}
}
diff --git a/src/Http/Http/test/RequestCookiesCollectionTests.cs b/src/Http/Http/test/RequestCookiesCollectionTests.cs
index 0b90f13120..0804c4b102 100644
--- a/src/Http/Http/test/RequestCookiesCollectionTests.cs
+++ b/src/Http/Http/test/RequestCookiesCollectionTests.cs
@@ -5,46 +5,45 @@ using System.Linq;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Tests
+namespace Microsoft.AspNetCore.Http.Tests;
+
+public class RequestCookiesCollectionTests
{
- public class RequestCookiesCollectionTests
+ [Theory]
+ [InlineData("key=value", "key", "value")]
+ [InlineData("key%2C=%21value", "key%2C", "!value")]
+ [InlineData("ke%23y%2C=val%5Eue", "ke%23y%2C", "val^ue")]
+ [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")]
+ [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")]
+ public void UnEscapesValues(string input, string expectedKey, string expectedValue)
+ {
+ var cookies = RequestCookieCollection.Parse(new StringValues(input));
+
+ Assert.Equal(1, cookies.Count);
+ Assert.Equal(expectedKey, cookies.Keys.Single());
+ Assert.Equal(expectedValue, cookies[expectedKey]);
+ }
+
+ [Theory]
+ [InlineData("key=value", "key", "value")]
+ [InlineData("key%2C=%21value", "key,", "!value")]
+ [InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")]
+ [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")]
+ [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")]
+ public void AppContextSwitchUnEscapesKeysAndValues(string input, string expectedKey, string expectedValue)
{
- [Theory]
- [InlineData("key=value", "key", "value")]
- [InlineData("key%2C=%21value", "key%2C", "!value")]
- [InlineData("ke%23y%2C=val%5Eue", "ke%23y%2C", "val^ue")]
- [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")]
- [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")]
- public void UnEscapesValues(string input, string expectedKey, string expectedValue)
- {
- var cookies = RequestCookieCollection.Parse(new StringValues(input));
-
- Assert.Equal(1, cookies.Count);
- Assert.Equal(expectedKey, cookies.Keys.Single());
- Assert.Equal(expectedValue, cookies[expectedKey]);
- }
-
- [Theory]
- [InlineData("key=value", "key", "value")]
- [InlineData("key%2C=%21value", "key,", "!value")]
- [InlineData("ke%23y%2C=val%5Eue", "ke#y,", "val^ue")]
- [InlineData("base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==")]
- [InlineData("base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==")]
- public void AppContextSwitchUnEscapesKeysAndValues(string input, string expectedKey, string expectedValue)
- {
- var cookies = RequestCookieCollection.ParseInternal(new StringValues(input), enableCookieNameEncoding: true);
-
- Assert.Equal(1, cookies.Count);
- Assert.Equal(expectedKey, cookies.Keys.Single());
- Assert.Equal(expectedValue, cookies[expectedKey]);
- }
-
- [Fact]
- public void ParseManyCookies()
- {
- var cookies = RequestCookieCollection.Parse(new StringValues(new[] { "a=a", "b=b", "c=c", "d=d", "e=e", "f=f", "g=g", "h=h", "i=i", "j=j", "k=k", "l=l" }));
-
- Assert.Equal(12, cookies.Count);
- }
+ var cookies = RequestCookieCollection.ParseInternal(new StringValues(input), enableCookieNameEncoding: true);
+
+ Assert.Equal(1, cookies.Count);
+ Assert.Equal(expectedKey, cookies.Keys.Single());
+ Assert.Equal(expectedValue, cookies[expectedKey]);
+ }
+
+ [Fact]
+ public void ParseManyCookies()
+ {
+ var cookies = RequestCookieCollection.Parse(new StringValues(new[] { "a=a", "b=b", "c=c", "d=d", "e=e", "f=f", "g=g", "h=h", "i=i", "j=j", "k=k", "l=l" }));
+
+ Assert.Equal(12, cookies.Count);
}
}
diff --git a/src/Http/Http/test/ResponseCookiesTest.cs b/src/Http/Http/test/ResponseCookiesTest.cs
index b29657567b..d1b1fd8274 100644
--- a/src/Http/Http/test/ResponseCookiesTest.cs
+++ b/src/Http/Http/test/ResponseCookiesTest.cs
@@ -10,242 +10,241 @@ using Microsoft.Extensions.Logging.Testing;
using Microsoft.Net.Http.Headers;
using Xunit;
-namespace Microsoft.AspNetCore.Http.Tests
+namespace Microsoft.AspNetCore.Http.Tests;
+
+public class ResponseCookiesTest
{
- public class ResponseCookiesTest
+ private IFeatureCollection MakeFeatures(IHeaderDictionary headers)
{
- private IFeatureCollection MakeFeatures(IHeaderDictionary headers)
+ var responseFeature = new HttpResponseFeature()
{
- var responseFeature = new HttpResponseFeature()
- {
- Headers = headers
- };
- var features = new FeatureCollection();
- features.Set<IHttpResponseFeature>(responseFeature);
- return features;
- }
+ Headers = headers
+ };
+ var features = new FeatureCollection();
+ features.Set<IHttpResponseFeature>(responseFeature);
+ return features;
+ }
- [Fact]
- public void AppendSameSiteNoneWithoutSecureLogsWarning()
- {
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var services = new ServiceCollection();
-
- var sink = new TestSink(TestSink.EnableWithTypeName<ResponseCookies>);
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- services.AddLogging();
- services.AddSingleton<ILoggerFactory>(loggerFactory);
-
- features.Set<IServiceProvidersFeature>(new ServiceProvidersFeature() { RequestServices = services.BuildServiceProvider() });
-
- var cookies = new ResponseCookies(features);
- var testCookie = "TestCookie";
-
- cookies.Append(testCookie, "value", new CookieOptions()
- {
- SameSite = SameSiteMode.None,
- });
-
- var cookieHeaderValues = headers.SetCookie;
- Assert.Single(cookieHeaderValues);
- Assert.StartsWith(testCookie, cookieHeaderValues[0]);
- Assert.Contains("path=/", cookieHeaderValues[0]);
- Assert.Contains("samesite=none", cookieHeaderValues[0]);
- Assert.DoesNotContain("secure", cookieHeaderValues[0]);
-
- var writeContext = Assert.Single(sink.Writes);
- Assert.Equal("The cookie 'TestCookie' has set 'SameSite=None' and must also set 'Secure'.", writeContext.Message);
- }
+ [Fact]
+ public void AppendSameSiteNoneWithoutSecureLogsWarning()
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var services = new ServiceCollection();
- [Fact]
- public void DeleteCookieShouldSetDefaultPath()
- {
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
- var testCookie = "TestCookie";
-
- cookies.Delete(testCookie);
-
- var cookieHeaderValues = headers.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]);
- }
+ var sink = new TestSink(TestSink.EnableWithTypeName<ResponseCookies>);
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ services.AddLogging();
+ services.AddSingleton<ILoggerFactory>(loggerFactory);
+
+ features.Set<IServiceProvidersFeature>(new ServiceProvidersFeature() { RequestServices = services.BuildServiceProvider() });
- [Fact]
- public void DeleteCookieWithDomainAndPathDeletesPriorMatchingCookies()
+ var cookies = new ResponseCookies(features);
+ var testCookie = "TestCookie";
+
+ cookies.Append(testCookie, "value", new CookieOptions()
{
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var responseCookies = new ResponseCookies(features);
+ SameSite = SameSiteMode.None,
+ });
+
+ var cookieHeaderValues = headers.SetCookie;
+ Assert.Single(cookieHeaderValues);
+ Assert.StartsWith(testCookie, cookieHeaderValues[0]);
+ Assert.Contains("path=/", cookieHeaderValues[0]);
+ Assert.Contains("samesite=none", cookieHeaderValues[0]);
+ Assert.DoesNotContain("secure", cookieHeaderValues[0]);
+
+ var writeContext = Assert.Single(sink.Writes);
+ Assert.Equal("The cookie 'TestCookie' has set 'SameSite=None' and must also set 'Secure'.", writeContext.Message);
+ }
+
+ [Fact]
+ public void DeleteCookieShouldSetDefaultPath()
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
+ var testCookie = "TestCookie";
+
+ cookies.Delete(testCookie);
+
+ var cookieHeaderValues = headers.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 DeleteCookieWithDomainAndPathDeletesPriorMatchingCookies()
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var responseCookies = new ResponseCookies(features);
- var testCookies = new (string Key, string Path, string Domain)[]
- {
+ var testCookies = new (string Key, string Path, string Domain)[]
+ {
new ("key1", "/path1/", null),
new ("key1", "/path2/", null),
new ("key2", "/path1/", "localhost"),
new ("key2", "/path2/", "localhost"),
- };
+ };
+
+ foreach (var cookie in testCookies)
+ {
+ responseCookies.Delete(cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path });
+ }
- foreach (var cookie in testCookies)
- {
- responseCookies.Delete(cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path });
- }
+ var deletedCookies = headers.SetCookie.ToArray();
+ Assert.Equal(testCookies.Length, deletedCookies.Length);
- var deletedCookies = headers.SetCookie.ToArray();
- Assert.Equal(testCookies.Length, deletedCookies.Length);
+ Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/"));
+ Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path2/"));
+ Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/") && cookie.Contains("domain=localhost"));
+ Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path2/") && cookie.Contains("domain=localhost"));
+ Assert.All(deletedCookies, cookie => Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookie));
+ }
- Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/"));
- Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path2/"));
- Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/") && cookie.Contains("domain=localhost"));
- Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path2/") && cookie.Contains("domain=localhost"));
- Assert.All(deletedCookies, cookie => Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookie));
- }
+ [Fact]
+ public void DeleteRemovesCookieWithDomainAndPathCreatedByAdd()
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var responseCookies = new ResponseCookies(features);
- [Fact]
- public void DeleteRemovesCookieWithDomainAndPathCreatedByAdd()
+ var testCookies = new (string Key, string Path, string Domain)[]
{
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var responseCookies = new ResponseCookies(features);
-
- var testCookies = new (string Key, string Path, string Domain)[]
- {
new ("key1", "/path1/", null),
new ("key1", "/path1/", null),
new ("key2", "/path1/", "localhost"),
new ("key2", "/path1/", "localhost"),
- };
-
- foreach (var cookie in testCookies)
- {
- responseCookies.Append(cookie.Key, cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path });
- responseCookies.Delete(cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path });
- }
-
- var deletedCookies = headers.SetCookie.ToArray();
- Assert.Equal(2, deletedCookies.Length);
- Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/"));
- Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/") && cookie.Contains("domain=localhost"));
- Assert.All(deletedCookies, cookie => Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookie));
- }
+ };
- [Fact]
- public void DeleteCookieWithCookieOptionsShouldKeepPropertiesOfCookieOptions()
+ foreach (var cookie in testCookies)
{
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
- 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.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]);
+ responseCookies.Append(cookie.Key, cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path });
+ responseCookies.Delete(cookie.Key, new CookieOptions() { Domain = cookie.Domain, Path = cookie.Path });
}
- [Fact]
- public void NoParamsDeleteRemovesCookieCreatedByAdd()
- {
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
- var testCookie = "TestCookie";
-
- cookies.Append(testCookie, testCookie);
- cookies.Delete(testCookie);
-
- var cookieHeaderValues = headers.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]);
- }
+ var deletedCookies = headers.SetCookie.ToArray();
+ Assert.Equal(2, deletedCookies.Length);
+ Assert.Single(deletedCookies, cookie => cookie.StartsWith("key1", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/"));
+ Assert.Single(deletedCookies, cookie => cookie.StartsWith("key2", StringComparison.InvariantCulture) && cookie.Contains("path=/path1/") && cookie.Contains("domain=localhost"));
+ Assert.All(deletedCookies, cookie => Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookie));
+ }
- [Fact]
- public void ProvidesMaxAgeWithCookieOptionsArgumentExpectMaxAgeToBeSet()
+ [Fact]
+ public void DeleteCookieWithCookieOptionsShouldKeepPropertiesOfCookieOptions()
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
+ var testCookie = "TestCookie";
+ var time = new DateTimeOffset(2000, 1, 1, 1, 1, 1, 1, TimeSpan.Zero);
+ var options = new CookieOptions
{
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
- 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.SetCookie;
- Assert.Single(cookieHeaderValues);
- Assert.Contains($"max-age={maxAgeTime.TotalSeconds}", cookieHeaderValues[0]);
- }
+ Secure = true,
+ HttpOnly = true,
+ Path = "/",
+ Expires = time,
+ Domain = "example.com",
+ SameSite = SameSiteMode.Lax
+ };
+
+ cookies.Delete(testCookie, options);
+
+ var cookieHeaderValues = headers.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]);
+ }
- [Theory]
- [InlineData("value", "key=value")]
- [InlineData("!value", "key=%21value")]
- [InlineData("val^ue", "key=val%5Eue")]
- [InlineData("QUI+REU/Rw==", "key=QUI%2BREU%2FRw%3D%3D")]
- public void EscapesValuesBeforeSettingCookie(string value, string expected)
- {
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
+ [Fact]
+ public void NoParamsDeleteRemovesCookieCreatedByAdd()
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
+ var testCookie = "TestCookie";
+
+ cookies.Append(testCookie, testCookie);
+ cookies.Delete(testCookie);
+
+ var cookieHeaderValues = headers.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 = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
+ 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.SetCookie;
+ Assert.Single(cookieHeaderValues);
+ Assert.Contains($"max-age={maxAgeTime.TotalSeconds}", cookieHeaderValues[0]);
+ }
- cookies.Append("key", value);
+ [Theory]
+ [InlineData("value", "key=value")]
+ [InlineData("!value", "key=%21value")]
+ [InlineData("val^ue", "key=val%5Eue")]
+ [InlineData("QUI+REU/Rw==", "key=QUI%2BREU%2FRw%3D%3D")]
+ public void EscapesValuesBeforeSettingCookie(string value, string expected)
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
- var cookieHeaderValues = headers.SetCookie;
- Assert.Single(cookieHeaderValues);
- Assert.StartsWith(expected, cookieHeaderValues[0]);
- }
+ cookies.Append("key", value);
- [Theory]
- [InlineData("key,")]
- [InlineData("ke@y")]
- public void InvalidKeysThrow(string key)
- {
- var headers = new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
+ var cookieHeaderValues = headers.SetCookie;
+ Assert.Single(cookieHeaderValues);
+ Assert.StartsWith(expected, cookieHeaderValues[0]);
+ }
- Assert.Throws<ArgumentException>(() => cookies.Append(key, "1"));
- }
+ [Theory]
+ [InlineData("key,")]
+ [InlineData("ke@y")]
+ public void InvalidKeysThrow(string key)
+ {
+ var headers = new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
- [Theory]
- [InlineData("key", "value", "key=value")]
- [InlineData("key,", "!value", "key%2C=%21value")]
- [InlineData("ke#y,", "val^ue", "ke%23y%2C=val%5Eue")]
- [InlineData("base64", "QUI+REU/Rw==", "base64=QUI%2BREU%2FRw%3D%3D")]
- public void AppContextSwitchEscapesKeysAndValuesBeforeSettingCookie(string key, string value, string expected)
- {
- var headers = (IHeaderDictionary)new HeaderDictionary();
- var features = MakeFeatures(headers);
- var cookies = new ResponseCookies(features);
- cookies._enableCookieNameEncoding = true;
+ Assert.Throws<ArgumentException>(() => cookies.Append(key, "1"));
+ }
- cookies.Append(key, value);
+ [Theory]
+ [InlineData("key", "value", "key=value")]
+ [InlineData("key,", "!value", "key%2C=%21value")]
+ [InlineData("ke#y,", "val^ue", "ke%23y%2C=val%5Eue")]
+ [InlineData("base64", "QUI+REU/Rw==", "base64=QUI%2BREU%2FRw%3D%3D")]
+ public void AppContextSwitchEscapesKeysAndValuesBeforeSettingCookie(string key, string value, string expected)
+ {
+ var headers = (IHeaderDictionary)new HeaderDictionary();
+ var features = MakeFeatures(headers);
+ var cookies = new ResponseCookies(features);
+ cookies._enableCookieNameEncoding = true;
- var cookieHeaderValues = headers.SetCookie;
- Assert.Single(cookieHeaderValues);
- Assert.StartsWith(expected, cookieHeaderValues[0]);
- }
+ cookies.Append(key, value);
+
+ var cookieHeaderValues = headers.SetCookie;
+ Assert.Single(cookieHeaderValues);
+ Assert.StartsWith(expected, cookieHeaderValues[0]);
}
}
diff --git a/src/Http/Metadata/src/IAllowAnonymous.cs b/src/Http/Metadata/src/IAllowAnonymous.cs
index ab48cb3dbf..7d62876aea 100644
--- a/src/Http/Metadata/src/IAllowAnonymous.cs
+++ b/src/Http/Metadata/src/IAllowAnonymous.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Authorization
+namespace Microsoft.AspNetCore.Authorization;
+
+/// <summary>
+/// Marker interface to allow access to anonymous users.
+/// </summary>
+public interface IAllowAnonymous
{
- /// <summary>
- /// Marker interface to allow access to anonymous users.
- /// </summary>
- public interface IAllowAnonymous
- {
- }
}
diff --git a/src/Http/Metadata/src/IAuthorizeData.cs b/src/Http/Metadata/src/IAuthorizeData.cs
index 13b586f9f5..d3c3e9effc 100644
--- a/src/Http/Metadata/src/IAuthorizeData.cs
+++ b/src/Http/Metadata/src/IAuthorizeData.cs
@@ -1,26 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Authorization
+namespace Microsoft.AspNetCore.Authorization;
+
+/// <summary>
+/// Defines the set of data required to apply authorization rules to a resource.
+/// </summary>
+public interface IAuthorizeData
{
/// <summary>
- /// Defines the set of data required to apply authorization rules to a resource.
+ /// Gets or sets the policy name that determines access to the resource.
/// </summary>
- public interface IAuthorizeData
- {
- /// <summary>
- /// Gets or sets the policy name that determines access to the resource.
- /// </summary>
- string? Policy { get; set; }
+ string? Policy { get; set; }
- /// <summary>
- /// Gets or sets a comma delimited list of roles that are allowed to access the resource.
- /// </summary>
- string? Roles { get; set; }
+ /// <summary>
+ /// Gets or sets a comma delimited list of roles that are allowed to access the resource.
+ /// </summary>
+ string? Roles { get; set; }
- /// <summary>
- /// Gets or sets a comma delimited list of schemes from which user information is constructed.
- /// </summary>
- string? AuthenticationSchemes { get; set; }
- }
+ /// <summary>
+ /// Gets or sets a comma delimited list of schemes from which user information is constructed.
+ /// </summary>
+ string? AuthenticationSchemes { get; set; }
}
diff --git a/src/Http/Owin/src/DictionaryStringArrayWrapper.cs b/src/Http/Owin/src/DictionaryStringArrayWrapper.cs
index 732078d517..8798003848 100644
--- a/src/Http/Owin/src/DictionaryStringArrayWrapper.cs
+++ b/src/Http/Owin/src/DictionaryStringArrayWrapper.cs
@@ -7,75 +7,74 @@ using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+internal class DictionaryStringArrayWrapper : IDictionary<string, string[]>
{
- internal class DictionaryStringArrayWrapper : IDictionary<string, string[]>
+ public DictionaryStringArrayWrapper(IHeaderDictionary inner)
{
- public DictionaryStringArrayWrapper(IHeaderDictionary inner)
- {
- Inner = inner;
- }
+ Inner = inner;
+ }
- public readonly IHeaderDictionary 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, 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 KeyValuePair<string, string[]> Convert(KeyValuePair<string, StringValues> item) => new KeyValuePair<string, string[]>(item.Key, item.Value);
- private StringValues Convert(string[] item) => item;
+ private StringValues Convert(string[] item) => item;
- private string[] Convert(StringValues 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; }
- }
+ 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;
+ int ICollection<KeyValuePair<string, string[]>>.Count => Inner.Count;
- bool ICollection<KeyValuePair<string, string[]>>.IsReadOnly => Inner.IsReadOnly;
+ bool ICollection<KeyValuePair<string, string[]>>.IsReadOnly => Inner.IsReadOnly;
- ICollection<string> IDictionary<string, string[]>.Keys => Inner.Keys;
+ ICollection<string> IDictionary<string, string[]>.Keys => Inner.Keys;
- ICollection<string[]> IDictionary<string, string[]>.Values => Inner.Values.Select(Convert).ToList();
+ 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 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 IDictionary<string, string[]>.Add(string key, string[] value) => Inner.Add(key, value);
- void ICollection<KeyValuePair<string, string[]>>.Clear() => Inner.Clear();
+ void ICollection<KeyValuePair<string, string[]>>.Clear() => Inner.Clear();
- bool ICollection<KeyValuePair<string, string[]>>.Contains(KeyValuePair<string, string[]> item) => Inner.Contains(Convert(item));
+ bool ICollection<KeyValuePair<string, string[]>>.Contains(KeyValuePair<string, string[]> item) => Inner.Contains(Convert(item));
- bool IDictionary<string, string[]>.ContainsKey(string key) => Inner.ContainsKey(key);
+ bool IDictionary<string, string[]>.ContainsKey(string key) => Inner.ContainsKey(key);
- void ICollection<KeyValuePair<string, string[]>>.CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
+ void ICollection<KeyValuePair<string, string[]>>.CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
+ {
+ foreach (var kv in Inner)
{
- foreach(var kv in Inner)
- {
- array[arrayIndex++] = Convert(kv);
- }
+ array[arrayIndex++] = Convert(kv);
}
+ }
- IEnumerator IEnumerable.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
- IEnumerator<KeyValuePair<string, string[]>> IEnumerable<KeyValuePair<string, string[]>>.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 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[]>.Remove(string key) => Inner.Remove(key);
- bool IDictionary<string, string[]>.TryGetValue(string key, out string[] value)
+ bool IDictionary<string, string[]>.TryGetValue(string key, out string[] value)
+ {
+ StringValues temp;
+ if (Inner.TryGetValue(key, out temp))
{
- StringValues temp;
- if (Inner.TryGetValue(key, out temp))
- {
- value = temp;
- return true;
- }
- value = default(StringValues);
- return false;
+ 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
index 6c3ae81209..673e41b784 100644
--- a/src/Http/Owin/src/DictionaryStringValuesWrapper.cs
+++ b/src/Http/Owin/src/DictionaryStringValuesWrapper.cs
@@ -8,119 +8,118 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+internal class DictionaryStringValuesWrapper : IHeaderDictionary
{
- internal class DictionaryStringValuesWrapper : IHeaderDictionary
+ public DictionaryStringValuesWrapper(IDictionary<string, string[]> inner)
{
- public DictionaryStringValuesWrapper(IDictionary<string, string[]> inner)
- {
- Inner = inner;
- }
+ Inner = inner;
+ }
- public readonly IDictionary<string, string[]> 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, 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 KeyValuePair<string, string[]> Convert(KeyValuePair<string, StringValues> item) => new KeyValuePair<string, string[]>(item.Key, item.Value);
- private StringValues Convert(string[] item) => item;
+ private StringValues Convert(string[] item) => item;
- private string[] Convert(StringValues item) => item;
+ private string[] Convert(StringValues item) => item;
- StringValues IHeaderDictionary.this[string key]
+ StringValues IHeaderDictionary.this[string key]
+ {
+ get
{
- get
- {
- string[] values;
- return Inner.TryGetValue(key, out values) ? values : null;
- }
- set { Inner[key] = value; }
+ 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; }
- }
+ StringValues IDictionary<string, StringValues>.this[string key]
+ {
+ get { return Inner[key]; }
+ set { Inner[key] = value; }
+ }
- public long? ContentLength
+ public long? ContentLength
+ {
+ get
{
- get
- {
- long value;
+ long value;
- string[] rawValue;
- if (!Inner.TryGetValue(HeaderNames.ContentLength, out rawValue))
- {
- return null;
- }
+ 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;
- }
+ if (rawValue.Length == 1 &&
+ !string.IsNullOrEmpty(rawValue[0]) &&
+ HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
+ {
+ return value;
+ }
- return null;
+ return null;
+ }
+ set
+ {
+ if (value.HasValue)
+ {
+ Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatNonNegativeInt64(value.GetValueOrDefault());
}
- set
+ else
{
- if (value.HasValue)
- {
- Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatNonNegativeInt64(value.GetValueOrDefault());
- }
- else
- {
- Inner.Remove(HeaderNames.ContentLength);
- }
+ Inner.Remove(HeaderNames.ContentLength);
}
}
+ }
- int ICollection<KeyValuePair<string, StringValues>>.Count => Inner.Count;
+ int ICollection<KeyValuePair<string, StringValues>>.Count => Inner.Count;
- bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly => Inner.IsReadOnly;
+ bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly => Inner.IsReadOnly;
- ICollection<string> IDictionary<string, StringValues>.Keys => Inner.Keys;
+ ICollection<string> IDictionary<string, StringValues>.Keys => Inner.Keys;
- ICollection<StringValues> IDictionary<string, StringValues>.Values => Inner.Values.Select(Convert).ToList();
+ 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 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 IDictionary<string, StringValues>.Add(string key, StringValues value) => Inner.Add(key, value);
- void ICollection<KeyValuePair<string, StringValues>>.Clear() => Inner.Clear();
+ void ICollection<KeyValuePair<string, StringValues>>.Clear() => Inner.Clear();
- bool ICollection<KeyValuePair<string, StringValues>>.Contains(KeyValuePair<string, StringValues> item) => Inner.Contains(Convert(item));
+ bool ICollection<KeyValuePair<string, StringValues>>.Contains(KeyValuePair<string, StringValues> item) => Inner.Contains(Convert(item));
- bool IDictionary<string, StringValues>.ContainsKey(string key) => Inner.ContainsKey(key);
+ bool IDictionary<string, StringValues>.ContainsKey(string key) => Inner.ContainsKey(key);
- void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
+ void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
+ {
+ foreach (var kv in Inner)
{
- foreach (var kv in Inner)
- {
- array[arrayIndex++] = Convert(kv);
- }
+ array[arrayIndex++] = Convert(kv);
}
+ }
- IEnumerator IEnumerable.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
- IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.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 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>.Remove(string key) => Inner.Remove(key);
- bool IDictionary<string, StringValues>.TryGetValue(string key, out StringValues value)
+ bool IDictionary<string, StringValues>.TryGetValue(string key, out StringValues value)
+ {
+ string[] temp;
+ if (Inner.TryGetValue(key, out temp))
{
- string[] temp;
- if (Inner.TryGetValue(key, out temp))
- {
- value = temp;
- return true;
- }
- value = default(StringValues);
- return false;
+ 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
index 0d33392a8f..8eb6a816fc 100644
--- a/src/Http/Owin/src/IOwinEnvironmentFeature.cs
+++ b/src/Http/Owin/src/IOwinEnvironmentFeature.cs
@@ -3,16 +3,15 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+/// <summary>
+/// A feature interface for an OWIN environment.
+/// </summary>
+public interface IOwinEnvironmentFeature
{
/// <summary>
- /// A feature interface for an OWIN environment.
+ /// Gets or sets the environment values.
/// </summary>
- public interface IOwinEnvironmentFeature
- {
- /// <summary>
- /// Gets or sets the environment values.
- /// </summary>
- IDictionary<string, object> Environment { get; set; }
- }
+ IDictionary<string, object> Environment { get; set; }
}
diff --git a/src/Http/Owin/src/OwinConstants.cs b/src/Http/Owin/src/OwinConstants.cs
index 1c6aa5167c..b00d5a73d7 100644
--- a/src/Http/Owin/src/OwinConstants.cs
+++ b/src/Http/Owin/src/OwinConstants.cs
@@ -1,177 +1,176 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Owin
-{
- internal static class OwinConstants
- {
- #region OWIN v1.0.0 - 3.2.1. Request Data
+namespace Microsoft.AspNetCore.Owin;
- // http://owin.org/spec/spec/owin-1.0.0.html
+internal static class OwinConstants
+{
+ #region OWIN v1.0.0 - 3.2.1. Request Data
- 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";
+ // http://owin.org/spec/spec/owin-1.0.0.html
- #endregion
+ 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";
- #region OWIN v1.0.1 - 3.2.1 Request Data
+ #endregion
- // OWIN 1.0.1 http://owin.org/html/owin.html
+ #region OWIN v1.0.1 - 3.2.1 Request Data
- public const string RequestId = "owin.RequestId";
- public const string RequestUser = "owin.RequestUser";
+ // OWIN 1.0.1 http://owin.org/html/owin.html
- #endregion
+ public const string RequestId = "owin.RequestId";
+ public const string RequestUser = "owin.RequestUser";
- #region OWIN v1.0.0 - 3.2.2. Response Data
+ #endregion
- // http://owin.org/spec/spec/owin-1.0.0.html
+ #region OWIN v1.0.0 - 3.2.2. Response Data
- 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";
+ // http://owin.org/spec/spec/owin-1.0.0.html
- #endregion
+ 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";
- #region OWIN v1.0.0 - 3.2.3. Other Data
+ #endregion
- // http://owin.org/spec/spec/owin-1.0.0.html
+ #region OWIN v1.0.0 - 3.2.3. Other Data
- public const string CallCancelled = "owin.CallCancelled";
+ // http://owin.org/spec/spec/owin-1.0.0.html
- public const string OwinVersion = "owin.Version";
+ public const string CallCancelled = "owin.CallCancelled";
- #endregion
+ public const string OwinVersion = "owin.Version";
- #region OWIN Keys for IAppBuilder.Properties
+ #endregion
- internal static class Builder
- {
- public const string AddSignatureConversion = "builder.AddSignatureConversion";
- public const string DefaultApp = "builder.DefaultApp";
- }
+ #region OWIN Keys for IAppBuilder.Properties
- #endregion
+ internal static class Builder
+ {
+ public const string AddSignatureConversion = "builder.AddSignatureConversion";
+ public const string DefaultApp = "builder.DefaultApp";
+ }
- #region OWIN Key Guidelines and Common Keys - 6. Common keys
+ #endregion
- // http://owin.org/spec/spec/CommonKeys.html
+ #region OWIN Key Guidelines and Common Keys - 6. Common keys
- 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";
- }
+ // http://owin.org/spec/spec/CommonKeys.html
- #endregion
+ 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";
+ }
- #region SendFiles v0.3.0
+ #endregion
- // http://owin.org/spec/extensions/owin-SendFile-Extension-v0.3.0.htm
+ #region SendFiles v0.3.0
- internal static class SendFiles
- {
- // 3.1. Startup
+ // http://owin.org/spec/extensions/owin-SendFile-Extension-v0.3.0.htm
- public const string Version = "sendfile.Version";
- public const string Support = "sendfile.Support";
- public const string Concurrency = "sendfile.Concurrency";
+ internal static class SendFiles
+ {
+ // 3.1. Startup
- // 3.2. Per Request
+ public const string Version = "sendfile.Version";
+ public const string Support = "sendfile.Support";
+ public const string Concurrency = "sendfile.Concurrency";
- public const string SendAsync = "sendfile.SendAsync";
- }
+ // 3.2. Per Request
- #endregion
+ public const string SendAsync = "sendfile.SendAsync";
+ }
- #region Opaque v0.3.0
+ #endregion
- // http://owin.org/spec/extensions/owin-OpaqueStream-Extension-v0.3.0.htm
+ #region Opaque v0.3.0
- internal static class OpaqueConstants
- {
- // 3.1. Startup
+ // http://owin.org/spec/extensions/owin-OpaqueStream-Extension-v0.3.0.htm
- public const string Version = "opaque.Version";
+ internal static class OpaqueConstants
+ {
+ // 3.1. Startup
- // 3.2. Per Request
+ public const string Version = "opaque.Version";
- public const string Upgrade = "opaque.Upgrade";
+ // 3.2. Per Request
- // 5. Consumption
+ public const string Upgrade = "opaque.Upgrade";
- public const string Stream = "opaque.Stream";
- // public const string Version = "opaque.Version"; // redundant, declared above
- public const string CallCancelled = "opaque.CallCancelled";
- }
+ // 5. Consumption
- #endregion
+ public const string Stream = "opaque.Stream";
+ // public const string Version = "opaque.Version"; // redundant, declared above
+ public const string CallCancelled = "opaque.CallCancelled";
+ }
- #region WebSocket v0.4.0
+ #endregion
- // http://owin.org/spec/extensions/owin-OpaqueStream-Extension-v0.3.0.htm
+ #region WebSocket v0.4.0
- internal static class WebSocket
- {
- // 3.1. Startup
+ // http://owin.org/spec/extensions/owin-OpaqueStream-Extension-v0.3.0.htm
- public const string Version = "websocket.Version";
- public const string VersionValue = "1.0";
+ internal static class WebSocket
+ {
+ // 3.1. Startup
- // 3.2. Per Request
+ public const string Version = "websocket.Version";
+ public const string VersionValue = "1.0";
- public const string Accept = "websocket.Accept";
- public const string AcceptAlt = "websocket.AcceptAlt"; // Non-spec
+ // 3.2. Per Request
- // 4. Accept
+ public const string Accept = "websocket.Accept";
+ public const string AcceptAlt = "websocket.AcceptAlt"; // Non-spec
- public const string SubProtocol = "websocket.SubProtocol";
+ // 4. Accept
- // 5. Consumption
+ public const string SubProtocol = "websocket.SubProtocol";
- 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";
- }
+ // 5. Consumption
- #endregion
+ 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";
+ }
- #region Security v0.1.0
+ #endregion
- internal static class Security
- {
- // 3.2. Per Request
+ #region Security v0.1.0
- public const string User = "server.User";
- }
+ internal static class Security
+ {
+ // 3.2. Per Request
- #endregion
+ public const string User = "server.User";
}
+
+ #endregion
}
diff --git a/src/Http/Owin/src/OwinEnvironment.cs b/src/Http/Owin/src/OwinEnvironment.cs
index 1edaad5819..70f9fab224 100644
--- a/src/Http/Owin/src/OwinEnvironment.cs
+++ b/src/Http/Owin/src/OwinEnvironment.cs
@@ -18,41 +18,41 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
+using WebSocketAcceptAlt =
+ Func
+ <
+ WebSocketAcceptContext, // WebSocket Accept parameters
+ Task<WebSocket>
+ >;
+
+/// <summary>
+/// A loosely-typed OWIN environment wrapper over an <see cref="HttpContext"/>.
+/// </summary>
+public class OwinEnvironment : IDictionary<string, object>
{
- using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
- using WebSocketAcceptAlt =
- Func
- <
- WebSocketAcceptContext, // WebSocket Accept parameters
- Task<WebSocket>
- >;
+ private readonly HttpContext _context;
+ private readonly IDictionary<string, FeatureMap> _entries;
/// <summary>
- /// A loosely-typed OWIN environment wrapper over an <see cref="HttpContext"/>.
+ /// Initializes a new instance of <see cref="OwinEnvironment"/>.
/// </summary>
- public class OwinEnvironment : IDictionary<string, object>
+ /// <param name="context">The request context.</param>
+ public OwinEnvironment(HttpContext context)
{
- private readonly HttpContext _context;
- private readonly IDictionary<string, FeatureMap> _entries;
-
- /// <summary>
- /// Initializes a new instance of <see cref="OwinEnvironment"/>.
- /// </summary>
- /// <param name="context">The request context.</param>
- public OwinEnvironment(HttpContext context)
+ if (context.Features.Get<IHttpRequestFeature>() == null)
{
- 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));
- }
+ 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>()
+ _context = context;
+ _entries = new Dictionary<string, FeatureMap>()
{
{ OwinConstants.RequestProtocol, new FeatureMap<IHttpRequestFeature>(feature => feature.Protocol, () => string.Empty, (feature, value) => feature.Protocol = Convert.ToString(value, CultureInfo.InvariantCulture)) },
{ OwinConstants.RequestScheme, new FeatureMap<IHttpRequestFeature>(feature => feature.Scheme, () => string.Empty, (feature, value) => feature.Scheme = Convert.ToString(value, CultureInfo.InvariantCulture)) },
@@ -105,374 +105,373 @@ namespace Microsoft.AspNetCore.Owin
}
};
- // 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))));
- }
+ // 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;
+ }
- if (context.WebSockets.IsWebSocketRequest)
- {
- _entries.Add(OwinConstants.WebSocket.AcceptAlt, new FeatureMap<IHttpWebSocketFeature>(feature => new WebSocketAcceptAlt(feature.AcceptAsync)));
- }
+ // owin.Version is required.
+ if (!context.Items.ContainsKey(OwinConstants.OwinVersion))
+ {
+ _context.Items[OwinConstants.OwinVersion] = "1.0";
+ }
- _context.Items[typeof(HttpContext).FullName] = _context; // Store for lookup when we transition back out of OWIN
+ 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))));
}
- // Public in case there's a new/custom feature interface that needs to be added.
- /// <summary>
- /// Get the environment's feature maps.
- /// </summary>
- public IDictionary<string, FeatureMap> FeatureMaps
+ if (context.WebSockets.IsWebSocketRequest)
{
- get { return _entries; }
+ _entries.Add(OwinConstants.WebSocket.AcceptAlt, new FeatureMap<IHttpWebSocketFeature>(feature => new WebSocketAcceptAlt(feature.AcceptAsync)));
}
- void IDictionary<string, object>.Add(string key, object value)
+ _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.
+ /// <summary>
+ /// Get the environment's feature maps.
+ /// </summary>
+ public IDictionary<string, FeatureMap> FeatureMaps
+ {
+ get { return _entries; }
+ }
+
+ void IDictionary<string, object>.Add(string key, object value)
+ {
+ if (_entries.ContainsKey(key))
{
- if (_entries.ContainsKey(key))
- {
- throw new InvalidOperationException("Key already present");
- }
- _context.Items.Add(key, value);
+ throw new InvalidOperationException("Key already present");
}
+ _context.Items.Add(key, value);
+ }
- bool IDictionary<string, object>.ContainsKey(string key)
+ 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 ((IDictionary<string, object>)this).TryGetValue(key, out value);
+ return _entries.Where(pair => pair.Value.TryGet(_context, out value))
+ .Select(pair => pair.Key).Concat(_context.Items.Keys.Select(key => Convert.ToString(key, CultureInfo.InvariantCulture))).ToList();
}
+ }
- ICollection<string> IDictionary<string, object>.Keys
+ bool IDictionary<string, object>.Remove(string key)
+ {
+ if (_entries.Remove(key))
{
- 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, CultureInfo.InvariantCulture))).ToList();
- }
+ return true;
}
+ return _context.Items.Remove(key);
+ }
- bool IDictionary<string, object>.Remove(string key)
+ bool IDictionary<string, object>.TryGetValue(string key, out object value)
+ {
+ FeatureMap entry;
+ if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
{
- if (_entries.Remove(key))
- {
- return true;
- }
- return _context.Items.Remove(key);
+ return true;
}
+ return _context.Items.TryGetValue(key, out value);
+ }
+
+ ICollection<object> IDictionary<string, object>.Values
+ {
+ get { throw new NotImplementedException(); }
+ }
- bool IDictionary<string, object>.TryGetValue(string key, out object value)
+ object IDictionary<string, object>.this[string key]
+ {
+ get
{
FeatureMap entry;
+ object value;
if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
{
- return true;
+ return value;
}
- return _context.Items.TryGetValue(key, out value);
- }
-
- ICollection<object> IDictionary<string, object>.Values
- {
- get { throw new NotImplementedException(); }
+ if (_context.Items.TryGetValue(key, out value))
+ {
+ return value;
+ }
+ throw new KeyNotFoundException(key);
}
-
- object IDictionary<string, object>.this[string key]
+ set
{
- get
+ FeatureMap entry;
+ if (_entries.TryGetValue(key, out entry))
{
- FeatureMap entry;
- object value;
- if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
+ if (entry.CanSet)
{
- return value;
+ entry.Set(_context, value);
}
- if (_context.Items.TryGetValue(key, out value))
+ else
{
- return value;
+ _entries.Remove(key);
+ if (value != null)
+ {
+ _context.Items[key] = value;
+ }
}
- throw new KeyNotFoundException(key);
}
- set
+ else
{
- FeatureMap entry;
- if (_entries.TryGetValue(key, out entry))
+ if (value == null)
{
- if (entry.CanSet)
- {
- entry.Set(_context, value);
- }
- else
- {
- _entries.Remove(key);
- if (value != null)
- {
- _context.Items[key] = value;
- }
- }
+ _context.Items.Remove(key);
}
else
{
- if (value == null)
- {
- _context.Items.Remove(key);
- }
- else
- {
- _context.Items[key] = value;
- }
+ _context.Items[key] = value;
}
}
}
+ }
- void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
- {
- throw new NotImplementedException();
- }
+ void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
+ {
+ throw new NotImplementedException();
+ }
- void ICollection<KeyValuePair<string, object>>.Clear()
- {
- _entries.Clear();
- _context.Items.Clear();
- }
+ void ICollection<KeyValuePair<string, object>>.Clear()
+ {
+ _entries.Clear();
+ _context.Items.Clear();
+ }
- bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
- {
- throw new NotImplementedException();
- }
+ 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)
+ 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();
+ }
+
+ /// <inheritdoc />
+ public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ {
+ foreach (var entryPair in _entries)
{
- throw new NotImplementedException();
+ object value;
+ if (entryPair.Value.TryGet(_context, out value))
+ {
+ yield return new KeyValuePair<string, object>(entryPair.Key, value);
+ }
}
-
- int ICollection<KeyValuePair<string, object>>.Count
+ foreach (var entryPair in _context.Items)
{
- get { return _entries.Count + _context.Items.Count; }
+ yield return new KeyValuePair<string, object>(Convert.ToString(entryPair.Key, CultureInfo.InvariantCulture), entryPair.Value);
}
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
- bool ICollection<KeyValuePair<string, object>>.IsReadOnly
+ /// <summary>
+ /// Maps OWIN keys to ASP.NET Core features.
+ /// </summary>
+ public class FeatureMap
+ {
+ /// <summary>
+ /// Create a <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="featureInterface">The feature interface type.</param>
+ /// <param name="getter">Value getter.</param>
+ public FeatureMap(Type featureInterface, Func<object, object> getter)
+ : this(featureInterface, getter, defaultFactory: null)
{
- get { return false; }
}
- bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="featureInterface">The feature interface type.</param>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="defaultFactory">Default value factory delegate.</param>
+ public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory)
+ : this(featureInterface, getter, defaultFactory, setter: null)
{
- throw new NotImplementedException();
}
- /// <inheritdoc />
- public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="featureInterface">The feature interface type.</param>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="setter">Value setter delegate.</param>
+ public FeatureMap(Type featureInterface, Func<object, object> getter, Action<object, object> setter)
+ : this(featureInterface, getter, defaultFactory: null, setter: setter)
{
- 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, CultureInfo.InvariantCulture), entryPair.Value);
- }
}
- IEnumerator IEnumerable.GetEnumerator()
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="featureInterface">The feature interface type.</param>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="defaultFactory">Default value factory delegate.</param>
+ /// <param name="setter">Value setter delegate.</param>
+ public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter)
+ : this(featureInterface, getter, defaultFactory, setter, featureFactory: null)
{
- return GetEnumerator();
}
/// <summary>
- /// Maps OWIN keys to ASP.NET Core features.
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
/// </summary>
- public class FeatureMap
+ /// <param name="featureInterface">The feature interface type.</param>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="defaultFactory">Default value factory delegate.</param>
+ /// <param name="setter">Value setter delegate.</param>
+ /// <param name="featureFactory">Feature factory delegate.</param>
+ public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter, Func<object> featureFactory)
{
- /// <summary>
- /// Create a <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="featureInterface">The feature interface type.</param>
- /// <param name="getter">Value getter.</param>
- public FeatureMap(Type featureInterface, Func<object, object> getter)
- : this(featureInterface, getter, defaultFactory: null)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="featureInterface">The feature interface type.</param>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="defaultFactory">Default value factory delegate.</param>
- public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory)
- : this(featureInterface, getter, defaultFactory, setter: null)
- {
- }
+ FeatureInterface = featureInterface;
+ Getter = getter;
+ Setter = setter;
+ DefaultFactory = defaultFactory;
+ FeatureFactory = featureFactory;
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="featureInterface">The feature interface type.</param>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="setter">Value setter delegate.</param>
- public FeatureMap(Type featureInterface, Func<object, object> getter, Action<object, object> setter)
- : this(featureInterface, getter, defaultFactory: null, setter: setter)
- {
- }
+ 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; }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="featureInterface">The feature interface type.</param>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="defaultFactory">Default value factory delegate.</param>
- /// <param name="setter">Value setter delegate.</param>
- public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter)
- : this(featureInterface, getter, defaultFactory, setter, featureFactory: null)
- {
- }
+ /// <summary>
+ /// Gets a value indicating whether the feature map is settable.
+ /// </summary>
+ public bool CanSet
+ {
+ get { return Setter != null; }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="featureInterface">The feature interface type.</param>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="defaultFactory">Default value factory delegate.</param>
- /// <param name="setter">Value setter delegate.</param>
- /// <param name="featureFactory">Feature factory delegate.</param>
- public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter, Func<object> featureFactory)
+ internal bool TryGet(HttpContext context, out object value)
+ {
+ object featureInstance = context.Features[FeatureInterface];
+ if (featureInstance == null)
{
- FeatureInterface = featureInterface;
- Getter = getter;
- Setter = setter;
- DefaultFactory = defaultFactory;
- FeatureFactory = featureFactory;
+ value = null;
+ return false;
}
-
- 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; }
-
- /// <summary>
- /// Gets a value indicating whether the feature map is settable.
- /// </summary>
- public bool CanSet
+ value = Getter(featureInstance);
+ if (value == null && DefaultFactory != null)
{
- get { return Setter != null; }
+ value = DefaultFactory();
}
+ return true;
+ }
- internal bool TryGet(HttpContext context, out object value)
+ internal void Set(HttpContext context, object value)
+ {
+ var feature = context.Features[FeatureInterface];
+ if (feature == null)
{
- object featureInstance = context.Features[FeatureInterface];
- if (featureInstance == null)
- {
- value = null;
- return false;
- }
- value = Getter(featureInstance);
- if (value == null && DefaultFactory != null)
+ if (FeatureFactory == null)
{
- value = DefaultFactory();
+ throw new InvalidOperationException("Missing feature: " + FeatureInterface.FullName); // TODO: LOC
}
- return true;
- }
-
- internal void Set(HttpContext context, object value)
- {
- var feature = context.Features[FeatureInterface];
- if (feature == null)
+ else
{
- if (FeatureFactory == null)
- {
- throw new InvalidOperationException("Missing feature: " + FeatureInterface.FullName); // TODO: LOC
- }
- else
- {
- feature = FeatureFactory();
- context.Features[FeatureInterface] = feature;
- }
+ feature = FeatureFactory();
+ context.Features[FeatureInterface] = feature;
}
- Setter(feature, value);
}
+ Setter(feature, value);
}
+ }
+ /// <summary>
+ /// Maps OWIN keys to ASP.NET Core features.
+ /// </summary>
+ /// <typeparam name="TFeature">Feature interface type.</typeparam>
+ public class FeatureMap<TFeature> : FeatureMap
+ {
/// <summary>
- /// Maps OWIN keys to ASP.NET Core features.
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
/// </summary>
- /// <typeparam name="TFeature">Feature interface type.</typeparam>
- public class FeatureMap<TFeature> : FeatureMap
+ /// <param name="getter">Value getter.</param>
+ public FeatureMap(Func<TFeature, object> getter)
+ : base(typeof(TFeature), feature => getter((TFeature)feature))
{
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="getter">Value getter.</param>
- public FeatureMap(Func<TFeature, object> getter)
- : base(typeof(TFeature), feature => getter((TFeature)feature))
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="defaultFactory">Default value factory delegate.</param>
- public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory)
- : base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="defaultFactory">Default value factory delegate.</param>
+ public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory)
+ : base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="setter">Value setter delegate.</param>
- public FeatureMap(Func<TFeature, object> getter, Action<TFeature, object> setter)
- : base(typeof(TFeature), feature => getter((TFeature)feature), (feature, value) => setter((TFeature)feature, value))
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="setter">Value setter delegate.</param>
+ public FeatureMap(Func<TFeature, object> getter, Action<TFeature, object> setter)
+ : base(typeof(TFeature), feature => getter((TFeature)feature), (feature, value) => setter((TFeature)feature, value))
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="defaultFactory">Default value factory delegate.</param>
- /// <param name="setter">Value setter delegate.</param>
- 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))
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="defaultFactory">Default value factory delegate.</param>
+ /// <param name="setter">Value setter delegate.</param>
+ 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))
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
- /// </summary>
- /// <param name="getter">Value getter delegate.</param>
- /// <param name="defaultFactory">Default value factory delegate.</param>
- /// <param name="setter">Value setter delegate.</param>
- /// <param name="featureFactory">Feature factory delegate.</param>
- 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())
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FeatureMap"/> for the specified feature interface type.
+ /// </summary>
+ /// <param name="getter">Value getter delegate.</param>
+ /// <param name="defaultFactory">Default value factory delegate.</param>
+ /// <param name="setter">Value setter delegate.</param>
+ /// <param name="featureFactory">Feature factory delegate.</param>
+ 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
index 9419f30b04..8960d3e26f 100644
--- a/src/Http/Owin/src/OwinEnvironmentFeature.cs
+++ b/src/Http/Owin/src/OwinEnvironmentFeature.cs
@@ -3,14 +3,13 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+/// <summary>
+/// Default implementation of <see cref="IOwinEnvironmentFeature"/>.
+/// </summary>
+public class OwinEnvironmentFeature : IOwinEnvironmentFeature
{
- /// <summary>
- /// Default implementation of <see cref="IOwinEnvironmentFeature"/>.
- /// </summary>
- public class OwinEnvironmentFeature : IOwinEnvironmentFeature
- {
- /// <inheritdoc />
- public IDictionary<string, object> Environment { get; set; }
- }
+ /// <inheritdoc />
+ public IDictionary<string, object> Environment { get; set; }
}
diff --git a/src/Http/Owin/src/OwinExtensions.cs b/src/Http/Owin/src/OwinExtensions.cs
index ffafb19911..5ebc288b50 100644
--- a/src/Http/Owin/src/OwinExtensions.cs
+++ b/src/Http/Owin/src/OwinExtensions.cs
@@ -8,205 +8,204 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Owin;
-namespace Microsoft.AspNetCore.Builder
+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>
+ >;
+
+/// <summary>
+/// Extension methods to add OWIN to an HTTP application pipeline.
+/// </summary>
+public static class OwinExtensions
{
- 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>
- >;
-
/// <summary>
- /// Extension methods to add OWIN to an HTTP application pipeline.
+ /// Adds an OWIN pipeline to the specified <see cref="IApplicationBuilder"/>.
/// </summary>
- public static class OwinExtensions
+ /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the pipeline to.</param>
+ /// <returns>An action used to create the OWIN pipeline.</returns>
+ public static AddMiddleware UseOwin(this IApplicationBuilder builder)
{
- /// <summary>
- /// Adds an OWIN pipeline to the specified <see cref="IApplicationBuilder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the pipeline to.</param>
- /// <returns>An action used to create the OWIN pipeline.</returns>
- public static AddMiddleware UseOwin(this IApplicationBuilder builder)
+ if (builder == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
+ throw new ArgumentNullException(nameof(builder));
+ }
- AddMiddleware add = middleware =>
+ AddMiddleware add = middleware =>
+ {
+ Func<RequestDelegate, RequestDelegate> middleware1 = next1 =>
{
- Func<RequestDelegate, RequestDelegate> middleware1 = next1 =>
+ AppFunc exitMiddleware = env =>
+ {
+ return next1((HttpContext)env[typeof(HttpContext).FullName]);
+ };
+ var app = middleware(exitMiddleware);
+ return httpContext =>
{
- AppFunc exitMiddleware = env =>
- {
- return next1((HttpContext)env[typeof(HttpContext).FullName]);
- };
- var app = middleware(exitMiddleware);
- 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);
- };
+ 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;
- }
+ builder.Use(middleware1);
+ };
+ // Adapt WebSockets by default.
+ add(WebSocketAcceptAdapter.AdaptWebSockets);
+ return add;
+ }
- /// <summary>
- /// Adds OWIN middleware pipeline to the specified <see cref="IApplicationBuilder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
- /// <param name="pipeline">A delegate which can specify the OWIN pipeline.</param>
- /// <returns>The original <see cref="IApplicationBuilder"/>.</returns>
- public static IApplicationBuilder UseOwin(this IApplicationBuilder builder, Action<AddMiddleware> pipeline)
+ /// <summary>
+ /// Adds OWIN middleware pipeline to the specified <see cref="IApplicationBuilder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
+ /// <param name="pipeline">A delegate which can specify the OWIN pipeline.</param>
+ /// <returns>The original <see cref="IApplicationBuilder"/>.</returns>
+ public static IApplicationBuilder UseOwin(this IApplicationBuilder builder, Action<AddMiddleware> pipeline)
+ {
+ if (builder == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
- if (pipeline == null)
- {
- throw new ArgumentNullException(nameof(pipeline));
- }
-
- pipeline(builder.UseOwin());
- return builder;
+ throw new ArgumentNullException(nameof(builder));
}
-
- /// <summary>
- /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
- /// </summary>
- /// <param name="app">The OWIN pipeline.</param>
- /// <returns>An <see cref="IApplicationBuilder"/></returns>
- public static IApplicationBuilder UseBuilder(this AddMiddleware app)
+ if (pipeline == null)
{
- return app.UseBuilder(serviceProvider: null);
+ throw new ArgumentNullException(nameof(pipeline));
}
- /// <summary>
- /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
- /// </summary>
- /// <param name="app">The OWIN pipeline.</param>
- /// <param name="serviceProvider">A service provider for <see cref="IApplicationBuilder.ApplicationServices"/>.</param>
- /// <returns>An <see cref="IApplicationBuilder"/>.</returns>
- public static IApplicationBuilder UseBuilder(this AddMiddleware app, IServiceProvider serviceProvider)
+ pipeline(builder.UseOwin());
+ return builder;
+ }
+
+ /// <summary>
+ /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
+ /// </summary>
+ /// <param name="app">The OWIN pipeline.</param>
+ /// <returns>An <see cref="IApplicationBuilder"/></returns>
+ public static IApplicationBuilder UseBuilder(this AddMiddleware app)
+ {
+ return app.UseBuilder(serviceProvider: null);
+ }
+
+ /// <summary>
+ /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
+ /// </summary>
+ /// <param name="app">The OWIN pipeline.</param>
+ /// <param name="serviceProvider">A service provider for <see cref="IApplicationBuilder.ApplicationServices"/>.</param>
+ /// <returns>An <see cref="IApplicationBuilder"/>.</returns>
+ public static IApplicationBuilder UseBuilder(this AddMiddleware app, IServiceProvider serviceProvider)
+ {
+ if (app == null)
{
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
+ 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();
- }
+ // 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);
+ // 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);
+ var middleware = CreateMiddlewareFactory(exit =>
+ {
+ builder.Use(ignored => exit);
+ return builder.Build();
+ }, builder.ApplicationServices);
- app(middleware);
- return builder;
- }
+ app(middleware);
+ return builder;
+ }
- private static CreateMiddleware CreateMiddlewareFactory(Func<RequestDelegate, RequestDelegate> middleware, IServiceProvider services)
+ private static CreateMiddleware CreateMiddlewareFactory(Func<RequestDelegate, RequestDelegate> middleware, IServiceProvider services)
+ {
+ return next =>
{
- return next =>
+ var app = middleware(httpContext =>
{
- var app = middleware(httpContext =>
- {
- return next(httpContext.Features.Get<IOwinEnvironmentFeature>().Environment);
- });
+ return next(httpContext.Features.Get<IOwinEnvironmentFeature>().Environment);
+ });
- return env =>
- {
+ 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;
- }
+ 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);
- };
+ return app.Invoke(context);
};
- }
+ };
+ }
- /// <summary>
- /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
- /// </summary>
- /// <param name="app">The OWIN pipeline.</param>
- /// <param name="pipeline">A delegate used to configure a middleware pipeline.</param>
- /// <returns>An <see cref="IApplicationBuilder"/>.</returns>
- public static AddMiddleware UseBuilder(this AddMiddleware app, Action<IApplicationBuilder> pipeline)
+ /// <summary>
+ /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
+ /// </summary>
+ /// <param name="app">The OWIN pipeline.</param>
+ /// <param name="pipeline">A delegate used to configure a middleware pipeline.</param>
+ /// <returns>An <see cref="IApplicationBuilder"/>.</returns>
+ public static AddMiddleware UseBuilder(this AddMiddleware app, Action<IApplicationBuilder> pipeline)
+ {
+ return app.UseBuilder(pipeline, serviceProvider: null);
+ }
+
+ /// <summary>
+ /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
+ /// </summary>
+ /// <param name="app">The OWIN pipeline.</param>
+ /// <param name="pipeline">A delegate used to configure a middleware pipeline.</param>
+ /// <param name="serviceProvider">A service provider for <see cref="IApplicationBuilder.ApplicationServices"/>.</param>
+ /// <returns>An <see cref="IApplicationBuilder"/>.</returns>
+ public static AddMiddleware UseBuilder(this AddMiddleware app, Action<IApplicationBuilder> pipeline, IServiceProvider serviceProvider)
+ {
+ if (app == null)
{
- return app.UseBuilder(pipeline, serviceProvider: null);
+ throw new ArgumentNullException(nameof(app));
}
-
- /// <summary>
- /// Creates an <see cref="IApplicationBuilder"/> for an OWIN pipeline.
- /// </summary>
- /// <param name="app">The OWIN pipeline.</param>
- /// <param name="pipeline">A delegate used to configure a middleware pipeline.</param>
- /// <param name="serviceProvider">A service provider for <see cref="IApplicationBuilder.ApplicationServices"/>.</param>
- /// <returns>An <see cref="IApplicationBuilder"/>.</returns>
- public static AddMiddleware UseBuilder(this AddMiddleware app, Action<IApplicationBuilder> pipeline, IServiceProvider serviceProvider)
+ if (pipeline == null)
{
- 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;
+ throw new ArgumentNullException(nameof(pipeline));
}
- private class EmptyProvider : IServiceProvider
+ var builder = app.UseBuilder(serviceProvider);
+ pipeline(builder);
+ return app;
+ }
+
+ private class EmptyProvider : IServiceProvider
+ {
+ public object GetService(Type serviceType)
{
- public object GetService(Type serviceType)
- {
- return null;
- }
+ return null;
}
}
}
diff --git a/src/Http/Owin/src/OwinFeatureCollection.cs b/src/Http/Owin/src/OwinFeatureCollection.cs
index 2bfba79232..ee1193b471 100644
--- a/src/Http/Owin/src/OwinFeatureCollection.cs
+++ b/src/Http/Owin/src/OwinFeatureCollection.cs
@@ -19,440 +19,439 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
+
+/// <summary>
+/// OWIN feature collection.
+/// </summary>
+public class OwinFeatureCollection :
+ IFeatureCollection,
+ IHttpRequestFeature,
+ IHttpResponseFeature,
+ IHttpResponseBodyFeature,
+ IHttpConnectionFeature,
+ ITlsConnectionFeature,
+ IHttpRequestIdentifierFeature,
+ IHttpRequestLifetimeFeature,
+ IHttpAuthenticationFeature,
+ IHttpWebSocketFeature,
+ IOwinEnvironmentFeature
{
- using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
-
/// <summary>
- /// OWIN feature collection.
+ /// Gets or sets OWIN environment values.
/// </summary>
- public class OwinFeatureCollection :
- IFeatureCollection,
- IHttpRequestFeature,
- IHttpResponseFeature,
- IHttpResponseBodyFeature,
- IHttpConnectionFeature,
- ITlsConnectionFeature,
- IHttpRequestIdentifierFeature,
- IHttpRequestLifetimeFeature,
- IHttpAuthenticationFeature,
- IHttpWebSocketFeature,
- IOwinEnvironmentFeature
- {
- /// <summary>
- /// Gets or sets OWIN environment values.
- /// </summary>
- public IDictionary<string, object> Environment { get; set; }
- private PipeWriter _responseBodyWrapper;
- private bool _headersSent;
-
- /// <summary>
- /// Initializes a new instance of <see cref="OwinFeatureCollection"/>.
- /// </summary>
- /// <param name="environment">The environment values.</param>
- public OwinFeatureCollection(IDictionary<string, object> environment)
- {
- Environment = environment;
- SupportsWebSockets = true;
+ public IDictionary<string, object> Environment { get; set; }
+ private PipeWriter _responseBodyWrapper;
+ private bool _headersSent;
- var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
- register?.Invoke(state =>
- {
- var collection = (OwinFeatureCollection)state;
- collection._headersSent = true;
- }, this);
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="OwinFeatureCollection"/>.
+ /// </summary>
+ /// <param name="environment">The environment values.</param>
+ public OwinFeatureCollection(IDictionary<string, object> environment)
+ {
+ Environment = environment;
+ SupportsWebSockets = true;
- T Prop<T>(string key)
+ var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
+ register?.Invoke(state =>
{
- object value;
- if (Environment.TryGetValue(key, out value) && value is T)
- {
- return (T)value;
- }
- return default(T);
- }
+ var collection = (OwinFeatureCollection)state;
+ collection._headersSent = true;
+ }, this);
+ }
- void Prop(string key, object value)
+ T Prop<T>(string key)
+ {
+ object value;
+ if (Environment.TryGetValue(key, out value) && value is T)
{
- Environment[key] = value;
+ return (T)value;
}
+ return default(T);
+ }
- string IHttpRequestFeature.Protocol
- {
- get { return Prop<string>(OwinConstants.RequestProtocol); }
- set { Prop(OwinConstants.RequestProtocol, value); }
- }
+ void Prop(string key, object value)
+ {
+ Environment[key] = value;
+ }
- string IHttpRequestFeature.Scheme
- {
- get { return Prop<string>(OwinConstants.RequestScheme); }
- set { Prop(OwinConstants.RequestScheme, value); }
- }
+ string IHttpRequestFeature.Protocol
+ {
+ get { return Prop<string>(OwinConstants.RequestProtocol); }
+ set { Prop(OwinConstants.RequestProtocol, value); }
+ }
- string IHttpRequestFeature.Method
- {
- get { return Prop<string>(OwinConstants.RequestMethod); }
- set { Prop(OwinConstants.RequestMethod, value); }
- }
+ string IHttpRequestFeature.Scheme
+ {
+ get { return Prop<string>(OwinConstants.RequestScheme); }
+ set { Prop(OwinConstants.RequestScheme, value); }
+ }
- string IHttpRequestFeature.PathBase
- {
- get { return Prop<string>(OwinConstants.RequestPathBase); }
- set { Prop(OwinConstants.RequestPathBase, value); }
- }
+ string IHttpRequestFeature.Method
+ {
+ get { return Prop<string>(OwinConstants.RequestMethod); }
+ set { Prop(OwinConstants.RequestMethod, value); }
+ }
- string IHttpRequestFeature.Path
- {
- get { return Prop<string>(OwinConstants.RequestPath); }
- set { Prop(OwinConstants.RequestPath, value); }
- }
+ string IHttpRequestFeature.PathBase
+ {
+ get { return Prop<string>(OwinConstants.RequestPathBase); }
+ set { Prop(OwinConstants.RequestPathBase, value); }
+ }
- string IHttpRequestFeature.QueryString
- {
- get { return Utilities.AddQuestionMark(Prop<string>(OwinConstants.RequestQueryString)); }
- set { Prop(OwinConstants.RequestQueryString, Utilities.RemoveQuestionMark(value)); }
- }
+ string IHttpRequestFeature.Path
+ {
+ get { return Prop<string>(OwinConstants.RequestPath); }
+ set { Prop(OwinConstants.RequestPath, value); }
+ }
- string IHttpRequestFeature.RawTarget
- {
- get { return string.Empty; }
- set { throw new NotSupportedException(); }
- }
+ string IHttpRequestFeature.QueryString
+ {
+ get { return Utilities.AddQuestionMark(Prop<string>(OwinConstants.RequestQueryString)); }
+ set { Prop(OwinConstants.RequestQueryString, Utilities.RemoveQuestionMark(value)); }
+ }
- IHeaderDictionary IHttpRequestFeature.Headers
- {
- get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.RequestHeaders)); }
- set { Prop(OwinConstants.RequestHeaders, Utilities.MakeDictionaryStringArray(value)); }
- }
+ string IHttpRequestFeature.RawTarget
+ {
+ get { return string.Empty; }
+ set { throw new NotSupportedException(); }
+ }
- string IHttpRequestIdentifierFeature.TraceIdentifier
- {
- get { return Prop<string>(OwinConstants.RequestId); }
- set { Prop(OwinConstants.RequestId, value); }
- }
+ IHeaderDictionary IHttpRequestFeature.Headers
+ {
+ get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.RequestHeaders)); }
+ set { Prop(OwinConstants.RequestHeaders, Utilities.MakeDictionaryStringArray(value)); }
+ }
- Stream IHttpRequestFeature.Body
- {
- get { return Prop<Stream>(OwinConstants.RequestBody); }
- set { Prop(OwinConstants.RequestBody, value); }
- }
+ string IHttpRequestIdentifierFeature.TraceIdentifier
+ {
+ get { return Prop<string>(OwinConstants.RequestId); }
+ set { Prop(OwinConstants.RequestId, value); }
+ }
- int IHttpResponseFeature.StatusCode
- {
- get { return Prop<int>(OwinConstants.ResponseStatusCode); }
- set { Prop(OwinConstants.ResponseStatusCode, value); }
- }
+ Stream IHttpRequestFeature.Body
+ {
+ get { return Prop<Stream>(OwinConstants.RequestBody); }
+ set { Prop(OwinConstants.RequestBody, value); }
+ }
- string IHttpResponseFeature.ReasonPhrase
- {
- get { return Prop<string>(OwinConstants.ResponseReasonPhrase); }
- set { Prop(OwinConstants.ResponseReasonPhrase, value); }
- }
+ int IHttpResponseFeature.StatusCode
+ {
+ get { return Prop<int>(OwinConstants.ResponseStatusCode); }
+ set { Prop(OwinConstants.ResponseStatusCode, value); }
+ }
- IHeaderDictionary IHttpResponseFeature.Headers
- {
- get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.ResponseHeaders)); }
- set { Prop(OwinConstants.ResponseHeaders, Utilities.MakeDictionaryStringArray(value)); }
- }
+ string IHttpResponseFeature.ReasonPhrase
+ {
+ get { return Prop<string>(OwinConstants.ResponseReasonPhrase); }
+ set { Prop(OwinConstants.ResponseReasonPhrase, value); }
+ }
- Stream IHttpResponseFeature.Body
- {
- get { return Prop<Stream>(OwinConstants.ResponseBody); }
- set { Prop(OwinConstants.ResponseBody, value); }
- }
+ IHeaderDictionary IHttpResponseFeature.Headers
+ {
+ get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.ResponseHeaders)); }
+ set { Prop(OwinConstants.ResponseHeaders, Utilities.MakeDictionaryStringArray(value)); }
+ }
- Stream IHttpResponseBodyFeature.Stream
- {
- get { return Prop<Stream>(OwinConstants.ResponseBody); }
- }
+ Stream IHttpResponseFeature.Body
+ {
+ get { return Prop<Stream>(OwinConstants.ResponseBody); }
+ set { Prop(OwinConstants.ResponseBody, value); }
+ }
+
+ Stream IHttpResponseBodyFeature.Stream
+ {
+ get { return Prop<Stream>(OwinConstants.ResponseBody); }
+ }
- PipeWriter IHttpResponseBodyFeature.Writer
+ PipeWriter IHttpResponseBodyFeature.Writer
+ {
+ get
{
- get
+ if (_responseBodyWrapper == null)
{
- if (_responseBodyWrapper == null)
- {
- _responseBodyWrapper = PipeWriter.Create(Prop<Stream>(OwinConstants.ResponseBody), new StreamPipeWriterOptions(leaveOpen: true));
- }
-
- return _responseBodyWrapper;
+ _responseBodyWrapper = PipeWriter.Create(Prop<Stream>(OwinConstants.ResponseBody), new StreamPipeWriterOptions(leaveOpen: true));
}
+
+ return _responseBodyWrapper;
}
+ }
+
+ bool IHttpResponseFeature.HasStarted
+ {
+ get { return _headersSent; }
+ }
- bool IHttpResponseFeature.HasStarted
+ void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
+ {
+ var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
+ if (register == null)
{
- get { return _headersSent; }
+ throw new NotSupportedException(OwinConstants.CommonKeys.OnSendingHeaders);
}
- 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);
+ }
- // 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();
+ }
- 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.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()); }
+ }
- 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), CultureInfo.InvariantCulture); }
+ set { Prop(OwinConstants.CommonKeys.RemotePort, value.ToString(CultureInfo.InvariantCulture)); }
+ }
- int IHttpConnectionFeature.RemotePort
- {
- get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.RemotePort), CultureInfo.InvariantCulture); }
- set { Prop(OwinConstants.CommonKeys.RemotePort, value.ToString(CultureInfo.InvariantCulture)); }
- }
+ int IHttpConnectionFeature.LocalPort
+ {
+ get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.LocalPort), CultureInfo.InvariantCulture); }
+ set { Prop(OwinConstants.CommonKeys.LocalPort, value.ToString(CultureInfo.InvariantCulture)); }
+ }
- int IHttpConnectionFeature.LocalPort
- {
- get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.LocalPort), CultureInfo.InvariantCulture); }
- 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); }
+ }
- string IHttpConnectionFeature.ConnectionId
+ Task IHttpResponseBodyFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
+ {
+ object obj;
+ if (Environment.TryGetValue(OwinConstants.SendFiles.SendAsync, out obj))
{
- get { return Prop<string>(OwinConstants.CommonKeys.ConnectionId); }
- set { Prop(OwinConstants.CommonKeys.ConnectionId, value); }
+ var func = (SendFileFunc)obj;
+ return func(path, offset, length, cancellation);
}
+ throw new NotSupportedException(OwinConstants.SendFiles.SendAsync);
+ }
- Task IHttpResponseBodyFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
+ private bool SupportsClientCerts
+ {
+ get
{
object obj;
- if (Environment.TryGetValue(OwinConstants.SendFiles.SendAsync, out 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)
{
- var func = (SendFileFunc)obj;
- return func(path, offset, length, cancellation);
+ return true;
}
- throw new NotSupportedException(OwinConstants.SendFiles.SendAsync);
+ return false;
}
+ }
- 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); }
+ }
- X509Certificate2 ITlsConnectionFeature.ClientCertificate
+ async Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
+ {
+ var loadAsync = Prop<Func<Task>>(OwinConstants.CommonKeys.LoadClientCertAsync);
+ if (loadAsync != null)
{
- get { return Prop<X509Certificate2>(OwinConstants.CommonKeys.ClientCertificate); }
- set { Prop(OwinConstants.CommonKeys.ClientCertificate, value); }
+ await loadAsync();
}
+ return Prop<X509Certificate2>(OwinConstants.CommonKeys.ClientCertificate);
+ }
- 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); }
+ }
- CancellationToken IHttpRequestLifetimeFeature.RequestAborted
- {
- get { return Prop<CancellationToken>(OwinConstants.CallCancelled); }
- set { Prop(OwinConstants.CallCancelled, value); }
- }
+ void IHttpRequestLifetimeFeature.Abort()
+ {
+ throw new NotImplementedException();
+ }
- void IHttpRequestLifetimeFeature.Abort()
+ ClaimsPrincipal IHttpAuthenticationFeature.User
+ {
+ get
{
- throw new NotImplementedException();
+ return Prop<ClaimsPrincipal>(OwinConstants.RequestUser)
+ ?? Utilities.MakeClaimsPrincipal(Prop<IPrincipal>(OwinConstants.Security.User));
}
-
- ClaimsPrincipal IHttpAuthenticationFeature.User
+ set
{
- get
- {
- return Prop<ClaimsPrincipal>(OwinConstants.RequestUser)
- ?? Utilities.MakeClaimsPrincipal(Prop<IPrincipal>(OwinConstants.Security.User));
- }
- set
- {
- Prop(OwinConstants.RequestUser, value);
- Prop(OwinConstants.Security.User, value);
- }
+ Prop(OwinConstants.RequestUser, value);
+ Prop(OwinConstants.Security.User, value);
}
+ }
- /// <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; }
+ /// <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
+ bool IHttpWebSocketFeature.IsWebSocketRequest
+ {
+ get
{
- get
- {
- object obj;
- return Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj);
- }
+ object obj;
+ return Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj);
}
+ }
- Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context)
+ Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context)
+ {
+ object obj;
+ if (!Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj))
{
- 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);
+ throw new NotSupportedException("WebSockets are not supported"); // TODO: LOC
}
+ var accept = (Func<WebSocketAcceptContext, Task<WebSocket>>)obj;
+ return accept(context);
+ }
- // IFeatureCollection
+ // IFeatureCollection
- /// <inheritdoc/>
- public int Revision
- {
- get { return 0; } // Not modifiable
- }
+ /// <inheritdoc/>
+ public int Revision
+ {
+ get { return 0; } // Not modifiable
+ }
- /// <inheritdoc/>
- public bool IsReadOnly
- {
- get { return true; }
- }
+ /// <inheritdoc/>
+ public bool IsReadOnly
+ {
+ get { return true; }
+ }
- /// <inheritdoc/>
- public object this[Type key]
- {
- get { return Get(key); }
- set { throw new NotSupportedException(); }
- }
+ /// <inheritdoc/>
+ public object this[Type key]
+ {
+ get { return Get(key); }
+ set { throw new NotSupportedException(); }
+ }
- private bool SupportsInterface(Type key)
+ private bool SupportsInterface(Type key)
+ {
+ // Does this type implement the requested interface?
+ if (key.IsAssignableFrom(GetType()))
{
- // Does this type implement the requested interface?
- if (key.IsAssignableFrom(GetType()))
+ // Check for conditional features
+ if (key == typeof(ITlsConnectionFeature))
{
- // Check for conditional features
- if (key == typeof(ITlsConnectionFeature))
- {
- return SupportsClientCerts;
- }
- else if (key == typeof(IHttpWebSocketFeature))
- {
- return SupportsWebSockets;
- }
-
- // The rest of the features are always supported.
- return true;
+ return SupportsClientCerts;
}
- return false;
- }
-
- /// <inheritdoc/>
- public object Get(Type key)
- {
- if (SupportsInterface(key))
+ else if (key == typeof(IHttpWebSocketFeature))
{
- return this;
+ return SupportsWebSockets;
}
- return null;
- }
- /// <inheritdoc/>
- public void Set(Type key, object value)
- {
- throw new NotSupportedException();
+ // The rest of the features are always supported.
+ return true;
}
+ return false;
+ }
- /// <inheritdoc/>
- public TFeature Get<TFeature>()
+ /// <inheritdoc/>
+ public object Get(Type key)
+ {
+ if (SupportsInterface(key))
{
- return (TFeature)this[typeof(TFeature)];
+ return this;
}
+ return null;
+ }
- /// <inheritdoc/>
- public void Set<TFeature>(TFeature instance)
- {
- this[typeof(TFeature)] = instance;
- }
+ /// <inheritdoc/>
+ public void Set(Type key, object value)
+ {
+ throw new NotSupportedException();
+ }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
+ /// <inheritdoc/>
+ public TFeature Get<TFeature>()
+ {
+ return (TFeature)this[typeof(TFeature)];
+ }
- /// <inheritdoc/>
- 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(IHttpResponseBodyFeature), 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);
+ /// <inheritdoc/>
+ public void Set<TFeature>(TFeature instance)
+ {
+ this[typeof(TFeature)] = instance;
+ }
- // Check for conditional features
- if (SupportsClientCerts)
- {
- yield return new KeyValuePair<Type, object>(typeof(ITlsConnectionFeature), this);
- }
- if (SupportsWebSockets)
- {
- yield return new KeyValuePair<Type, object>(typeof(IHttpWebSocketFeature), this);
- }
- }
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ /// <inheritdoc/>
+ 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(IHttpResponseBodyFeature), 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);
- void IHttpResponseBodyFeature.DisableBuffering()
+ // Check for conditional features
+ if (SupportsClientCerts)
{
+ yield return new KeyValuePair<Type, object>(typeof(ITlsConnectionFeature), this);
}
-
- async Task IHttpResponseBodyFeature.StartAsync(CancellationToken cancellationToken)
+ if (SupportsWebSockets)
{
- if (_responseBodyWrapper != null)
- {
- await _responseBodyWrapper.FlushAsync(cancellationToken);
- }
-
- // The pipe may or may not have flushed the stream. Make sure the stream gets flushed to trigger response start.
- await Prop<Stream>(OwinConstants.ResponseBody).FlushAsync(cancellationToken);
+ yield return new KeyValuePair<Type, object>(typeof(IHttpWebSocketFeature), this);
}
+ }
- Task IHttpResponseBodyFeature.CompleteAsync()
- {
- if (_responseBodyWrapper != null)
- {
- return _responseBodyWrapper.FlushAsync().AsTask();
- }
+ void IHttpResponseBodyFeature.DisableBuffering()
+ {
+ }
- return Task.CompletedTask;
+ async Task IHttpResponseBodyFeature.StartAsync(CancellationToken cancellationToken)
+ {
+ if (_responseBodyWrapper != null)
+ {
+ await _responseBodyWrapper.FlushAsync(cancellationToken);
}
- /// <inheritdoc/>
- public void Dispose()
+ // The pipe may or may not have flushed the stream. Make sure the stream gets flushed to trigger response start.
+ await Prop<Stream>(OwinConstants.ResponseBody).FlushAsync(cancellationToken);
+ }
+
+ Task IHttpResponseBodyFeature.CompleteAsync()
+ {
+ if (_responseBodyWrapper != null)
{
+ return _responseBodyWrapper.FlushAsync().AsTask();
}
+
+ return Task.CompletedTask;
+ }
+
+ /// <inheritdoc/>
+ public void Dispose()
+ {
}
}
diff --git a/src/Http/Owin/src/Utilities.cs b/src/Http/Owin/src/Utilities.cs
index 6a1b268faf..ffb4862e2c 100644
--- a/src/Http/Owin/src/Utilities.cs
+++ b/src/Http/Owin/src/Utilities.cs
@@ -8,62 +8,61 @@ using System.Security.Principal;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+internal static class Utilities
{
- internal static class Utilities
+ internal static string RemoveQuestionMark(string queryString)
{
- internal static string RemoveQuestionMark(string queryString)
+ if (!string.IsNullOrEmpty(queryString))
{
- if (!string.IsNullOrEmpty(queryString))
+ if (queryString[0] == '?')
{
- if (queryString[0] == '?')
- {
- return queryString.Substring(1);
- }
+ return queryString.Substring(1);
}
- return queryString;
}
+ return queryString;
+ }
- internal static string AddQuestionMark(string queryString)
+ internal static string AddQuestionMark(string queryString)
+ {
+ if (!string.IsNullOrEmpty(queryString))
{
- if (!string.IsNullOrEmpty(queryString))
- {
- return '?' + queryString;
- }
- return queryString;
+ return '?' + queryString;
}
+ return queryString;
+ }
- internal static ClaimsPrincipal MakeClaimsPrincipal(IPrincipal principal)
+ internal static ClaimsPrincipal MakeClaimsPrincipal(IPrincipal principal)
+ {
+ if (principal == null)
{
- if (principal == null)
- {
- return null;
- }
- if (principal is ClaimsPrincipal)
- {
- return principal as ClaimsPrincipal;
- }
- return new ClaimsPrincipal(principal);
+ return null;
}
+ if (principal is ClaimsPrincipal)
+ {
+ return principal as ClaimsPrincipal;
+ }
+ return new ClaimsPrincipal(principal);
+ }
- internal static IHeaderDictionary MakeHeaderDictionary(IDictionary<string, string[]> dictionary)
+ internal static IHeaderDictionary MakeHeaderDictionary(IDictionary<string, string[]> dictionary)
+ {
+ var wrapper = dictionary as DictionaryStringArrayWrapper;
+ if (wrapper != null)
{
- var wrapper = dictionary as DictionaryStringArrayWrapper;
- if (wrapper != null)
- {
- return wrapper.Inner;
- }
- return new DictionaryStringValuesWrapper(dictionary);
+ return wrapper.Inner;
}
+ return new DictionaryStringValuesWrapper(dictionary);
+ }
- internal static IDictionary<string, string[]> MakeDictionaryStringArray(IHeaderDictionary dictionary)
+ internal static IDictionary<string, string[]> MakeDictionaryStringArray(IHeaderDictionary dictionary)
+ {
+ var wrapper = dictionary as DictionaryStringValuesWrapper;
+ if (wrapper != null)
{
- var wrapper = dictionary as DictionaryStringValuesWrapper;
- if (wrapper != null)
- {
- return wrapper.Inner;
- }
- return new DictionaryStringArrayWrapper(dictionary);
+ 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
index 16a1916ecb..03311884d4 100644
--- a/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs
+++ b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs
@@ -8,143 +8,142 @@ 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
+namespace Microsoft.AspNetCore.Owin;
+
+using AppFunc = Func<IDictionary<string, object>, Task>;
+using WebSocketAccept =
+ Action
+ <
+ IDictionary<string, object>, // WebSocket Accept parameters
+ Func // WebSocketFunc callback
<
- WebSocketAcceptContext, // WebSocket Accept parameters
- Task<WebSocket>
- >;
+ 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
+/// <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 readonly WebSocketAccept _owinWebSocketAccept;
+ private readonly TaskCompletionSource<int> _requestTcs = new TaskCompletionSource<int>();
+ private readonly TaskCompletionSource<WebSocket> _acceptTcs = new TaskCompletionSource<WebSocket>();
+ private readonly TaskCompletionSource<int> _upstreamWentAsync = new TaskCompletionSource<int>();
+ private string _subProtocol;
+
+ private OwinWebSocketAcceptAdapter(WebSocketAccept owinWebSocketAccept)
{
- private readonly WebSocketAccept _owinWebSocketAccept;
- private readonly TaskCompletionSource<int> _requestTcs = new TaskCompletionSource<int>();
- private readonly TaskCompletionSource<WebSocket> _acceptTcs = new TaskCompletionSource<WebSocket>();
- private readonly TaskCompletionSource<int> _upstreamWentAsync = new TaskCompletionSource<int>();
- private string _subProtocol;
+ _owinWebSocketAccept = owinWebSocketAccept;
+ }
+
+ private Task RequestTask { get { return _requestTcs.Task; } }
+ private Task UpstreamTask { get; set; }
+ private TaskCompletionSource<int> UpstreamWentAsyncTcs { get { return _upstreamWentAsync; } }
- private OwinWebSocketAcceptAdapter(WebSocketAccept owinWebSocketAccept)
+ private async Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext context)
+ {
+ IDictionary<string, object> options = null;
+ if (context is OwinWebSocketAcceptContext)
{
- _owinWebSocketAccept = owinWebSocketAccept;
+ var acceptContext = context as OwinWebSocketAcceptContext;
+ options = acceptContext.Options;
+ _subProtocol = acceptContext.SubProtocol;
}
-
- 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)
+ else if (context?.SubProtocol != null)
{
- 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)
+ options = new Dictionary<string, object>(1)
{
{ OwinConstants.WebSocket.SubProtocol, context.SubProtocol }
};
- _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;
+ // 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.
+ _owinWebSocketAccept(options, OwinAcceptCallback);
+ _requestTcs.TrySetResult(0); // Let the pipeline unwind.
- return await _acceptTcs.Task;
- }
+ return await _acceptTcs.Task;
+ }
- private Task OwinAcceptCallback(IDictionary<string, object> webSocketContext)
+ 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)
{
- _acceptTcs.TrySetResult(new OwinWebSocketAdapter(webSocketContext, _subProtocol));
- return UpstreamTask;
+ _requestTcs.TrySetCanceled();
}
-
- // Make sure declined websocket requests complete. This is a no-op for accepted websocket requests.
- private void EnsureCompleted(Task task)
+ else if (task.IsFaulted)
{
- if (task.IsCanceled)
- {
- _requestTcs.TrySetCanceled();
- }
- else if (task.IsFaulted)
- {
- _requestTcs.TrySetException(task.Exception);
- }
- else
- {
- _requestTcs.TrySetResult(0);
- }
+ _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 pipeline 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 creates a WebSocket adapter and completes the original 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.
- //
- /// <summary>
- /// Adapt web sockets to OWIN.
- /// </summary>
- /// <param name="next">The next OWIN app delegate.</param>
- /// <returns>An OWIN app delegate.</returns>
- public static AppFunc AdaptWebSockets(AppFunc next)
+ // 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 pipeline 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 creates a WebSocket adapter and completes the original 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.
+ //
+ /// <summary>
+ /// Adapt web sockets to OWIN.
+ /// </summary>
+ /// <param name="next">The next OWIN app delegate.</param>
+ /// <returns>An OWIN app delegate.</returns>
+ public static AppFunc AdaptWebSockets(AppFunc next)
+ {
+ return environment =>
{
- return environment =>
+ object accept;
+ if (environment.TryGetValue(OwinConstants.WebSocket.Accept, out accept) && accept is WebSocketAccept)
{
- 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
{
- 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, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
- }
- catch (Exception ex)
- {
- adapter.UpstreamWentAsyncTcs.TrySetException(ex);
- throw;
- }
-
- return adapter.RequestTask;
+ adapter.UpstreamTask = next(environment);
+ adapter.UpstreamWentAsyncTcs.TrySetResult(0);
+ adapter.UpstreamTask.ContinueWith(adapter.EnsureCompleted, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
- else
+ catch (Exception ex)
{
- return next(environment);
+ adapter.UpstreamWentAsyncTcs.TrySetException(ex);
+ throw;
}
- };
- }
+
+ return adapter.RequestTask;
+ }
+ else
+ {
+ return next(environment);
+ }
+ };
}
}
diff --git a/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs
index c255a4cfc9..9ccf5ef8f9 100644
--- a/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs
+++ b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs
@@ -4,59 +4,58 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+/// <summary>
+/// OWIN WebSocket accept context.
+/// </summary>
+public class OwinWebSocketAcceptContext : WebSocketAcceptContext
{
+ private IDictionary<string, object> _options;
+
/// <summary>
- /// OWIN WebSocket accept context.
+ /// Initializes a new instance of <see cref="OwinWebSocketAcceptContext"/>.
/// </summary>
- public class OwinWebSocketAcceptContext : WebSocketAcceptContext
+ public OwinWebSocketAcceptContext() : this(new Dictionary<string, object>(1))
{
- private IDictionary<string, object> _options;
-
- /// <summary>
- /// Initializes a new instance of <see cref="OwinWebSocketAcceptContext"/>.
- /// </summary>
- public OwinWebSocketAcceptContext() : this(new Dictionary<string, object>(1))
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="OwinWebSocketAcceptContext"/>.
- /// </summary>
- /// <param name="options">OWIN WebSocket options.</param>
- public OwinWebSocketAcceptContext(IDictionary<string, object> options)
- {
- _options = options;
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="OwinWebSocketAcceptContext"/>.
+ /// </summary>
+ /// <param name="options">OWIN WebSocket options.</param>
+ public OwinWebSocketAcceptContext(IDictionary<string, object> options)
+ {
+ _options = options;
+ }
- /// <inheritdocs />
- public override string SubProtocol
+ /// <inheritdocs />
+ public override string SubProtocol
+ {
+ get
{
- get
+ object obj;
+ if (_options != null && _options.TryGetValue(OwinConstants.WebSocket.SubProtocol, out obj))
{
- object obj;
- if (_options != null && _options.TryGetValue(OwinConstants.WebSocket.SubProtocol, out obj))
- {
- return (string)obj;
- }
- return null;
+ return (string)obj;
}
- set
+ return null;
+ }
+ set
+ {
+ if (_options == null)
{
- if (_options == null)
- {
- _options = new Dictionary<string, object>(1);
- }
- _options[OwinConstants.WebSocket.SubProtocol] = value;
+ _options = new Dictionary<string, object>(1);
}
+ _options[OwinConstants.WebSocket.SubProtocol] = value;
}
+ }
- /// <summary>
- /// Gets OWIN WebSocket options.
- /// </summary>
- public IDictionary<string, object> Options
- {
- get { return _options; }
- }
+ /// <summary>
+ /// Gets OWIN WebSocket options.
+ /// </summary>
+ public IDictionary<string, object> Options
+ {
+ get { return _options; }
}
}
diff --git a/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs b/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs
index 0adb7cdce0..ace67ba590 100644
--- a/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs
+++ b/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs
@@ -4,215 +4,214 @@
using System;
using System.Buffers;
using System.Collections.Generic;
+using System.Net.WebSockets;
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 */,
+namespace Microsoft.AspNetCore.Owin;
+
+using RawWebSocketReceiveResult = Tuple<int, // type
+ bool, // end of message?
+ int>; // count
+// 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 */,
- CancellationToken /* cancel */,
- Task>;
- using RawWebSocketReceiveResult = Tuple<int, // type
- bool, // end of message?
- int>; // count
+ int /* count */>>>;
+using WebSocketSendAsync =
+ Func<ArraySegment<byte> /* data */,
+ int /* messageType */,
+ bool /* endOfMessage */,
+ CancellationToken /* cancel */,
+ Task>;
+
+/// <summary>
+/// OWIN WebSocket adapter.
+/// </summary>
+public class OwinWebSocketAdapter : WebSocket
+{
+ private const int _rentedBufferSize = 1024;
+ private readonly IDictionary<string, object> _websocketContext;
+ private readonly WebSocketSendAsync _sendAsync;
+ private readonly WebSocketReceiveAsync _receiveAsync;
+ private readonly WebSocketCloseAsync _closeAsync;
+ private WebSocketState _state;
+ private readonly string _subProtocol;
/// <summary>
- /// OWIN WebSocket adapter.
+ /// Initializes a new instance of <see cref="OwinWebSocketAdapter"/>.
/// </summary>
- public class OwinWebSocketAdapter : WebSocket
+ /// <param name="websocketContext">WebSocket context options.</param>
+ /// <param name="subProtocol">The WebSocket subprotocol.</param>
+ public OwinWebSocketAdapter(IDictionary<string, object> websocketContext, string subProtocol)
{
- private const int _rentedBufferSize = 1024;
- private readonly IDictionary<string, object> _websocketContext;
- private readonly WebSocketSendAsync _sendAsync;
- private readonly WebSocketReceiveAsync _receiveAsync;
- private readonly WebSocketCloseAsync _closeAsync;
- private WebSocketState _state;
- private readonly string _subProtocol;
-
- /// <summary>
- /// Initializes a new instance of <see cref="OwinWebSocketAdapter"/>.
- /// </summary>
- /// <param name="websocketContext">WebSocket context options.</param>
- /// <param name="subProtocol">The WebSocket subprotocol.</param>
- 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;
- }
+ _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;
+ }
- /// <inheritdocs />
- public override WebSocketCloseStatus? CloseStatus
+ /// <inheritdocs />
+ public override WebSocketCloseStatus? CloseStatus
+ {
+ get
{
- get
+ object obj;
+ if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseStatus, out obj))
{
- object obj;
- if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseStatus, out obj))
- {
- return (WebSocketCloseStatus)obj;
- }
- return null;
+ return (WebSocketCloseStatus)obj;
}
+ return null;
}
+ }
- /// <inheritdocs />
- public override string CloseStatusDescription
+ /// <inheritdocs />
+ public override string CloseStatusDescription
+ {
+ get
{
- get
+ object obj;
+ if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseDescription, out obj))
{
- object obj;
- if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseDescription, out obj))
- {
- return (string)obj;
- }
- return null;
+ return (string)obj;
}
+ return null;
}
+ }
- /// <inheritdocs />
- public override string SubProtocol
+ /// <inheritdocs />
+ public override string SubProtocol
+ {
+ get
{
- get
- {
- return _subProtocol;
- }
+ return _subProtocol;
}
+ }
- /// <inheritdocs />
- public override WebSocketState State
+ /// <inheritdocs />
+ public override WebSocketState State
+ {
+ get
{
- get
- {
- return _state;
- }
+ return _state;
}
+ }
- /// <inheritdocs />
- public override async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
+ /// <inheritdocs />
+ 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)
{
- var rawResult = await _receiveAsync(buffer, cancellationToken);
- var messageType = OpCodeToEnum(rawResult.Item1);
- if (messageType == WebSocketMessageType.Close)
+ if (State == WebSocketState.Open)
{
- 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);
+ _state = WebSocketState.CloseReceived;
}
- else
+ else if (State == WebSocketState.CloseSent)
{
- return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2);
+ _state = WebSocketState.Closed;
}
+ return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2, CloseStatus, CloseStatusDescription);
}
-
- /// <inheritdocs />
- public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
+ else
{
- return _sendAsync(buffer, EnumToOpCode(messageType), endOfMessage, cancellationToken);
+ return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2);
}
+ }
- /// <inheritdocs />
- public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
- {
- if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
- {
- await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
- }
+ /// <inheritdocs />
+ public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
+ {
+ return _sendAsync(buffer, EnumToOpCode(messageType), endOfMessage, 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);
- }
+ /// <inheritdocs />
+ public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
+ {
+ if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
+ {
+ await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
}
- /// <inheritdocs />
- public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
+ var buffer = ArrayPool<byte>.Shared.Rent(_rentedBufferSize);
+ try
{
- // TODO: Validate state
- if (State == WebSocketState.Open)
+ while (State == WebSocketState.CloseSent)
{
- _state = WebSocketState.CloseSent;
+ // Drain until close received
+ await ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
}
- else if (State == WebSocketState.CloseReceived)
- {
- _state = WebSocketState.Closed;
- }
- return _closeAsync((int)closeStatus, statusDescription, cancellationToken);
}
-
- /// <inheritdocs />
- public override void Abort()
+ finally
{
- _state = WebSocketState.Aborted;
+ ArrayPool<byte>.Shared.Return(buffer);
}
+ }
- /// <inheritdocs />
- public override void Dispose()
+ /// <inheritdocs />
+ 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);
+ }
+
+ /// <inheritdocs />
+ public override void Abort()
+ {
+ _state = WebSocketState.Aborted;
+ }
+
+ /// <inheritdocs />
+ public override void Dispose()
+ {
+ _state = WebSocketState.Closed;
+ }
- private static WebSocketMessageType OpCodeToEnum(int messageType)
+ private static WebSocketMessageType OpCodeToEnum(int messageType)
+ {
+ switch (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);
- }
+ 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)
+ private static int EnumToOpCode(WebSocketMessageType webSocketMessageType)
+ {
+ switch (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);
- }
+ 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/WebSockets/WebSocketAcceptAdapter.cs b/src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs
index f167313f10..f60f159f56 100644
--- a/src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs
+++ b/src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs
@@ -8,95 +8,94 @@ 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
+namespace Microsoft.AspNetCore.Owin;
+
+using AppFunc = Func<IDictionary<string, object>, Task>;
+using WebSocketAccept =
+ Action
+ <
+ IDictionary<string, object>, // WebSocket Accept parameters
+ Func // WebSocketFunc callback
<
- WebSocketAcceptContext, // WebSocket Accept parameters
- Task<WebSocket>
- >;
+ 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 readonly IDictionary<string, object> _env;
+ private readonly WebSocketAcceptAlt _accept;
+ private AppFunc _callback;
+ private IDictionary<string, object> _options;
/// <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.
+ /// Initializes a new instance of <see cref="WebSocketAcceptAdapter"/> for an OWIN environment.
/// </summary>
- public class WebSocketAcceptAdapter
+ /// <param name="env">The OWIN environment.</param>
+ /// <param name="accept">WebSocket accept delegate.</param>
+ public WebSocketAcceptAdapter(IDictionary<string, object> env, WebSocketAcceptAlt accept)
{
- private readonly IDictionary<string, object> _env;
- private readonly WebSocketAcceptAlt _accept;
- private AppFunc _callback;
- private IDictionary<string, object> _options;
-
- /// <summary>
- /// Initializes a new instance of <see cref="WebSocketAcceptAdapter"/> for an OWIN environment.
- /// </summary>
- /// <param name="env">The OWIN environment.</param>
- /// <param name="accept">WebSocket accept delegate.</param>
- public WebSocketAcceptAdapter(IDictionary<string, object> env, WebSocketAcceptAlt accept)
- {
- _env = env;
- _accept = accept;
- }
+ _env = env;
+ _accept = accept;
+ }
- private void AcceptWebSocket(IDictionary<string, object> options, AppFunc callback)
- {
- _options = options;
- _callback = callback;
- _env[OwinConstants.ResponseStatusCode] = 101;
- }
+ private void AcceptWebSocket(IDictionary<string, object> options, AppFunc callback)
+ {
+ _options = options;
+ _callback = callback;
+ _env[OwinConstants.ResponseStatusCode] = 101;
+ }
- /// <summary>
- /// Adapt web sockets to OWIN.
- /// </summary>
- /// <param name="next">The next OWIN app delegate.</param>
- /// <returns>An OWIN app delegate.</returns>
- public static AppFunc AdaptWebSockets(AppFunc next)
+ /// <summary>
+ /// Adapt web sockets to OWIN.
+ /// </summary>
+ /// <param name="next">The next OWIN app delegate.</param>
+ /// <returns>An OWIN app delegate.</returns>
+ public static AppFunc AdaptWebSockets(AppFunc next)
+ {
+ return async environment =>
{
- return async environment =>
+ object accept;
+ if (environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out accept) && accept is WebSocketAcceptAlt)
{
- object accept;
- if (environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out accept) && accept is WebSocketAcceptAlt)
- {
- var adapter = new WebSocketAcceptAdapter(environment, (WebSocketAcceptAlt)accept);
+ 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)
+ 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))
{
- 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();
+ 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);
- }
- };
- }
+ }
+ else
+ {
+ await next(environment);
+ }
+ };
}
}
diff --git a/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs b/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs
index 792aa83b29..3c2dfe4192 100644
--- a/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs
+++ b/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs
@@ -8,167 +8,166 @@ 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 */,
+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 */,
- CancellationToken /* cancel */,
- Task>;
+ int /* count */>>>;
+using WebSocketReceiveTuple =
+ Tuple<int /* messageType */,
+ bool /* endOfMessage */,
+ int /* count */>;
+using WebSocketSendAsync =
+ Func<ArraySegment<byte> /* data */,
+ int /* messageType */,
+ bool /* endOfMessage */,
+ CancellationToken /* cancel */,
+ Task>;
+
+/// <summary>
+/// WebSocket adapter.
+/// </summary>
+public class WebSocketAdapter
+{
+ private readonly WebSocket _webSocket;
+ private readonly IDictionary<string, object> _environment;
+ private readonly CancellationToken _cancellationToken;
- /// <summary>
- /// WebSocket adapter.
- /// </summary>
- public class WebSocketAdapter
+ internal WebSocketAdapter(WebSocket webSocket, CancellationToken ct)
{
- private readonly WebSocket _webSocket;
- private readonly IDictionary<string, object> _environment;
- private readonly CancellationToken _cancellationToken;
+ _webSocket = webSocket;
+ _cancellationToken = ct;
- 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 = 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;
+ }
- _environment[typeof(WebSocket).FullName] = webSocket;
- }
+ internal IDictionary<string, object> Environment
+ {
+ get { return _environment; }
+ }
- internal IDictionary<string, object> 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)
{
- get { return _environment; }
+ return RedirectSendToCloseAsync(buffer, cancel);
}
-
- internal Task SendAsync(ArraySegment<byte> buffer, int messageType, bool endOfMessage, CancellationToken cancel)
+ else if (messageType == 0x9 || messageType == 0xA)
{
- // 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);
+ // Ping & Pong, not allowed by the underlying APIs, silently discard.
+ return Task.CompletedTask;
}
- internal async Task<WebSocketReceiveTuple> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancel)
+ 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)
{
- 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);
+ _environment[OwinConstants.WebSocket.ClientCloseStatus] = (int)(nativeResult.CloseStatus ?? WebSocketCloseStatus.NormalClosure);
+ _environment[OwinConstants.WebSocket.ClientCloseDescription] = nativeResult.CloseStatusDescription ?? string.Empty;
}
- internal Task CloseAsync(int status, string description, CancellationToken cancel)
+ 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 _webSocket.CloseOutputAsync((WebSocketCloseStatus)status, description, cancel);
+ 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);
- private Task RedirectSendToCloseAsync(ArraySegment<byte> buffer, CancellationToken cancel)
+ return CloseAsync(statusCode, description, cancel);
+ }
+ else
{
- 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));
- }
+ throw new ArgumentOutOfRangeException(nameof(buffer));
}
+ }
- internal async Task CleanupAsync()
+ internal async Task CleanupAsync()
+ {
+ switch (_webSocket.State)
{
- 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}.");
- }
+ 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)
+ private static WebSocketMessageType OpCodeToEnum(int messageType)
+ {
+ switch (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);
- }
+ 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)
+ private static int EnumToOpCode(WebSocketMessageType webSocketMessageType)
+ {
+ switch (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);
- }
+ 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/test/OwinEnvironmentTests.cs b/src/Http/Owin/test/OwinEnvironmentTests.cs
index dcdeb847e1..6cb8e415d9 100644
--- a/src/Http/Owin/test/OwinEnvironmentTests.cs
+++ b/src/Http/Owin/test/OwinEnvironmentTests.cs
@@ -10,139 +10,138 @@ using System.Threading;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+public class OwinEnvironmentTests
{
- 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 OwinEnvironmentImplementsGetEnumerator()
+ {
+ var owinEnvironment = new OwinEnvironment(CreateContext());
+
+ Assert.NotNull(owinEnvironment.GetEnumerator());
+ Assert.NotNull(((IEnumerable)owinEnvironment).GetEnumerator());
+ }
+
+ private HttpContext CreateContext()
{
- 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 OwinEnvironmentImplementsGetEnumerator()
- {
- var owinEnvironment = new OwinEnvironment(CreateContext());
-
- Assert.NotNull(owinEnvironment.GetEnumerator());
- Assert.NotNull(((IEnumerable)owinEnvironment).GetEnumerator());
- }
-
- private HttpContext CreateContext()
- {
- var context = new DefaultHttpContext();
- return context;
- }
+ var context = new DefaultHttpContext();
+ return context;
}
}
diff --git a/src/Http/Owin/test/OwinExtensionTests.cs b/src/Http/Owin/test/OwinExtensionTests.cs
index 9bb1d3f038..ca6ea45ba8 100644
--- a/src/Http/Owin/test/OwinExtensionTests.cs
+++ b/src/Http/Owin/test/OwinExtensionTests.cs
@@ -10,154 +10,153 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Owin
+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
{
- 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 readonly AppFunc notFound = env => new Task(() => { env["owin.ResponseStatusCode"] = 404; });
+
+ [Fact]
+ public async Task OwinConfigureServiceProviderAddsServices()
{
- static readonly AppFunc notFound = env => new Task(() => { env["owin.ResponseStatusCode"] = 404; });
+ var list = new List<CreateMiddleware>();
+ AddMiddleware build = list.Add;
+ IServiceProvider serviceProvider = null;
+ FakeService fakeService = null;
- [Fact]
- public async Task OwinConfigureServiceProviderAddsServices()
+ var builder = build.UseBuilder(applicationBuilder =>
{
- 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 =>
{
- 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()
+ 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 =>
{
- 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 =>
{
- 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()
+ 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 =>
{
- 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 =>
{
- builderExecuted = true;
- serviceProvider = applicationBuilder.ApplicationServices;
- applicationBuilder.Run(context =>
- {
- applicationExecuted = true;
- fakeService = context.RequestServices.GetService<FakeService>();
- return Task.FromResult(0);
- });
+ 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>());
+ 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);
- }
+ 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();
+ [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 =>
+ builder.UseOwin(addToPipeline =>
+ {
+ addToPipeline(next =>
{
- addToPipeline(next =>
+ Assert.NotNull(next);
+ return async env =>
{
- Assert.NotNull(next);
- return async env =>
- {
- environment = env;
- await next(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
- {
- }
+ });
+ 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
index b956960d1c..e92dab8444 100644
--- a/src/Http/Owin/test/OwinFeatureCollectionTests.cs
+++ b/src/Http/Owin/test/OwinFeatureCollectionTests.cs
@@ -6,63 +6,62 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Owin
+namespace Microsoft.AspNetCore.Owin;
+
+public class OwinHttpEnvironmentTests
{
- public class OwinHttpEnvironmentTests
+ private T Get<T>(IFeatureCollection features)
{
- private T Get<T>(IFeatureCollection features)
- {
- return (T)features[typeof(T)];
- }
+ 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);
- }
+ 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>
+ [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 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);
- }
+ 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>
+ [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 features = new OwinFeatureCollection(env);
- var requestFeature = Get<IHttpRequestFeature>(features);
- requestFeature.Method = HttpMethods.Get;
- requestFeature.Path = "/path2";
- requestFeature.PathBase = "/pathBase2";
- requestFeature.QueryString = "?name=value2";
+ 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"));
- }
+ 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/Routing.Abstractions/src/IOutboundParameterTransformer.cs b/src/Http/Routing.Abstractions/src/IOutboundParameterTransformer.cs
index c882fac762..308c1f7e11 100644
--- a/src/Http/Routing.Abstractions/src/IOutboundParameterTransformer.cs
+++ b/src/Http/Routing.Abstractions/src/IOutboundParameterTransformer.cs
@@ -1,19 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines the contract that a class must implement to transform route values while building
+/// a URI.
+/// </summary>
+public interface IOutboundParameterTransformer : IParameterPolicy
{
/// <summary>
- /// Defines the contract that a class must implement to transform route values while building
- /// a URI.
+ /// Transforms the specified route value to a string for inclusion in a URI.
/// </summary>
- public interface IOutboundParameterTransformer : IParameterPolicy
- {
- /// <summary>
- /// Transforms the specified route value to a string for inclusion in a URI.
- /// </summary>
- /// <param name="value">The route value to transform.</param>
- /// <returns>The transformed value.</returns>
- string? TransformOutbound(object? value);
- }
+ /// <param name="value">The route value to transform.</param>
+ /// <returns>The transformed value.</returns>
+ string? TransformOutbound(object? value);
}
diff --git a/src/Http/Routing.Abstractions/src/IParameterPolicy.cs b/src/Http/Routing.Abstractions/src/IParameterPolicy.cs
index 25a16dadd9..05fb05bc56 100644
--- a/src/Http/Routing.Abstractions/src/IParameterPolicy.cs
+++ b/src/Http/Routing.Abstractions/src/IParameterPolicy.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A marker interface for types that are associated with route parameters.
+/// </summary>
+public interface IParameterPolicy
{
- /// <summary>
- /// A marker interface for types that are associated with route parameters.
- /// </summary>
- public interface IParameterPolicy
- {
- }
}
diff --git a/src/Http/Routing.Abstractions/src/IRouteConstraint.cs b/src/Http/Routing.Abstractions/src/IRouteConstraint.cs
index a259a18410..1c390b318b 100644
--- a/src/Http/Routing.Abstractions/src/IRouteConstraint.cs
+++ b/src/Http/Routing.Abstractions/src/IRouteConstraint.cs
@@ -3,31 +3,30 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines the contract that a class must implement in order to check whether a URL parameter
+/// value is valid for a constraint.
+/// </summary>
+public interface IRouteConstraint : IParameterPolicy
{
/// <summary>
- /// Defines the contract that a class must implement in order to check whether a URL parameter
- /// value is valid for a constraint.
+ /// Determines whether the URL parameter contains a valid value for this constraint.
/// </summary>
- public interface IRouteConstraint : IParameterPolicy
- {
- /// <summary>
- /// Determines whether the URL parameter contains a valid value for this constraint.
- /// </summary>
- /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
- /// <param name="route">The router that this constraint belongs to.</param>
- /// <param name="routeKey">The name of the parameter that is being checked.</param>
- /// <param name="values">A dictionary that contains the parameters for the URL.</param>
- /// <param name="routeDirection">
- /// An object that indicates whether the constraint check is being performed
- /// when an incoming request is being handled or when a URL is being generated.
- /// </param>
- /// <returns><c>true</c> if the URL parameter contains a valid value; otherwise, <c>false</c>.</returns>
- bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection);
- }
+ /// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
+ /// <param name="route">The router that this constraint belongs to.</param>
+ /// <param name="routeKey">The name of the parameter that is being checked.</param>
+ /// <param name="values">A dictionary that contains the parameters for the URL.</param>
+ /// <param name="routeDirection">
+ /// An object that indicates whether the constraint check is being performed
+ /// when an incoming request is being handled or when a URL is being generated.
+ /// </param>
+ /// <returns><c>true</c> if the URL parameter contains a valid value; otherwise, <c>false</c>.</returns>
+ bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection);
}
diff --git a/src/Http/Routing.Abstractions/src/IRouteHandler.cs b/src/Http/Routing.Abstractions/src/IRouteHandler.cs
index 44d33eddfe..385fd4b8ff 100644
--- a/src/Http/Routing.Abstractions/src/IRouteHandler.cs
+++ b/src/Http/Routing.Abstractions/src/IRouteHandler.cs
@@ -3,22 +3,21 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract for a handler of a route.
+/// </summary>
+public interface IRouteHandler
{
/// <summary>
- /// Defines a contract for a handler of a route.
+ /// Gets a <see cref="RequestDelegate"/> to handle the request, based on the provided
+ /// <paramref name="routeData"/>.
/// </summary>
- public interface IRouteHandler
- {
- /// <summary>
- /// Gets a <see cref="RequestDelegate"/> to handle the request, based on the provided
- /// <paramref name="routeData"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="routeData">The <see cref="RouteData"/> associated with the current routing match.</param>
- /// <returns>
- /// A <see cref="RequestDelegate"/>, or <c>null</c> if the handler cannot handle this request.
- /// </returns>
- RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
- }
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="routeData">The <see cref="RouteData"/> associated with the current routing match.</param>
+ /// <returns>
+ /// A <see cref="RequestDelegate"/>, or <c>null</c> if the handler cannot handle this request.
+ /// </returns>
+ RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData);
}
diff --git a/src/Http/Routing.Abstractions/src/IRouter.cs b/src/Http/Routing.Abstractions/src/IRouter.cs
index d86455546e..8a90c2dd86 100644
--- a/src/Http/Routing.Abstractions/src/IRouter.cs
+++ b/src/Http/Routing.Abstractions/src/IRouter.cs
@@ -3,24 +3,23 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Interface for implementing a router.
+/// </summary>
+public interface IRouter
{
/// <summary>
- /// Interface for implementing a router.
+ /// Asynchronously routes based on the current <paramref name="context"/>.
/// </summary>
- public interface IRouter
- {
- /// <summary>
- /// Asynchronously routes based on the current <paramref name="context"/>.
- /// </summary>
- /// <param name="context">A <see cref="RouteContext"/> instance.</param>
- Task RouteAsync(RouteContext context);
+ /// <param name="context">A <see cref="RouteContext"/> instance.</param>
+ Task RouteAsync(RouteContext context);
- /// <summary>
- /// Returns the URL that is associated with the route details provided in <paramref name="context"/>
- /// </summary>
- /// <param name="context">A <see cref="VirtualPathContext"/> instance.</param>
- /// <returns>A <see cref="VirtualPathData"/> object. Can be null.</returns>
- VirtualPathData? GetVirtualPath(VirtualPathContext context);
- }
+ /// <summary>
+ /// Returns the URL that is associated with the route details provided in <paramref name="context"/>
+ /// </summary>
+ /// <param name="context">A <see cref="VirtualPathContext"/> instance.</param>
+ /// <returns>A <see cref="VirtualPathData"/> object. Can be null.</returns>
+ VirtualPathData? GetVirtualPath(VirtualPathContext context);
}
diff --git a/src/Http/Routing.Abstractions/src/IRoutingFeature.cs b/src/Http/Routing.Abstractions/src/IRoutingFeature.cs
index 9a4f84e56b..d2d69bb725 100644
--- a/src/Http/Routing.Abstractions/src/IRoutingFeature.cs
+++ b/src/Http/Routing.Abstractions/src/IRoutingFeature.cs
@@ -3,16 +3,15 @@
#nullable enable
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A feature interface for routing functionality.
+/// </summary>
+public interface IRoutingFeature
{
/// <summary>
- /// A feature interface for routing functionality.
+ /// Gets or sets the <see cref="Routing.RouteData"/> associated with the current request.
/// </summary>
- public interface IRoutingFeature
- {
- /// <summary>
- /// Gets or sets the <see cref="Routing.RouteData"/> associated with the current request.
- /// </summary>
- RouteData? RouteData { get; set; }
- }
+ RouteData? RouteData { get; set; }
}
diff --git a/src/Http/Routing.Abstractions/src/LinkGenerator.cs b/src/Http/Routing.Abstractions/src/LinkGenerator.cs
index 88a4361ce9..1a67311653 100644
--- a/src/Http/Routing.Abstractions/src/LinkGenerator.cs
+++ b/src/Http/Routing.Abstractions/src/LinkGenerator.cs
@@ -4,153 +4,152 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract to generate absolute and related URIs based on endpoint routing.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Generating URIs in endpoint routing occurs in two phases. First, an address is bound to a list of
+/// endpoints that match the address. Secondly, each endpoint's <c>RoutePattern</c> is evaluated, until
+/// a route pattern that matches the supplied values is found. The resulting output is combined with
+/// the other URI parts supplied to the link generator and returned.
+/// </para>
+/// <para>
+/// The methods provided by the <see cref="LinkGenerator"/> type are general infrastructure, and support
+/// the standard link generator functionality for any type of address. The most convenient way to use
+/// <see cref="LinkGenerator"/> is through extension methods that perform operations for a specific
+/// address type.
+/// </para>
+/// </remarks>
+public abstract class LinkGenerator
{
/// <summary>
- /// Defines a contract to generate absolute and related URIs based on endpoint routing.
+ /// Generates a URI with an absolute path based on the provided values and <see cref="HttpContext"/>.
+ /// </summary>
+ /// <typeparam name="TAddress">The address type.</typeparam>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="address">The address value. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template.</param>
+ /// <param name="ambientValues">The values associated with the current request. Optional.</param>
+ /// <param name="pathBase">
+ /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
+ /// </param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public abstract string? GetPathByAddress<TAddress>(
+ HttpContext httpContext,
+ TAddress address,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues = default,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default);
+
+ /// <summary>
+ /// Generates a URI with an absolute path based on the provided values.
/// </summary>
+ /// <typeparam name="TAddress">The address type.</typeparam>
+ /// <param name="address">The address value. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template.</param>
+ /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public abstract string? GetPathByAddress<TAddress>(
+ TAddress address,
+ RouteValueDictionary values,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default);
+
+ /// <summary>
+ /// Generates an absolute URI based on the provided values and <see cref="HttpContext"/>.
+ /// </summary>
+ /// <typeparam name="TAddress">The address type.</typeparam>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="address">The address value. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template.</param>
+ /// <param name="ambientValues">The values associated with the current request. Optional.</param>
+ /// <param name="scheme">
+ /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
+ /// </param>
+ /// <param name="host">
+ /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
+ /// See the remarks section for details about the security implications of the <paramref name="host"/>.
+ /// </param>
+ /// <param name="pathBase">
+ /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
+ /// </param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
/// <remarks>
/// <para>
- /// Generating URIs in endpoint routing occurs in two phases. First, an address is bound to a list of
- /// endpoints that match the address. Secondly, each endpoint's <c>RoutePattern</c> is evaluated, until
- /// a route pattern that matches the supplied values is found. The resulting output is combined with
- /// the other URI parts supplied to the link generator and returned.
+ /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
+ /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
+ /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
+ /// your deployment environment.
/// </para>
+ /// </remarks>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public abstract string? GetUriByAddress<TAddress>(
+ HttpContext httpContext,
+ TAddress address,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues = default,
+ string? scheme = default,
+ HostString? host = default,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default);
+
+ /// <summary>
+ /// Generates an absolute URI based on the provided values.
+ /// </summary>
+ /// <typeparam name="TAddress">The address type.</typeparam>
+ /// <param name="address">The address value. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template.</param>
+ /// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
+ /// <param name="host">
+ /// The URI host/authority, applied to the resulting URI.
+ /// See the remarks section for details about the security implications of the <paramref name="host"/>.
+ /// </param>
+ /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>An absolute URI, or <c>null</c>.</returns>
+ /// <remarks>
/// <para>
- /// The methods provided by the <see cref="LinkGenerator"/> type are general infrastructure, and support
- /// the standard link generator functionality for any type of address. The most convenient way to use
- /// <see cref="LinkGenerator"/> is through extension methods that perform operations for a specific
- /// address type.
+ /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
+ /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
+ /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
+ /// your deployment environment.
/// </para>
/// </remarks>
- public abstract class LinkGenerator
- {
- /// <summary>
- /// Generates a URI with an absolute path based on the provided values and <see cref="HttpContext"/>.
- /// </summary>
- /// <typeparam name="TAddress">The address type.</typeparam>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="address">The address value. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template.</param>
- /// <param name="ambientValues">The values associated with the current request. Optional.</param>
- /// <param name="pathBase">
- /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
- /// </param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public abstract string? GetPathByAddress<TAddress>(
- HttpContext httpContext,
- TAddress address,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues = default,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default);
-
- /// <summary>
- /// Generates a URI with an absolute path based on the provided values.
- /// </summary>
- /// <typeparam name="TAddress">The address type.</typeparam>
- /// <param name="address">The address value. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template.</param>
- /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public abstract string? GetPathByAddress<TAddress>(
- TAddress address,
- RouteValueDictionary values,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default);
-
- /// <summary>
- /// Generates an absolute URI based on the provided values and <see cref="HttpContext"/>.
- /// </summary>
- /// <typeparam name="TAddress">The address type.</typeparam>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="address">The address value. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template.</param>
- /// <param name="ambientValues">The values associated with the current request. Optional.</param>
- /// <param name="scheme">
- /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
- /// </param>
- /// <param name="host">
- /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
- /// See the remarks section for details about the security implications of the <paramref name="host"/>.
- /// </param>
- /// <param name="pathBase">
- /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
- /// </param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- /// <remarks>
- /// <para>
- /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
- /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
- /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
- /// your deployment environment.
- /// </para>
- /// </remarks>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public abstract string? GetUriByAddress<TAddress>(
- HttpContext httpContext,
- TAddress address,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues = default,
- string? scheme = default,
- HostString? host = default,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default);
-
- /// <summary>
- /// Generates an absolute URI based on the provided values.
- /// </summary>
- /// <typeparam name="TAddress">The address type.</typeparam>
- /// <param name="address">The address value. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template.</param>
- /// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
- /// <param name="host">
- /// The URI host/authority, applied to the resulting URI.
- /// See the remarks section for details about the security implications of the <paramref name="host"/>.
- /// </param>
- /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>An absolute URI, or <c>null</c>.</returns>
- /// <remarks>
- /// <para>
- /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
- /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
- /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
- /// your deployment environment.
- /// </para>
- /// </remarks>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public abstract string? GetUriByAddress<TAddress>(
- TAddress address,
- RouteValueDictionary values,
- string? scheme,
- HostString host,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default);
- }
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public abstract string? GetUriByAddress<TAddress>(
+ TAddress address,
+ RouteValueDictionary values,
+ string? scheme,
+ HostString host,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default);
}
diff --git a/src/Http/Routing.Abstractions/src/LinkOptions.cs b/src/Http/Routing.Abstractions/src/LinkOptions.cs
index 1c7e5f2165..83d5ec8062 100644
--- a/src/Http/Routing.Abstractions/src/LinkOptions.cs
+++ b/src/Http/Routing.Abstractions/src/LinkOptions.cs
@@ -1,28 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Configures options for generated URLs.
+/// </summary>
+public class LinkOptions
{
/// <summary>
- /// Configures options for generated URLs.
+ /// Gets or sets a value indicating whether all generated paths URLs are lowercase.
+ /// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
/// </summary>
- public class LinkOptions
- {
- /// <summary>
- /// Gets or sets a value indicating whether all generated paths URLs are lowercase.
- /// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
- /// </summary>
- public bool? LowercaseUrls { get; set; }
+ public bool? LowercaseUrls { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether a generated query strings are lowercase.
- /// This property will be false unless <see cref="LowercaseUrls" /> is also <c>true</c>.
- /// </summary>
- public bool? LowercaseQueryStrings { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether a generated query strings are lowercase.
+ /// This property will be false unless <see cref="LowercaseUrls" /> is also <c>true</c>.
+ /// </summary>
+ public bool? LowercaseQueryStrings { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
- /// </summary>
- public bool? AppendTrailingSlash { get; set; }
- }
+ /// <summary>
+ /// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
+ /// </summary>
+ public bool? AppendTrailingSlash { get; set; }
}
diff --git a/src/Http/Routing.Abstractions/src/Properties/AssemblyInfo.cs b/src/Http/Routing.Abstractions/src/Properties/AssemblyInfo.cs
index b0ee28d60e..bd79df6b37 100644
--- a/src/Http/Routing.Abstractions/src/Properties/AssemblyInfo.cs
+++ b/src/Http/Routing.Abstractions/src/Properties/AssemblyInfo.cs
@@ -2,9 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.CompilerServices;
-using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Routing;
[assembly: TypeForwardedTo(typeof(IEndpointFeature))]
[assembly: TypeForwardedTo(typeof(IRouteValuesFeature))]
diff --git a/src/Http/Routing.Abstractions/src/RouteContext.cs b/src/Http/Routing.Abstractions/src/RouteContext.cs
index 5bf51a0a00..09a4c4dd0c 100644
--- a/src/Http/Routing.Abstractions/src/RouteContext.cs
+++ b/src/Http/Routing.Abstractions/src/RouteContext.cs
@@ -4,55 +4,54 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A context object for <see cref="IRouter.RouteAsync(RouteContext)"/>.
+/// </summary>
+public class RouteContext
{
+ private RouteData _routeData;
+
/// <summary>
- /// A context object for <see cref="IRouter.RouteAsync(RouteContext)"/>.
+ /// Creates a new instance of <see cref="RouteContext"/> for the provided <paramref name="httpContext"/>.
/// </summary>
- public class RouteContext
+ /// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
+ public RouteContext(HttpContext httpContext)
{
- private RouteData _routeData;
+ HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
- /// <summary>
- /// Creates a new instance of <see cref="RouteContext"/> for the provided <paramref name="httpContext"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
- public RouteContext(HttpContext httpContext)
- {
- HttpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
+ RouteData = new RouteData();
+ }
- RouteData = new RouteData();
- }
+ /// <summary>
+ /// Gets or sets the handler for the request. An <see cref="IRouter"/> should set <see cref="Handler"/>
+ /// when it matches.
+ /// </summary>
+ public RequestDelegate? Handler { get; set; }
- /// <summary>
- /// Gets or sets the handler for the request. An <see cref="IRouter"/> should set <see cref="Handler"/>
- /// when it matches.
- /// </summary>
- public RequestDelegate? Handler { get; set; }
-
- /// <summary>
- /// Gets the <see cref="Http.HttpContext"/> associated with the current request.
- /// </summary>
- public HttpContext HttpContext { get; }
-
- /// <summary>
- /// Gets or sets the <see cref="Routing.RouteData"/> associated with the current context.
- /// </summary>
- public RouteData RouteData
+ /// <summary>
+ /// Gets the <see cref="Http.HttpContext"/> associated with the current request.
+ /// </summary>
+ public HttpContext HttpContext { get; }
+
+ /// <summary>
+ /// Gets or sets the <see cref="Routing.RouteData"/> associated with the current context.
+ /// </summary>
+ public RouteData RouteData
+ {
+ get
{
- get
+ return _routeData;
+ }
+ set
+ {
+ if (value == null)
{
- return _routeData;
+ throw new ArgumentNullException(nameof(RouteData));
}
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(RouteData));
- }
- _routeData = value;
- }
+ _routeData = value;
}
}
}
diff --git a/src/Http/Routing.Abstractions/src/RouteData.cs b/src/Http/Routing.Abstractions/src/RouteData.cs
index 3cd807f85f..cd1a4df2fe 100644
--- a/src/Http/Routing.Abstractions/src/RouteData.cs
+++ b/src/Http/Routing.Abstractions/src/RouteData.cs
@@ -7,307 +7,306 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Information about the current routing path.
+/// </summary>
+public class RouteData
{
+ private RouteValueDictionary? _dataTokens;
+ private List<IRouter>? _routers;
+ private RouteValueDictionary? _values;
+
/// <summary>
- /// Information about the current routing path.
+ /// Creates a new instance of <see cref="RouteData"/> instance.
/// </summary>
- public class RouteData
+ public RouteData()
{
- private RouteValueDictionary? _dataTokens;
- private List<IRouter>? _routers;
- private RouteValueDictionary? _values;
+ // Perf: Avoid allocating collections unless needed.
+ }
- /// <summary>
- /// Creates a new instance of <see cref="RouteData"/> instance.
- /// </summary>
- public RouteData()
+ /// <summary>
+ /// Creates a new instance of <see cref="RouteData"/> instance with values copied from <paramref name="other"/>.
+ /// </summary>
+ /// <param name="other">The other <see cref="RouteData"/> instance to copy.</param>
+ public RouteData(RouteData other)
+ {
+ if (other == null)
{
- // Perf: Avoid allocating collections unless needed.
+ throw new ArgumentNullException(nameof(other));
}
- /// <summary>
- /// Creates a new instance of <see cref="RouteData"/> instance with values copied from <paramref name="other"/>.
- /// </summary>
- /// <param name="other">The other <see cref="RouteData"/> instance to copy.</param>
- public RouteData(RouteData other)
+ // Perf: Avoid allocating collections unless we need to make a copy.
+ if (other._routers != null)
{
- if (other == null)
- {
- throw new ArgumentNullException(nameof(other));
- }
+ _routers = new List<IRouter>(other.Routers);
+ }
- // Perf: Avoid allocating collections unless we need to make a copy.
- if (other._routers != null)
+ if (other._dataTokens != null)
+ {
+ _dataTokens = new RouteValueDictionary(other._dataTokens);
+ }
+
+ if (other._values != null)
+ {
+ _values = new RouteValueDictionary(other._values);
+ }
+ }
+
+ /// <summary>
+ /// Creates a new instance of <see cref="RouteData"/> instance with the specified values.
+ /// </summary>
+ /// <param name="values">The <see cref="RouteValueDictionary"/> values.</param>
+ public RouteData(RouteValueDictionary values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
+
+ _values = values;
+ }
+
+ /// <summary>
+ /// Gets the data tokens produced by routes on the current routing path.
+ /// </summary>
+ public RouteValueDictionary DataTokens
+ {
+ get
+ {
+ if (_dataTokens == null)
{
- _routers = new List<IRouter>(other.Routers);
+ _dataTokens = new RouteValueDictionary();
}
- if (other._dataTokens != null)
+ return _dataTokens;
+ }
+ }
+
+ /// <summary>
+ /// Gets the list of <see cref="IRouter"/> instances on the current routing path.
+ /// </summary>
+ public IList<IRouter> Routers
+ {
+ get
+ {
+ if (_routers == null)
{
- _dataTokens = new RouteValueDictionary(other._dataTokens);
+ _routers = new List<IRouter>();
}
- if (other._values != null)
+ return _routers;
+ }
+ }
+
+ /// <summary>
+ /// Gets the values produced by routes on the current routing path.
+ /// </summary>
+ public RouteValueDictionary Values
+ {
+ get
+ {
+ if (_values == null)
{
- _values = new RouteValueDictionary(other._values);
+ _values = new RouteValueDictionary();
}
+
+ return _values;
}
+ }
- /// <summary>
- /// Creates a new instance of <see cref="RouteData"/> instance with the specified values.
- /// </summary>
- /// <param name="values">The <see cref="RouteValueDictionary"/> values.</param>
- public RouteData(RouteValueDictionary values)
+ /// <summary>
+ /// <para>
+ /// Creates a snapshot of the current state of the <see cref="RouteData"/> before appending
+ /// <paramref name="router"/> to <see cref="Routers"/>, merging <paramref name="values"/> into
+ /// <see cref="Values"/>, and merging <paramref name="dataTokens"/> into <see cref="DataTokens"/>.
+ /// </para>
+ /// <para>
+ /// Call <see cref="RouteDataSnapshot.Restore"/> to restore the state of this <see cref="RouteData"/>
+ /// to the state at the time of calling
+ /// <see cref="PushState(IRouter, RouteValueDictionary, RouteValueDictionary)"/>.
+ /// </para>
+ /// </summary>
+ /// <param name="router">
+ /// An <see cref="IRouter"/> to append to <see cref="Routers"/>. If <c>null</c>, then <see cref="Routers"/>
+ /// will not be changed.
+ /// </param>
+ /// <param name="values">
+ /// A <see cref="RouteValueDictionary"/> to merge into <see cref="Values"/>. If <c>null</c>, then
+ /// <see cref="Values"/> will not be changed.
+ /// </param>
+ /// <param name="dataTokens">
+ /// A <see cref="RouteValueDictionary"/> to merge into <see cref="DataTokens"/>. If <c>null</c>, then
+ /// <see cref="DataTokens"/> will not be changed.
+ /// </param>
+ /// <returns>A <see cref="RouteDataSnapshot"/> that captures the current state.</returns>
+ public RouteDataSnapshot PushState(IRouter? router, RouteValueDictionary? values, RouteValueDictionary? dataTokens)
+ {
+ // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
+ // Array.CopyTo inside the List(IEnumerable<T>) constructor.
+ List<IRouter>? routers = null;
+ var count = _routers?.Count;
+ if (count > 0)
{
- if (values == null)
+ Debug.Assert(_routers != null);
+
+ routers = new List<IRouter>(count.Value);
+ for (var i = 0; i < count.Value; i++)
{
- throw new ArgumentNullException(nameof(values));
+ routers.Add(_routers[i]);
}
+ }
- _values = values;
+ var snapshot = new RouteDataSnapshot(
+ this,
+ _dataTokens?.Count > 0 ? new RouteValueDictionary(_dataTokens) : null,
+ routers,
+ _values?.Count > 0 ? new RouteValueDictionary(_values) : null);
+
+ if (router != null)
+ {
+ Routers.Add(router);
}
- /// <summary>
- /// Gets the data tokens produced by routes on the current routing path.
- /// </summary>
- public RouteValueDictionary DataTokens
+ if (values != null)
{
- get
+ foreach (var kvp in values)
{
- if (_dataTokens == null)
+ if (kvp.Value != null)
{
- _dataTokens = new RouteValueDictionary();
+ Values[kvp.Key] = kvp.Value;
}
-
- return _dataTokens;
}
}
- /// <summary>
- /// Gets the list of <see cref="IRouter"/> instances on the current routing path.
- /// </summary>
- public IList<IRouter> Routers
+ if (dataTokens != null)
{
- get
+ foreach (var kvp in dataTokens)
{
- if (_routers == null)
- {
- _routers = new List<IRouter>();
- }
-
- return _routers;
+ DataTokens[kvp.Key] = kvp.Value;
}
}
+ return snapshot;
+ }
+
+ /// <summary>
+ /// A snapshot of the state of a <see cref="RouteData"/> instance.
+ /// </summary>
+ public readonly struct RouteDataSnapshot
+ {
+ private readonly RouteData _routeData;
+ private readonly RouteValueDictionary? _dataTokens;
+ private readonly IList<IRouter>? _routers;
+ private readonly RouteValueDictionary? _values;
+
/// <summary>
- /// Gets the values produced by routes on the current routing path.
+ /// Creates a new instance of <see cref="RouteDataSnapshot"/> for <paramref name="routeData"/>.
/// </summary>
- public RouteValueDictionary Values
+ /// <param name="routeData">The <see cref="RouteData"/>.</param>
+ /// <param name="dataTokens">The data tokens.</param>
+ /// <param name="routers">The routers.</param>
+ /// <param name="values">The route values.</param>
+ public RouteDataSnapshot(
+ RouteData routeData,
+ RouteValueDictionary? dataTokens,
+ IList<IRouter>? routers,
+ RouteValueDictionary? values)
{
- get
+ if (routeData == null)
{
- if (_values == null)
- {
- _values = new RouteValueDictionary();
- }
-
- return _values;
+ throw new ArgumentNullException(nameof(routeData));
}
+
+ _routeData = routeData;
+ _dataTokens = dataTokens;
+ _routers = routers;
+ _values = values;
}
/// <summary>
- /// <para>
- /// Creates a snapshot of the current state of the <see cref="RouteData"/> before appending
- /// <paramref name="router"/> to <see cref="Routers"/>, merging <paramref name="values"/> into
- /// <see cref="Values"/>, and merging <paramref name="dataTokens"/> into <see cref="DataTokens"/>.
- /// </para>
- /// <para>
- /// Call <see cref="RouteDataSnapshot.Restore"/> to restore the state of this <see cref="RouteData"/>
- /// to the state at the time of calling
- /// <see cref="PushState(IRouter, RouteValueDictionary, RouteValueDictionary)"/>.
- /// </para>
+ /// Restores the <see cref="RouteData"/> to the captured state.
/// </summary>
- /// <param name="router">
- /// An <see cref="IRouter"/> to append to <see cref="Routers"/>. If <c>null</c>, then <see cref="Routers"/>
- /// will not be changed.
- /// </param>
- /// <param name="values">
- /// A <see cref="RouteValueDictionary"/> to merge into <see cref="Values"/>. If <c>null</c>, then
- /// <see cref="Values"/> will not be changed.
- /// </param>
- /// <param name="dataTokens">
- /// A <see cref="RouteValueDictionary"/> to merge into <see cref="DataTokens"/>. If <c>null</c>, then
- /// <see cref="DataTokens"/> will not be changed.
- /// </param>
- /// <returns>A <see cref="RouteDataSnapshot"/> that captures the current state.</returns>
- public RouteDataSnapshot PushState(IRouter? router, RouteValueDictionary? values, RouteValueDictionary? dataTokens)
+ public void Restore()
{
- // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
- // Array.CopyTo inside the List(IEnumerable<T>) constructor.
- List<IRouter>? routers = null;
- var count = _routers?.Count;
- if (count > 0)
+ if (_routeData._dataTokens == null && _dataTokens == null)
{
- Debug.Assert(_routers != null);
-
- routers = new List<IRouter>(count.Value);
- for (var i = 0; i < count.Value; i++)
- {
- routers.Add(_routers[i]);
- }
+ // Do nothing
}
-
- var snapshot = new RouteDataSnapshot(
- this,
- _dataTokens?.Count > 0 ? new RouteValueDictionary(_dataTokens) : null,
- routers,
- _values?.Count > 0 ? new RouteValueDictionary(_values) : null);
-
- if (router != null)
+ else if (_dataTokens == null)
{
- Routers.Add(router);
+ _routeData._dataTokens!.Clear();
}
-
- if (values != null)
+ else
{
- foreach (var kvp in values)
+ _routeData._dataTokens!.Clear();
+
+ foreach (var kvp in _dataTokens)
{
- if (kvp.Value != null)
- {
- Values[kvp.Key] = kvp.Value;
- }
+ _routeData._dataTokens.Add(kvp.Key, kvp.Value);
}
}
- if (dataTokens != null)
+ if (_routeData._routers == null && _routers == null)
{
- foreach (var kvp in dataTokens)
- {
- DataTokens[kvp.Key] = kvp.Value;
- }
+ // Do nothing
}
-
- return snapshot;
- }
-
- /// <summary>
- /// A snapshot of the state of a <see cref="RouteData"/> instance.
- /// </summary>
- public readonly struct RouteDataSnapshot
- {
- private readonly RouteData _routeData;
- private readonly RouteValueDictionary? _dataTokens;
- private readonly IList<IRouter>? _routers;
- private readonly RouteValueDictionary? _values;
-
- /// <summary>
- /// Creates a new instance of <see cref="RouteDataSnapshot"/> for <paramref name="routeData"/>.
- /// </summary>
- /// <param name="routeData">The <see cref="RouteData"/>.</param>
- /// <param name="dataTokens">The data tokens.</param>
- /// <param name="routers">The routers.</param>
- /// <param name="values">The route values.</param>
- public RouteDataSnapshot(
- RouteData routeData,
- RouteValueDictionary? dataTokens,
- IList<IRouter>? routers,
- RouteValueDictionary? values)
+ else if (_routers == null)
{
- if (routeData == null)
+ // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
+ // Array.Clear inside the List.Clear() method.
+ var routers = _routeData._routers!;
+ for (var i = routers.Count - 1; i >= 0; i--)
{
- throw new ArgumentNullException(nameof(routeData));
+ routers.RemoveAt(i);
}
-
- _routeData = routeData;
- _dataTokens = dataTokens;
- _routers = routers;
- _values = values;
}
-
- /// <summary>
- /// Restores the <see cref="RouteData"/> to the captured state.
- /// </summary>
- public void Restore()
+ else
{
- if (_routeData._dataTokens == null && _dataTokens == null)
- {
- // Do nothing
- }
- else if (_dataTokens == null)
+ // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
+ // Array.Clear inside the List.Clear() method.
+ //
+ // We want to basically copy the contents of _routers in _routeData._routers - this change does
+ // that with the minimal number of reads/writes and without calling Clear().
+ var routers = _routeData._routers!;
+ var snapshotRouters = _routers;
+
+ // This is made more complicated by the fact that List[int] throws if i == Count, so we have
+ // to do two loops and call Add for those cases.
+ var i = 0;
+ for (; i < snapshotRouters.Count && i < routers.Count; i++)
{
- _routeData._dataTokens!.Clear();
+ routers[i] = snapshotRouters[i];
}
- else
- {
- _routeData._dataTokens!.Clear();
- foreach (var kvp in _dataTokens)
- {
- _routeData._dataTokens.Add(kvp.Key, kvp.Value);
- }
- }
-
- if (_routeData._routers == null && _routers == null)
- {
- // Do nothing
- }
- else if (_routers == null)
- {
- // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
- // Array.Clear inside the List.Clear() method.
- var routers = _routeData._routers!;
- for (var i = routers.Count - 1; i >= 0 ; i--)
- {
- routers.RemoveAt(i);
- }
- }
- else
+ for (; i < snapshotRouters.Count; i++)
{
- // Perf: this is optimized for small list sizes, in particular to avoid overhead of a native call in
- // Array.Clear inside the List.Clear() method.
- //
- // We want to basically copy the contents of _routers in _routeData._routers - this change does
- // that with the minimal number of reads/writes and without calling Clear().
- var routers = _routeData._routers!;
- var snapshotRouters = _routers;
-
- // This is made more complicated by the fact that List[int] throws if i == Count, so we have
- // to do two loops and call Add for those cases.
- var i = 0;
- for (; i < snapshotRouters.Count && i < routers.Count; i++)
- {
- routers[i] = snapshotRouters[i];
- }
-
- for (; i < snapshotRouters.Count; i++)
- {
- routers.Add(snapshotRouters[i]);
- }
-
- // Trim excess - again avoiding RemoveRange because it uses native methods.
- for (i = routers.Count - 1; i >= snapshotRouters.Count; i--)
- {
- routers.RemoveAt(i);
- }
+ routers.Add(snapshotRouters[i]);
}
- if (_routeData._values == null && _values == null)
+ // Trim excess - again avoiding RemoveRange because it uses native methods.
+ for (i = routers.Count - 1; i >= snapshotRouters.Count; i--)
{
- // Do nothing
+ routers.RemoveAt(i);
}
- else if (_values == null)
- {
- _routeData._values!.Clear();
- }
- else
- {
- _routeData._values!.Clear();
+ }
- foreach (var kvp in _values)
- {
- _routeData._values.Add(kvp.Key, kvp.Value);
- }
+ if (_routeData._values == null && _values == null)
+ {
+ // Do nothing
+ }
+ else if (_values == null)
+ {
+ _routeData._values!.Clear();
+ }
+ else
+ {
+ _routeData._values!.Clear();
+
+ foreach (var kvp in _values)
+ {
+ _routeData._values.Add(kvp.Key, kvp.Value);
}
}
}
diff --git a/src/Http/Routing.Abstractions/src/RouteDirection.cs b/src/Http/Routing.Abstractions/src/RouteDirection.cs
index cc98d09d6b..bf5629c174 100644
--- a/src/Http/Routing.Abstractions/src/RouteDirection.cs
+++ b/src/Http/Routing.Abstractions/src/RouteDirection.cs
@@ -1,21 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Indicates whether ASP.NET routing is processing a URL from an HTTP request or generating a URL.
+/// </summary>
+public enum RouteDirection
{
/// <summary>
- /// Indicates whether ASP.NET routing is processing a URL from an HTTP request or generating a URL.
+ /// A URL from a client is being processed.
/// </summary>
- public enum RouteDirection
- {
- /// <summary>
- /// A URL from a client is being processed.
- /// </summary>
- IncomingRequest,
+ IncomingRequest,
- /// <summary>
- /// A URL is being created based on the route definition.
- /// </summary>
- UrlGeneration,
- }
+ /// <summary>
+ /// A URL is being created based on the route definition.
+ /// </summary>
+ UrlGeneration,
}
diff --git a/src/Http/Routing.Abstractions/src/RoutingHttpContextExtensions.cs b/src/Http/Routing.Abstractions/src/RoutingHttpContextExtensions.cs
index 9c613e077d..c23cd467ec 100644
--- a/src/Http/Routing.Abstractions/src/RoutingHttpContextExtensions.cs
+++ b/src/Http/Routing.Abstractions/src/RoutingHttpContextExtensions.cs
@@ -7,49 +7,48 @@ using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Extension methods for <see cref="HttpContext"/> related to routing.
+/// </summary>
+public static class RoutingHttpContextExtensions
{
/// <summary>
- /// Extension methods for <see cref="HttpContext"/> related to routing.
+ /// Gets the <see cref="RouteData"/> associated with the provided <paramref name="httpContext"/>.
/// </summary>
- public static class RoutingHttpContextExtensions
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <returns>The <see cref="RouteData"/>.</returns>
+ public static RouteData GetRouteData(this HttpContext httpContext)
{
- /// <summary>
- /// Gets the <see cref="RouteData"/> associated with the provided <paramref name="httpContext"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <returns>The <see cref="RouteData"/>.</returns>
- public static RouteData GetRouteData(this HttpContext httpContext)
+ if (httpContext == null)
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var routingFeature = httpContext.Features.Get<IRoutingFeature>();
- return routingFeature?.RouteData ?? new RouteData(httpContext.Request.RouteValues);
+ throw new ArgumentNullException(nameof(httpContext));
}
- /// <summary>
- /// Gets a route value from <see cref="RouteData.Values"/> associated with the provided
- /// <paramref name="httpContext"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="key">The key of the route value.</param>
- /// <returns>The corresponding route value, or null.</returns>
- public static object? GetRouteValue(this HttpContext httpContext, string key)
- {
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
+ var routingFeature = httpContext.Features.Get<IRoutingFeature>();
+ return routingFeature?.RouteData ?? new RouteData(httpContext.Request.RouteValues);
+ }
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
+ /// <summary>
+ /// Gets a route value from <see cref="RouteData.Values"/> associated with the provided
+ /// <paramref name="httpContext"/>.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="key">The key of the route value.</param>
+ /// <returns>The corresponding route value, or null.</returns>
+ public static object? GetRouteValue(this HttpContext httpContext, string key)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
- return httpContext.Features.Get<IRouteValuesFeature>()?.RouteValues[key];
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
}
+
+ return httpContext.Features.Get<IRouteValuesFeature>()?.RouteValues[key];
}
}
diff --git a/src/Http/Routing.Abstractions/src/VirtualPathContext.cs b/src/Http/Routing.Abstractions/src/VirtualPathContext.cs
index a296a048ce..5259f71042 100644
--- a/src/Http/Routing.Abstractions/src/VirtualPathContext.cs
+++ b/src/Http/Routing.Abstractions/src/VirtualPathContext.cs
@@ -3,64 +3,63 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A context for virtual path generation operations.
+/// </summary>
+public class VirtualPathContext
{
/// <summary>
- /// A context for virtual path generation operations.
+ /// Creates a new instance of <see cref="VirtualPathContext"/>.
/// </summary>
- public class VirtualPathContext
+ /// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
+ /// <param name="ambientValues">The set of route values associated with the current request.</param>
+ /// <param name="values">The set of new values provided for virtual path generation.</param>
+ public VirtualPathContext(
+ HttpContext httpContext,
+ RouteValueDictionary ambientValues,
+ RouteValueDictionary values)
+ : this(httpContext, ambientValues, values, null)
{
- /// <summary>
- /// Creates a new instance of <see cref="VirtualPathContext"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
- /// <param name="ambientValues">The set of route values associated with the current request.</param>
- /// <param name="values">The set of new values provided for virtual path generation.</param>
- public VirtualPathContext(
- HttpContext httpContext,
- RouteValueDictionary ambientValues,
- RouteValueDictionary values)
- : this(httpContext, ambientValues, values, null)
- {
- }
+ }
- /// <summary>
- /// Creates a new instance of <see cref="VirtualPathContext"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
- /// <param name="ambientValues">The set of route values associated with the current request.</param>
- /// <param name="values">The set of new values provided for virtual path generation.</param>
- /// <param name="routeName">The name of the route to use for virtual path generation.</param>
- public VirtualPathContext(
- HttpContext httpContext,
- RouteValueDictionary ambientValues,
- RouteValueDictionary values,
- string? routeName)
- {
- HttpContext = httpContext;
- AmbientValues = ambientValues;
- Values = values;
- RouteName = routeName;
- }
+ /// <summary>
+ /// Creates a new instance of <see cref="VirtualPathContext"/>.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="Http.HttpContext"/> associated with the current request.</param>
+ /// <param name="ambientValues">The set of route values associated with the current request.</param>
+ /// <param name="values">The set of new values provided for virtual path generation.</param>
+ /// <param name="routeName">The name of the route to use for virtual path generation.</param>
+ public VirtualPathContext(
+ HttpContext httpContext,
+ RouteValueDictionary ambientValues,
+ RouteValueDictionary values,
+ string? routeName)
+ {
+ HttpContext = httpContext;
+ AmbientValues = ambientValues;
+ Values = values;
+ RouteName = routeName;
+ }
- /// <summary>
- /// Gets the set of route values associated with the current request.
- /// </summary>
- public RouteValueDictionary AmbientValues { get; }
+ /// <summary>
+ /// Gets the set of route values associated with the current request.
+ /// </summary>
+ public RouteValueDictionary AmbientValues { get; }
- /// <summary>
- /// Gets the <see cref="Http.HttpContext"/> associated with the current request.
- /// </summary>
- public HttpContext HttpContext { get; }
+ /// <summary>
+ /// Gets the <see cref="Http.HttpContext"/> associated with the current request.
+ /// </summary>
+ public HttpContext HttpContext { get; }
- /// <summary>
- /// Gets the name of the route to use for virtual path generation.
- /// </summary>
- public string? RouteName { get; }
+ /// <summary>
+ /// Gets the name of the route to use for virtual path generation.
+ /// </summary>
+ public string? RouteName { get; }
- /// <summary>
- /// Gets or sets the set of new values provided for virtual path generation.
- /// </summary>
- public RouteValueDictionary Values { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the set of new values provided for virtual path generation.
+ /// </summary>
+ public RouteValueDictionary Values { get; set; }
}
diff --git a/src/Http/Routing.Abstractions/src/VirtualPathData.cs b/src/Http/Routing.Abstractions/src/VirtualPathData.cs
index a2f042ce58..c1b3c97d55 100644
--- a/src/Http/Routing.Abstractions/src/VirtualPathData.cs
+++ b/src/Http/Routing.Abstractions/src/VirtualPathData.cs
@@ -3,97 +3,96 @@
using System;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents information about the route and virtual path that are the result of
+/// generating a URL with the ASP.NET routing middleware.
+/// </summary>
+public class VirtualPathData
{
+ private RouteValueDictionary _dataTokens;
+ private string _virtualPath;
+
/// <summary>
- /// Represents information about the route and virtual path that are the result of
- /// generating a URL with the ASP.NET routing middleware.
+ /// Initializes a new instance of the <see cref="VirtualPathData"/> class.
/// </summary>
- public class VirtualPathData
+ /// <param name="router">The object that is used to generate the URL.</param>
+ /// <param name="virtualPath">The generated URL.</param>
+ public VirtualPathData(IRouter router, string virtualPath)
+ : this(router, virtualPath, dataTokens: null)
{
- private RouteValueDictionary _dataTokens;
- private string _virtualPath;
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="VirtualPathData"/> class.
- /// </summary>
- /// <param name="router">The object that is used to generate the URL.</param>
- /// <param name="virtualPath">The generated URL.</param>
- public VirtualPathData(IRouter router, string virtualPath)
- : this(router, virtualPath, dataTokens: null)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="VirtualPathData"/> class.
+ /// </summary>
+ /// <param name="router">The object that is used to generate the URL.</param>
+ /// <param name="virtualPath">The generated URL.</param>
+ /// <param name="dataTokens">The collection of custom values.</param>
+ public VirtualPathData(
+ IRouter router,
+ string virtualPath,
+ RouteValueDictionary dataTokens)
+ {
+ if (router == null)
{
+ throw new ArgumentNullException(nameof(router));
}
- /// <summary>
- /// Initializes a new instance of the <see cref="VirtualPathData"/> class.
- /// </summary>
- /// <param name="router">The object that is used to generate the URL.</param>
- /// <param name="virtualPath">The generated URL.</param>
- /// <param name="dataTokens">The collection of custom values.</param>
- public VirtualPathData(
- IRouter router,
- string virtualPath,
- RouteValueDictionary dataTokens)
+ Router = router;
+ VirtualPath = virtualPath;
+ _dataTokens = dataTokens == null ? null : new RouteValueDictionary(dataTokens);
+ }
+
+ /// <summary>
+ /// Gets the collection of custom values for the <see cref="Router"/>.
+ /// </summary>
+ public RouteValueDictionary DataTokens
+ {
+ get
{
- if (router == null)
+ if (_dataTokens == null)
{
- throw new ArgumentNullException(nameof(router));
+ _dataTokens = new RouteValueDictionary();
}
- Router = router;
- VirtualPath = virtualPath;
- _dataTokens = dataTokens == null ? null : new RouteValueDictionary(dataTokens);
+ return _dataTokens;
}
+ }
- /// <summary>
- /// Gets the collection of custom values for the <see cref="Router"/>.
- /// </summary>
- public RouteValueDictionary DataTokens
- {
- get
- {
- if (_dataTokens == null)
- {
- _dataTokens = new RouteValueDictionary();
- }
+ /// <summary>
+ /// Gets or sets the <see cref="IRouter"/> that was used to generate the URL.
+ /// </summary>
+ public IRouter Router { get; set; }
- return _dataTokens;
- }
+ /// <summary>
+ /// Gets or sets the URL that was generated from the <see cref="Router"/>.
+ /// </summary>
+ public string VirtualPath
+ {
+ get
+ {
+ return _virtualPath;
}
-
- /// <summary>
- /// Gets or sets the <see cref="IRouter"/> that was used to generate the URL.
- /// </summary>
- public IRouter Router { get; set; }
-
- /// <summary>
- /// Gets or sets the URL that was generated from the <see cref="Router"/>.
- /// </summary>
- public string VirtualPath
+ set
{
- get
- {
- return _virtualPath;
- }
- set
- {
- _virtualPath = NormalizePath(value);
- }
+ _virtualPath = NormalizePath(value);
}
+ }
- private static string NormalizePath(string path)
+ private static string NormalizePath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
{
- if (string.IsNullOrEmpty(path))
- {
- return string.Empty;
- }
-
- if (!path.StartsWith("/", StringComparison.Ordinal))
- {
- return "/" + path;
- }
+ return string.Empty;
+ }
- return path;
+ if (!path.StartsWith("/", StringComparison.Ordinal))
+ {
+ return "/" + path;
}
+
+ return path;
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing.Abstractions/test/RouteDataTest.cs b/src/Http/Routing.Abstractions/test/RouteDataTest.cs
index 8b1da0edf8..825cb2ad90 100644
--- a/src/Http/Routing.Abstractions/test/RouteDataTest.cs
+++ b/src/Http/Routing.Abstractions/test/RouteDataTest.cs
@@ -4,154 +4,153 @@
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteDataTest
{
- public class RouteDataTest
+ [Fact]
+ public void RouteData_DefaultPropertyValues()
+ {
+ // Arrange & Act
+ var routeData = new RouteData();
+
+ // Assert
+ Assert.Empty(routeData.DataTokens);
+ Assert.Empty(routeData.Routers);
+ Assert.Empty(routeData.Values);
+ }
+
+ [Fact]
+ public void RouteData_CopyConstructor()
+ {
+ // Arrange & Act
+ var original = new RouteData();
+
+ original.DataTokens.Add("data", "token");
+ original.Routers.Add(Mock.Of<IRouter>());
+ original.Values.Add("route", "value");
+
+ var routeData = new RouteData(original);
+
+ // Assert
+ Assert.NotSame(routeData.DataTokens, original.DataTokens);
+ Assert.Equal(routeData.DataTokens, original.DataTokens);
+ Assert.NotSame(routeData.Routers, original.Routers);
+ Assert.Equal(routeData.Routers, original.Routers);
+ Assert.NotSame(routeData.Values, original.Values);
+ Assert.Equal(routeData.Values, original.Values);
+ }
+
+ [Fact]
+ public void RouteData_PushStateAndRestore_NullValues()
+ {
+ // Arrange
+ var routeData = new RouteData();
+
+ // Act
+ var snapshot = routeData.PushState(null, null, null);
+ var copy = new RouteData(routeData);
+ snapshot.Restore();
+
+ // Assert
+ Assert.Equal(routeData.DataTokens, copy.DataTokens);
+ Assert.Equal(routeData.Routers, copy.Routers);
+ Assert.Equal(routeData.Values, copy.Values);
+ }
+
+ [Fact]
+ public void RouteData_PushStateAndRestore_EmptyValues()
+ {
+ // Arrange
+ var routeData = new RouteData();
+
+ // Act
+ var snapshot = routeData.PushState(null, new RouteValueDictionary(), new RouteValueDictionary());
+ var copy = new RouteData(routeData);
+ snapshot.Restore();
+
+ // Assert
+ Assert.Equal(routeData.DataTokens, copy.DataTokens);
+ Assert.Equal(routeData.Routers, copy.Routers);
+ Assert.Equal(routeData.Values, copy.Values);
+ }
+
+ // This is an important semantic for catchall parameters. A null route value shouldn't be
+ // merged.
+ [Fact]
+ public void RouteData_PushStateAndRestore_NullRouteValueNotSet()
+ {
+ // Arrange
+ var original = new RouteData();
+ original.Values.Add("bleh", "16");
+
+ var routeData = new RouteData(original);
+
+ // Act
+ var snapshot = routeData.PushState(
+ null,
+ new RouteValueDictionary(new { bleh = (string)null }),
+ new RouteValueDictionary());
+ snapshot.Restore();
+
+ // Assert
+ Assert.Equal(routeData.Values, original.Values);
+ }
+
+ [Fact]
+ public void RouteData_PushStateAndThenModify()
+ {
+ // Arrange
+ var routeData = new RouteData();
+
+ // Act
+ var snapshot = routeData.PushState(null, null, null);
+ routeData.DataTokens.Add("data", "token");
+ routeData.Routers.Add(Mock.Of<IRouter>());
+ routeData.Values.Add("route", "value");
+
+ var copy = new RouteData(routeData);
+ snapshot.Restore();
+
+ // Assert
+ Assert.Empty(routeData.DataTokens);
+ Assert.NotEqual(routeData.DataTokens, copy.DataTokens);
+ Assert.Empty(routeData.Routers);
+ Assert.NotEqual(routeData.Routers, copy.Routers);
+ Assert.Empty(routeData.Values);
+ Assert.NotEqual(routeData.Values, copy.Values);
+ }
+
+ [Fact]
+ public void RouteData_PushStateAndThenModify_WithInitialData()
{
- [Fact]
- public void RouteData_DefaultPropertyValues()
- {
- // Arrange & Act
- var routeData = new RouteData();
-
- // Assert
- Assert.Empty(routeData.DataTokens);
- Assert.Empty(routeData.Routers);
- Assert.Empty(routeData.Values);
- }
-
- [Fact]
- public void RouteData_CopyConstructor()
- {
- // Arrange & Act
- var original = new RouteData();
-
- original.DataTokens.Add("data", "token");
- original.Routers.Add(Mock.Of<IRouter>());
- original.Values.Add("route", "value");
-
- var routeData = new RouteData(original);
-
- // Assert
- Assert.NotSame(routeData.DataTokens, original.DataTokens);
- Assert.Equal(routeData.DataTokens, original.DataTokens);
- Assert.NotSame(routeData.Routers, original.Routers);
- Assert.Equal(routeData.Routers, original.Routers);
- Assert.NotSame(routeData.Values, original.Values);
- Assert.Equal(routeData.Values, original.Values);
- }
-
- [Fact]
- public void RouteData_PushStateAndRestore_NullValues()
- {
- // Arrange
- var routeData = new RouteData();
-
- // Act
- var snapshot = routeData.PushState(null, null, null);
- var copy = new RouteData(routeData);
- snapshot.Restore();
-
- // Assert
- Assert.Equal(routeData.DataTokens, copy.DataTokens);
- Assert.Equal(routeData.Routers, copy.Routers);
- Assert.Equal(routeData.Values, copy.Values);
- }
-
- [Fact]
- public void RouteData_PushStateAndRestore_EmptyValues()
- {
- // Arrange
- var routeData = new RouteData();
-
- // Act
- var snapshot = routeData.PushState(null, new RouteValueDictionary(), new RouteValueDictionary());
- var copy = new RouteData(routeData);
- snapshot.Restore();
-
- // Assert
- Assert.Equal(routeData.DataTokens, copy.DataTokens);
- Assert.Equal(routeData.Routers, copy.Routers);
- Assert.Equal(routeData.Values, copy.Values);
- }
-
- // This is an important semantic for catchall parameters. A null route value shouldn't be
- // merged.
- [Fact]
- public void RouteData_PushStateAndRestore_NullRouteValueNotSet()
- {
- // Arrange
- var original = new RouteData();
- original.Values.Add("bleh", "16");
-
- var routeData = new RouteData(original);
-
- // Act
- var snapshot = routeData.PushState(
- null,
- new RouteValueDictionary(new { bleh = (string)null }),
- new RouteValueDictionary());
- snapshot.Restore();
-
- // Assert
- Assert.Equal(routeData.Values, original.Values);
- }
-
- [Fact]
- public void RouteData_PushStateAndThenModify()
- {
- // Arrange
- var routeData = new RouteData();
-
- // Act
- var snapshot = routeData.PushState(null, null, null);
- routeData.DataTokens.Add("data", "token");
- routeData.Routers.Add(Mock.Of<IRouter>());
- routeData.Values.Add("route", "value");
-
- var copy = new RouteData(routeData);
- snapshot.Restore();
-
- // Assert
- Assert.Empty(routeData.DataTokens);
- Assert.NotEqual(routeData.DataTokens, copy.DataTokens);
- Assert.Empty(routeData.Routers);
- Assert.NotEqual(routeData.Routers, copy.Routers);
- Assert.Empty(routeData.Values);
- Assert.NotEqual(routeData.Values, copy.Values);
- }
-
- [Fact]
- public void RouteData_PushStateAndThenModify_WithInitialData()
- {
- // Arrange
- var original = new RouteData();
- original.DataTokens.Add("data", "token1");
- original.Routers.Add(Mock.Of<IRouter>());
- original.Values.Add("route", "value1");
-
- var routeData = new RouteData(original);
-
- // Act
- var snapshot = routeData.PushState(
- Mock.Of<IRouter>(),
- new RouteValueDictionary(new { route = "value2" }),
- new RouteValueDictionary(new { data = "token2" }));
-
- routeData.DataTokens.Add("data2", "token");
- routeData.Routers.Add(Mock.Of<IRouter>());
- routeData.Values.Add("route2", "value");
-
- var copy = new RouteData(routeData);
- snapshot.Restore();
-
- // Assert
- Assert.Equal(original.DataTokens, routeData.DataTokens);
- Assert.NotEqual(routeData.DataTokens, copy.DataTokens);
- Assert.Equal(original.Routers, routeData.Routers);
- Assert.NotEqual(routeData.Routers, copy.Routers);
- Assert.Equal(original.Values, routeData.Values);
- Assert.NotEqual(routeData.Values, copy.Values);
- }
+ // Arrange
+ var original = new RouteData();
+ original.DataTokens.Add("data", "token1");
+ original.Routers.Add(Mock.Of<IRouter>());
+ original.Values.Add("route", "value1");
+
+ var routeData = new RouteData(original);
+
+ // Act
+ var snapshot = routeData.PushState(
+ Mock.Of<IRouter>(),
+ new RouteValueDictionary(new { route = "value2" }),
+ new RouteValueDictionary(new { data = "token2" }));
+
+ routeData.DataTokens.Add("data2", "token");
+ routeData.Routers.Add(Mock.Of<IRouter>());
+ routeData.Values.Add("route2", "value");
+
+ var copy = new RouteData(routeData);
+ snapshot.Restore();
+
+ // Assert
+ Assert.Equal(original.DataTokens, routeData.DataTokens);
+ Assert.NotEqual(routeData.DataTokens, copy.DataTokens);
+ Assert.Equal(original.Routers, routeData.Routers);
+ Assert.NotEqual(routeData.Routers, copy.Routers);
+ Assert.Equal(original.Values, routeData.Values);
+ Assert.NotEqual(routeData.Values, copy.Values);
}
}
diff --git a/src/Http/Routing.Abstractions/test/VirtualPathDataTests.cs b/src/Http/Routing.Abstractions/test/VirtualPathDataTests.cs
index 5312fdd52e..ebbf2200d4 100644
--- a/src/Http/Routing.Abstractions/test/VirtualPathDataTests.cs
+++ b/src/Http/Routing.Abstractions/test/VirtualPathDataTests.cs
@@ -4,62 +4,61 @@
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class VirtualPathDataTests
{
- public class VirtualPathDataTests
+ [Fact]
+ public void Constructor_CreatesEmptyDataTokensIfNull()
{
- [Fact]
- public void Constructor_CreatesEmptyDataTokensIfNull()
- {
- // Arrange
- var router = Mock.Of<IRouter>();
- var path = "/virtual path";
+ // Arrange
+ var router = Mock.Of<IRouter>();
+ var path = "/virtual path";
- // Act
- var pathData = new VirtualPathData(router, path, null);
+ // Act
+ var pathData = new VirtualPathData(router, path, null);
- // Assert
- Assert.Same(router, pathData.Router);
- Assert.Equal(path, pathData.VirtualPath);
- Assert.NotNull(pathData.DataTokens);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.Same(router, pathData.Router);
+ Assert.Equal(path, pathData.VirtualPath);
+ Assert.NotNull(pathData.DataTokens);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void Constructor_CopiesDataTokens()
- {
- // Arrange
- var router = Mock.Of<IRouter>();
- var path = "/virtual path";
- var dataTokens = new RouteValueDictionary();
- dataTokens["TestKey"] = "TestValue";
+ [Fact]
+ public void Constructor_CopiesDataTokens()
+ {
+ // Arrange
+ var router = Mock.Of<IRouter>();
+ var path = "/virtual path";
+ var dataTokens = new RouteValueDictionary();
+ dataTokens["TestKey"] = "TestValue";
- // Act
- var pathData = new VirtualPathData(router, path, dataTokens);
+ // Act
+ var pathData = new VirtualPathData(router, path, dataTokens);
- // Assert
- Assert.Same(router, pathData.Router);
- Assert.Equal(path, pathData.VirtualPath);
- Assert.NotNull(pathData.DataTokens);
- Assert.Equal("TestValue", pathData.DataTokens["TestKey"]);
- Assert.Single(pathData.DataTokens);
- Assert.NotSame(dataTokens, pathData.DataTokens);
- }
+ // Assert
+ Assert.Same(router, pathData.Router);
+ Assert.Equal(path, pathData.VirtualPath);
+ Assert.NotNull(pathData.DataTokens);
+ Assert.Equal("TestValue", pathData.DataTokens["TestKey"]);
+ Assert.Single(pathData.DataTokens);
+ Assert.NotSame(dataTokens, pathData.DataTokens);
+ }
- [Fact]
- public void VirtualPath_ReturnsEmptyStringIfNull()
- {
- // Arrange
- var router = Mock.Of<IRouter>();
+ [Fact]
+ public void VirtualPath_ReturnsEmptyStringIfNull()
+ {
+ // Arrange
+ var router = Mock.Of<IRouter>();
- // Act
- var pathData = new VirtualPathData(router, virtualPath: null);
+ // Act
+ var pathData = new VirtualPathData(router, virtualPath: null);
- // Assert
- Assert.Same(router, pathData.Router);
- Assert.Empty(pathData.VirtualPath);
- Assert.NotNull(pathData.DataTokens);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.Same(router, pathData.Router);
+ Assert.Empty(pathData.VirtualPath);
+ Assert.NotNull(pathData.DataTokens);
+ Assert.Empty(pathData.DataTokens);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/EndpointMetadataCollectionBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/EndpointMetadataCollectionBenchmark.cs
index 8c67f50f2e..0a5c918ea3 100644
--- a/src/Http/Routing/perf/Microbenchmarks/EndpointMetadataCollectionBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/EndpointMetadataCollectionBenchmark.cs
@@ -5,21 +5,21 @@ using System;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class EndpointMetadataCollectionBenchmark
{
- public class EndpointMetadataCollectionBenchmark
- {
- private object[] _items;
- private EndpointMetadataCollection _collection;
+ private object[] _items;
+ private EndpointMetadataCollection _collection;
- [Params(3, 10, 25)]
- public int Count { get; set; }
+ [Params(3, 10, 25)]
+ public int Count { get; set; }
- [GlobalSetup]
- public void Setup()
+ [GlobalSetup]
+ public void Setup()
+ {
+ var seeds = new Type[]
{
- var seeds = new Type[]
- {
typeof(Metadata1),
typeof(Metadata2),
typeof(Metadata3),
@@ -29,100 +29,99 @@ namespace Microsoft.AspNetCore.Routing
typeof(Metadata7),
typeof(Metadata8),
typeof(Metadata9),
- };
+ };
+
+ _items = new object[Count];
+ for (var i = 0; i < _items.Length; i++)
+ {
+ _items[i] = seeds[i % seeds.Length];
+ }
+
+ _collection = new EndpointMetadataCollection(_items);
+ }
+
+ // This is a synthetic baseline that visits each item and does an as-cast.
+ [Benchmark(Baseline = true, OperationsPerInvoke = 5)]
+ public void Baseline()
+ {
+ var items = _items;
+ for (var i = items.Length - 1; i >= 0; i--)
+ {
+ GC.KeepAlive(_items[i] as IMetadata1);
+ }
+
+ for (var i = items.Length - 1; i >= 0; i--)
+ {
+ GC.KeepAlive(_items[i] as IMetadata2);
+ }
+
+ for (var i = items.Length - 1; i >= 0; i--)
+ {
+ GC.KeepAlive(_items[i] as IMetadata3);
+ }
+
+ for (var i = items.Length - 1; i >= 0; i--)
+ {
+ GC.KeepAlive(_items[i] as IMetadata4);
+ }
+
+ for (var i = items.Length - 1; i >= 0; i--)
+ {
+ GC.KeepAlive(_items[i] as IMetadata5);
+ }
+ }
- _items = new object[Count];
- for (var i = 0; i < _items.Length; i++)
- {
- _items[i] = seeds[i % seeds.Length];
- }
+ [Benchmark(OperationsPerInvoke = 5)]
+ public void GetMetadata()
+ {
+ GC.KeepAlive(_collection.GetMetadata<IMetadata1>());
+ GC.KeepAlive(_collection.GetMetadata<IMetadata2>());
+ GC.KeepAlive(_collection.GetMetadata<IMetadata3>());
+ GC.KeepAlive(_collection.GetMetadata<IMetadata4>());
+ GC.KeepAlive(_collection.GetMetadata<IMetadata5>());
+ }
- _collection = new EndpointMetadataCollection(_items);
+ [Benchmark(OperationsPerInvoke = 5)]
+ public void GetOrderedMetadata()
+ {
+ foreach (var item in _collection.GetOrderedMetadata<IMetadata1>())
+ {
+ GC.KeepAlive(item);
}
- // This is a synthetic baseline that visits each item and does an as-cast.
- [Benchmark(Baseline = true, OperationsPerInvoke = 5)]
- public void Baseline()
+ foreach (var item in _collection.GetOrderedMetadata<IMetadata2>())
{
- var items = _items;
- for (var i = items.Length - 1; i >= 0; i--)
- {
- GC.KeepAlive(_items[i] as IMetadata1);
- }
-
- for (var i = items.Length - 1; i >= 0; i--)
- {
- GC.KeepAlive(_items[i] as IMetadata2);
- }
-
- for (var i = items.Length - 1; i >= 0; i--)
- {
- GC.KeepAlive(_items[i] as IMetadata3);
- }
-
- for (var i = items.Length - 1; i >= 0; i--)
- {
- GC.KeepAlive(_items[i] as IMetadata4);
- }
-
- for (var i = items.Length - 1; i >= 0; i--)
- {
- GC.KeepAlive(_items[i] as IMetadata5);
- }
+ GC.KeepAlive(item);
}
- [Benchmark(OperationsPerInvoke = 5)]
- public void GetMetadata()
+ foreach (var item in _collection.GetOrderedMetadata<IMetadata3>())
{
- GC.KeepAlive(_collection.GetMetadata<IMetadata1>());
- GC.KeepAlive(_collection.GetMetadata<IMetadata2>());
- GC.KeepAlive(_collection.GetMetadata<IMetadata3>());
- GC.KeepAlive(_collection.GetMetadata<IMetadata4>());
- GC.KeepAlive(_collection.GetMetadata<IMetadata5>());
+ GC.KeepAlive(item);
}
- [Benchmark(OperationsPerInvoke = 5)]
- public void GetOrderedMetadata()
+ foreach (var item in _collection.GetOrderedMetadata<IMetadata4>())
{
- foreach (var item in _collection.GetOrderedMetadata<IMetadata1>())
- {
- GC.KeepAlive(item);
- }
-
- foreach (var item in _collection.GetOrderedMetadata<IMetadata2>())
- {
- GC.KeepAlive(item);
- }
-
- foreach (var item in _collection.GetOrderedMetadata<IMetadata3>())
- {
- GC.KeepAlive(item);
- }
-
- foreach (var item in _collection.GetOrderedMetadata<IMetadata4>())
- {
- GC.KeepAlive(item);
- }
-
- foreach (var item in _collection.GetOrderedMetadata<IMetadata5>())
- {
- GC.KeepAlive(item);
- }
+ GC.KeepAlive(item);
}
- private interface IMetadata1 { }
- private interface IMetadata2 { }
- private interface IMetadata3 { }
- private interface IMetadata4 { }
- private interface IMetadata5 { }
- private class Metadata1 : IMetadata1 { }
- private class Metadata2 : IMetadata2 { }
- private class Metadata3 : IMetadata3 { }
- private class Metadata4 : IMetadata4 { }
- private class Metadata5 : IMetadata5 { }
- private class Metadata6 : IMetadata1, IMetadata2 { }
- private class Metadata7 : IMetadata2, IMetadata3 { }
- private class Metadata8 : IMetadata4, IMetadata5 { }
- private class Metadata9 : IMetadata1, IMetadata2 { }
+ foreach (var item in _collection.GetOrderedMetadata<IMetadata5>())
+ {
+ GC.KeepAlive(item);
+ }
}
+
+ private interface IMetadata1 { }
+ private interface IMetadata2 { }
+ private interface IMetadata3 { }
+ private interface IMetadata4 { }
+ private interface IMetadata5 { }
+ private class Metadata1 : IMetadata1 { }
+ private class Metadata2 : IMetadata2 { }
+ private class Metadata3 : IMetadata3 { }
+ private class Metadata4 : IMetadata4 { }
+ private class Metadata5 : IMetadata5 { }
+ private class Metadata6 : IMetadata1, IMetadata2 { }
+ private class Metadata7 : IMetadata2, IMetadata3 { }
+ private class Metadata8 : IMetadata4, IMetadata5 { }
+ private class Metadata9 : IMetadata1, IMetadata2 { }
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingBenchmarkBase.cs b/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingBenchmarkBase.cs
index 55c15eef63..35b9a95544 100644
--- a/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingBenchmarkBase.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingBenchmarkBase.cs
@@ -14,138 +14,137 @@ using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public abstract class EndpointRoutingBenchmarkBase
{
- public abstract class EndpointRoutingBenchmarkBase
+ private protected RouteEndpoint[] Endpoints;
+ private protected HttpContext[] Requests;
+
+ private protected void SetupEndpoints(params RouteEndpoint[] endpoints)
{
- private protected RouteEndpoint[] Endpoints;
- private protected HttpContext[] Requests;
+ Endpoints = endpoints;
+ }
- private protected void SetupEndpoints(params RouteEndpoint[] endpoints)
- {
- Endpoints = endpoints;
- }
+ // The older routing implementations retrieve services when they first execute.
+ private protected IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddOptions();
+ services.AddRouting();
- // The older routing implementations retrieve services when they first execute.
- private protected IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddLogging();
- services.AddOptions();
- services.AddRouting();
+ services.TryAddEnumerable(
+ ServiceDescriptor.Singleton<EndpointDataSource>(new DefaultEndpointDataSource(Endpoints)));
- services.TryAddEnumerable(
- ServiceDescriptor.Singleton<EndpointDataSource>(new DefaultEndpointDataSource(Endpoints)));
+ return services.BuildServiceProvider();
+ }
- return services.BuildServiceProvider();
- }
+ private protected DfaMatcherBuilder CreateDfaMatcherBuilder()
+ {
+ return CreateServices().GetRequiredService<DfaMatcherBuilder>();
+ }
- private protected DfaMatcherBuilder CreateDfaMatcherBuilder()
+ private protected static int[] SampleRequests(int endpointCount, int count)
+ {
+ // This isn't very high tech, but it's at least regular distribution.
+ // We sort the route templates by precedence, so this should result in
+ // an even distribution of the 'complexity' of the routes that are exercised.
+ var frequency = endpointCount / count;
+ if (frequency < 2)
{
- return CreateServices().GetRequiredService<DfaMatcherBuilder>();
+ throw new InvalidOperationException(
+ "The sample count is too high. This won't produce an accurate sampling" +
+ "of the request data.");
}
- private protected static int[] SampleRequests(int endpointCount, int count)
+ var samples = new int[count];
+ for (var i = 0; i < samples.Length; i++)
{
- // This isn't very high tech, but it's at least regular distribution.
- // We sort the route templates by precedence, so this should result in
- // an even distribution of the 'complexity' of the routes that are exercised.
- var frequency = endpointCount / count;
- if (frequency < 2)
- {
- throw new InvalidOperationException(
- "The sample count is too high. This won't produce an accurate sampling" +
- "of the request data.");
- }
-
- var samples = new int[count];
- for (var i = 0; i < samples.Length; i++)
- {
- samples[i] = i * frequency;
- }
-
- return samples;
+ samples[i] = i * frequency;
}
- [MethodImpl(MethodImplOptions.NoInlining)]
- private protected void Validate(HttpContext httpContext, Endpoint expected, Endpoint actual)
- {
- if (!object.ReferenceEquals(expected, actual))
- {
- var message = new StringBuilder();
- message.AppendLine(FormattableString.Invariant($"Validation failed for request {Array.IndexOf(Requests, httpContext)}"));
- message.AppendLine(FormattableString.Invariant($"{httpContext.Request.Method} {httpContext.Request.Path}"));
- message.AppendLine(FormattableString.Invariant($"expected: '{((RouteEndpoint)expected)?.DisplayName ?? "null"}'"));
- message.AppendLine(FormattableString.Invariant($"actual: '{((RouteEndpoint)actual)?.DisplayName ?? "null"}'"));
- throw new InvalidOperationException(message.ToString());
- }
- }
+ return samples;
+ }
- protected void AssertUrl(string expectedUrl, string actualUrl)
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private protected void Validate(HttpContext httpContext, Endpoint expected, Endpoint actual)
+ {
+ if (!object.ReferenceEquals(expected, actual))
{
- AssertUrl(expectedUrl, actualUrl, StringComparison.Ordinal);
+ var message = new StringBuilder();
+ message.AppendLine(FormattableString.Invariant($"Validation failed for request {Array.IndexOf(Requests, httpContext)}"));
+ message.AppendLine(FormattableString.Invariant($"{httpContext.Request.Method} {httpContext.Request.Path}"));
+ message.AppendLine(FormattableString.Invariant($"expected: '{((RouteEndpoint)expected)?.DisplayName ?? "null"}'"));
+ message.AppendLine(FormattableString.Invariant($"actual: '{((RouteEndpoint)actual)?.DisplayName ?? "null"}'"));
+ throw new InvalidOperationException(message.ToString());
}
+ }
- protected void AssertUrl(string expectedUrl, string actualUrl, StringComparison stringComparison)
+ protected void AssertUrl(string expectedUrl, string actualUrl)
+ {
+ AssertUrl(expectedUrl, actualUrl, StringComparison.Ordinal);
+ }
+
+ protected void AssertUrl(string expectedUrl, string actualUrl, StringComparison stringComparison)
+ {
+ if (!string.Equals(expectedUrl, actualUrl, stringComparison))
{
- if (!string.Equals(expectedUrl, actualUrl, stringComparison))
- {
- throw new InvalidOperationException($"Expected: {expectedUrl}, Actual: {actualUrl}");
- }
+ throw new InvalidOperationException($"Expected: {expectedUrl}, Actual: {actualUrl}");
}
+ }
- protected RouteEndpoint CreateEndpoint(string template, string httpMethod)
+ protected RouteEndpoint CreateEndpoint(string template, string httpMethod)
+ {
+ return CreateEndpoint(template, metadata: new object[]
{
- return CreateEndpoint(template, metadata: new object[]
- {
new HttpMethodMetadata(new string[]{ httpMethod, }),
- });
- }
+ });
+ }
- protected RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- object constraints = null,
- object requiredValues = null,
- int order = 0,
- string displayName = null,
- string routeName = null,
- params object[] metadata)
+ protected RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ object constraints = null,
+ object requiredValues = null,
+ int order = 0,
+ string displayName = null,
+ string routeName = null,
+ params object[] metadata)
+ {
+ var endpointMetadata = new List<object>(metadata ?? Array.Empty<object>());
+ if (routeName != null)
{
- var endpointMetadata = new List<object>(metadata ?? Array.Empty<object>());
- if (routeName != null)
- {
- endpointMetadata.Add(new RouteNameMetadata(routeName));
- }
-
- return new RouteEndpoint(
- (context) => Task.CompletedTask,
- RoutePatternFactory.Parse(template, defaults, constraints, requiredValues),
- order,
- new EndpointMetadataCollection(endpointMetadata),
- displayName);
+ endpointMetadata.Add(new RouteNameMetadata(routeName));
}
- protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext(
- object ambientValues = null)
- {
- var context = new DefaultHttpContext();
- context.Request.RouteValues = new RouteValueDictionary(ambientValues);
+ return new RouteEndpoint(
+ (context) => Task.CompletedTask,
+ RoutePatternFactory.Parse(template, defaults, constraints, requiredValues),
+ order,
+ new EndpointMetadataCollection(endpointMetadata),
+ displayName);
+ }
- return (context, context.Request.RouteValues);
- }
+ protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext(
+ object ambientValues = null)
+ {
+ var context = new DefaultHttpContext();
+ context.Request.RouteValues = new RouteValueDictionary(ambientValues);
- protected void CreateOutboundRouteEntry(TreeRouteBuilder treeRouteBuilder, RouteEndpoint endpoint)
- {
- treeRouteBuilder.MapOutbound(
- NullRouter.Instance,
- new RouteTemplate(RoutePatternFactory.Parse(
- endpoint.RoutePattern.RawText,
- defaults: endpoint.RoutePattern.Defaults,
- parameterPolicies: null)),
- requiredLinkValues: new RouteValueDictionary(endpoint.RoutePattern.RequiredValues),
- routeName: null,
- order: 0);
- }
+ return (context, context.Request.RouteValues);
+ }
+
+ protected void CreateOutboundRouteEntry(TreeRouteBuilder treeRouteBuilder, RouteEndpoint endpoint)
+ {
+ treeRouteBuilder.MapOutbound(
+ NullRouter.Instance,
+ new RouteTemplate(RoutePatternFactory.Parse(
+ endpoint.RoutePattern.RawText,
+ defaults: endpoint.RoutePattern.Defaults,
+ parameterPolicies: null)),
+ requiredLinkValues: new RouteValueDictionary(endpoint.RoutePattern.RequiredValues),
+ routeName: null,
+ order: 0);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/LinkGenerationGithubBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/LinkGenerationGithubBenchmark.cs
index 826002361f..8df2a07ce4 100644
--- a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/LinkGenerationGithubBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/LinkGenerationGithubBenchmark.cs
@@ -6,70 +6,69 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.LinkGeneration
+namespace Microsoft.AspNetCore.Routing.LinkGeneration;
+
+public partial class LinkGenerationGithubBenchmark
{
- public partial class LinkGenerationGithubBenchmark
- {
- private LinkGenerator _linkGenerator;
- private TreeRouter _treeRouter;
- private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
- private RouteValueDictionary _lookUpValues;
+ private LinkGenerator _linkGenerator;
+ private TreeRouter _treeRouter;
+ private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
+ private RouteValueDictionary _lookUpValues;
- [GlobalSetup]
- public void Setup()
- {
- SetupEndpoints();
+ [GlobalSetup]
+ public void Setup()
+ {
+ SetupEndpoints();
- var services = CreateServices();
- _linkGenerator = services.GetRequiredService<LinkGenerator>();
+ var services = CreateServices();
+ _linkGenerator = services.GetRequiredService<LinkGenerator>();
- // Attribute routing related
- var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
- foreach (var endpoint in Endpoints)
- {
- CreateOutboundRouteEntry(treeRouteBuilder, endpoint);
- }
- _treeRouter = treeRouteBuilder.Build();
+ // Attribute routing related
+ var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
+ foreach (var endpoint in Endpoints)
+ {
+ CreateOutboundRouteEntry(treeRouteBuilder, endpoint);
+ }
+ _treeRouter = treeRouteBuilder.Build();
- _requestContext = CreateCurrentRequestContext();
+ _requestContext = CreateCurrentRequestContext();
- // Get the endpoint to test and pre-populate the lookup values with the defaults
- // (as they are dynamically generated) and update with other required parameter values.
- // /repos/{owner}/{repo}/issues/comments/{commentId}
- var endpointToTest = Endpoints[176];
- _lookUpValues = new RouteValueDictionary(endpointToTest.RoutePattern.Defaults);
- _lookUpValues["owner"] = "aspnet";
- _lookUpValues["repo"] = "routing";
- _lookUpValues["commentId"] = "20202";
- }
+ // Get the endpoint to test and pre-populate the lookup values with the defaults
+ // (as they are dynamically generated) and update with other required parameter values.
+ // /repos/{owner}/{repo}/issues/comments/{commentId}
+ var endpointToTest = Endpoints[176];
+ _lookUpValues = new RouteValueDictionary(endpointToTest.RoutePattern.Defaults);
+ _lookUpValues["owner"] = "aspnet";
+ _lookUpValues["repo"] = "routing";
+ _lookUpValues["commentId"] = "20202";
+ }
- [Benchmark(Baseline = true)]
- public void Baseline()
- {
- var url = $"/repos/{_lookUpValues["owner"]}/{_lookUpValues["repo"]}/issues/comments/{_lookUpValues["commentId"]}";
- AssertUrl("/repos/aspnet/routing/issues/comments/20202", url);
- }
+ [Benchmark(Baseline = true)]
+ public void Baseline()
+ {
+ var url = $"/repos/{_lookUpValues["owner"]}/{_lookUpValues["repo"]}/issues/comments/{_lookUpValues["commentId"]}";
+ AssertUrl("/repos/aspnet/routing/issues/comments/20202", url);
+ }
- [Benchmark]
- public void TreeRouter()
- {
- var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
- _requestContext.HttpContext,
- ambientValues: _requestContext.AmbientValues,
- values: new RouteValueDictionary(_lookUpValues)));
+ [Benchmark]
+ public void TreeRouter()
+ {
+ var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
+ _requestContext.HttpContext,
+ ambientValues: _requestContext.AmbientValues,
+ values: new RouteValueDictionary(_lookUpValues)));
- AssertUrl("/repos/aspnet/routing/issues/comments/20202", virtualPathData?.VirtualPath);
- }
+ AssertUrl("/repos/aspnet/routing/issues/comments/20202", virtualPathData?.VirtualPath);
+ }
- [Benchmark]
- public void EndpointRouting()
- {
- var actualUrl = _linkGenerator.GetPathByRouteValues(
- _requestContext.HttpContext,
- routeName: null,
- values: _lookUpValues);
+ [Benchmark]
+ public void EndpointRouting()
+ {
+ var actualUrl = _linkGenerator.GetPathByRouteValues(
+ _requestContext.HttpContext,
+ routeName: null,
+ values: _lookUpValues);
- AssertUrl("/repos/aspnet/routing/issues/comments/20202", actualUrl);
- }
+ AssertUrl("/repos/aspnet/routing/issues/comments/20202", actualUrl);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteRouteValuesAddressSchemeBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteRouteValuesAddressSchemeBenchmark.cs
index 016780f1f4..448c294188 100644
--- a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteRouteValuesAddressSchemeBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteRouteValuesAddressSchemeBenchmark.cs
@@ -6,69 +6,68 @@ using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.LinkGeneration
+namespace Microsoft.AspNetCore.Routing.LinkGeneration;
+
+public class SingleRouteRouteValuesAddressSchemeBenchmark : EndpointRoutingBenchmarkBase
{
- public class SingleRouteRouteValuesAddressSchemeBenchmark : EndpointRoutingBenchmarkBase
+ private IEndpointAddressScheme<RouteValuesAddress> _implementation;
+ private TestAddressScheme _baseline;
+ private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
+
+ [GlobalSetup]
+ public void Setup()
{
- private IEndpointAddressScheme<RouteValuesAddress> _implementation;
- private TestAddressScheme _baseline;
- private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
+ var template = "Products/Details";
+ var defaults = new { controller = "Products", action = "Details" };
+ var requiredValues = new { controller = "Products", action = "Details" };
- [GlobalSetup]
- public void Setup()
- {
- var template = "Products/Details";
- var defaults = new { controller = "Products", action = "Details" };
- var requiredValues = new { controller = "Products", action = "Details" };
+ SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues, routeName: "ProductDetails"));
+ var services = CreateServices();
+ _implementation = services.GetRequiredService<IEndpointAddressScheme<RouteValuesAddress>>();
+ _baseline = new TestAddressScheme(Endpoints[0]);
- SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues, routeName: "ProductDetails"));
- var services = CreateServices();
- _implementation = services.GetRequiredService<IEndpointAddressScheme<RouteValuesAddress>>();
- _baseline = new TestAddressScheme(Endpoints[0]);
+ _requestContext = CreateCurrentRequestContext();
+ }
- _requestContext = CreateCurrentRequestContext();
- }
+ [Benchmark(Baseline = true)]
+ public void Baseline()
+ {
+ var actual = _baseline.FindEndpoints(address: 0);
+ }
- [Benchmark(Baseline = true)]
- public void Baseline()
+ [Benchmark]
+ public void RouteValues()
+ {
+ var actual = _implementation.FindEndpoints(new RouteValuesAddress
{
- var actual = _baseline.FindEndpoints(address: 0);
- }
+ AmbientValues = _requestContext.AmbientValues,
+ ExplicitValues = new RouteValueDictionary(new { controller = "Products", action = "Details" }),
+ RouteName = null
+ });
+ }
- [Benchmark]
- public void RouteValues()
+ [Benchmark]
+ public void RouteName()
+ {
+ var actual = _implementation.FindEndpoints(new RouteValuesAddress
{
- var actual = _implementation.FindEndpoints(new RouteValuesAddress
- {
- AmbientValues = _requestContext.AmbientValues,
- ExplicitValues = new RouteValueDictionary(new { controller = "Products", action = "Details" }),
- RouteName = null
- });
- }
+ AmbientValues = _requestContext.AmbientValues,
+ RouteName = "ProductDetails"
+ });
+ }
- [Benchmark]
- public void RouteName()
+ private class TestAddressScheme : IEndpointAddressScheme<int>
+ {
+ private readonly Endpoint _endpoint;
+
+ public TestAddressScheme(Endpoint endpoint)
{
- var actual = _implementation.FindEndpoints(new RouteValuesAddress
- {
- AmbientValues = _requestContext.AmbientValues,
- RouteName = "ProductDetails"
- });
+ _endpoint = endpoint;
}
- private class TestAddressScheme : IEndpointAddressScheme<int>
+ public IEnumerable<Endpoint> FindEndpoints(int address)
{
- private readonly Endpoint _endpoint;
-
- public TestAddressScheme(Endpoint endpoint)
- {
- _endpoint = endpoint;
- }
-
- public IEnumerable<Endpoint> FindEndpoints(int address)
- {
- return new[] { _endpoint };
- }
+ return new[] { _endpoint };
}
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithConstraintsBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithConstraintsBenchmark.cs
index 7c2f7a2273..925bc5159d 100644
--- a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithConstraintsBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithConstraintsBenchmark.cs
@@ -6,69 +6,68 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.LinkGeneration
-{
- public class SingleRouteWithConstraintsBenchmark : EndpointRoutingBenchmarkBase
- {
- private TreeRouter _treeRouter;
- private LinkGenerator _linkGenerator;
- private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
-
- [GlobalSetup]
- public void Setup()
- {
- var template = "Customers/Details/{category}/{region}/{id:int}";
- var defaults = new { controller = "Customers", action = "Details" };
- var requiredValues = new { controller = "Customers", action = "Details" };
+namespace Microsoft.AspNetCore.Routing.LinkGeneration;
- // Endpoint routing related
- SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
- var services = CreateServices();
- _linkGenerator = services.GetRequiredService<LinkGenerator>();
+public class SingleRouteWithConstraintsBenchmark : EndpointRoutingBenchmarkBase
+{
+ private TreeRouter _treeRouter;
+ private LinkGenerator _linkGenerator;
+ private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
- // Attribute routing related
- var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
- CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
- _treeRouter = treeRouteBuilder.Build();
+ [GlobalSetup]
+ public void Setup()
+ {
+ var template = "Customers/Details/{category}/{region}/{id:int}";
+ var defaults = new { controller = "Customers", action = "Details" };
+ var requiredValues = new { controller = "Customers", action = "Details" };
- _requestContext = CreateCurrentRequestContext();
- }
+ // Endpoint routing related
+ SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
+ var services = CreateServices();
+ _linkGenerator = services.GetRequiredService<LinkGenerator>();
- [Benchmark(Baseline = true)]
- public void TreeRouter()
- {
- var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
- _requestContext.HttpContext,
- ambientValues: _requestContext.AmbientValues,
- values: new RouteValueDictionary(
- new
- {
- controller = "Customers",
- action = "Details",
- category = "Administration",
- region = "US",
- id = 10
- })));
+ // Attribute routing related
+ var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
+ CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
+ _treeRouter = treeRouteBuilder.Build();
- AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
- }
+ _requestContext = CreateCurrentRequestContext();
+ }
- [Benchmark]
- public void EndpointRouting()
- {
- var actualUrl = _linkGenerator.GetPathByRouteValues(
- _requestContext.HttpContext,
- routeName: null,
- values: new
+ [Benchmark(Baseline = true)]
+ public void TreeRouter()
+ {
+ var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
+ _requestContext.HttpContext,
+ ambientValues: _requestContext.AmbientValues,
+ values: new RouteValueDictionary(
+ new
{
controller = "Customers",
action = "Details",
category = "Administration",
region = "US",
id = 10
- });
+ })));
+
+ AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
+ }
+
+ [Benchmark]
+ public void EndpointRouting()
+ {
+ var actualUrl = _linkGenerator.GetPathByRouteValues(
+ _requestContext.HttpContext,
+ routeName: null,
+ values: new
+ {
+ controller = "Customers",
+ action = "Details",
+ category = "Administration",
+ region = "US",
+ id = 10
+ });
- AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
- }
+ AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithNoParametersBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithNoParametersBenchmark.cs
index a6f19e276c..d47b7a209b 100644
--- a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithNoParametersBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithNoParametersBenchmark.cs
@@ -6,63 +6,62 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.LinkGeneration
-{
- public class SingleRouteWithNoParametersBenchmark : EndpointRoutingBenchmarkBase
- {
- private TreeRouter _treeRouter;
- private LinkGenerator _linkGenerator;
- private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
-
- [GlobalSetup]
- public void Setup()
- {
- var template = "Products/Details";
- var defaults = new { controller = "Products", action = "Details" };
- var requiredValues = new { controller = "Products", action = "Details" };
+namespace Microsoft.AspNetCore.Routing.LinkGeneration;
- // Endpoint routing related
- SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
- var services = CreateServices();
- _linkGenerator = services.GetRequiredService<LinkGenerator>();
+public class SingleRouteWithNoParametersBenchmark : EndpointRoutingBenchmarkBase
+{
+ private TreeRouter _treeRouter;
+ private LinkGenerator _linkGenerator;
+ private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
- // Attribute routing related
- var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
- CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
- _treeRouter = treeRouteBuilder.Build();
+ [GlobalSetup]
+ public void Setup()
+ {
+ var template = "Products/Details";
+ var defaults = new { controller = "Products", action = "Details" };
+ var requiredValues = new { controller = "Products", action = "Details" };
- _requestContext = CreateCurrentRequestContext();
- }
+ // Endpoint routing related
+ SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
+ var services = CreateServices();
+ _linkGenerator = services.GetRequiredService<LinkGenerator>();
- [Benchmark(Baseline = true)]
- public void TreeRouter()
- {
- var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
- _requestContext.HttpContext,
- ambientValues: _requestContext.AmbientValues,
- values: new RouteValueDictionary(
- new
- {
- controller = "Products",
- action = "Details",
- })));
+ // Attribute routing related
+ var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
+ CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
+ _treeRouter = treeRouteBuilder.Build();
- AssertUrl("/Products/Details", virtualPathData?.VirtualPath);
- }
+ _requestContext = CreateCurrentRequestContext();
+ }
- [Benchmark]
- public void EndpointRouting()
- {
- var actualUrl = _linkGenerator.GetPathByRouteValues(
- _requestContext.HttpContext,
- routeName: null,
- values: new
+ [Benchmark(Baseline = true)]
+ public void TreeRouter()
+ {
+ var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
+ _requestContext.HttpContext,
+ ambientValues: _requestContext.AmbientValues,
+ values: new RouteValueDictionary(
+ new
{
controller = "Products",
action = "Details",
- });
+ })));
+
+ AssertUrl("/Products/Details", virtualPathData?.VirtualPath);
+ }
+
+ [Benchmark]
+ public void EndpointRouting()
+ {
+ var actualUrl = _linkGenerator.GetPathByRouteValues(
+ _requestContext.HttpContext,
+ routeName: null,
+ values: new
+ {
+ controller = "Products",
+ action = "Details",
+ });
- AssertUrl("/Products/Details", actualUrl);
- }
+ AssertUrl("/Products/Details", actualUrl);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithParametersBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithParametersBenchmark.cs
index 4a7a0b5ee9..df3e44ce8e 100644
--- a/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithParametersBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/LinkGeneration/SingleRouteWithParametersBenchmark.cs
@@ -6,69 +6,68 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.LinkGeneration
-{
- public class SingleRouteWithParametersBenchmark : EndpointRoutingBenchmarkBase
- {
- private TreeRouter _treeRouter;
- private LinkGenerator _linkGenerator;
- private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
-
- [GlobalSetup]
- public void Setup()
- {
- var template = "Customers/Details/{category}/{region}/{id}";
- var defaults = new { controller = "Customers", action = "Details" };
- var requiredValues = new { controller = "Customers", action = "Details" };
+namespace Microsoft.AspNetCore.Routing.LinkGeneration;
- // Endpoint routing related
- SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
- var services = CreateServices();
- _linkGenerator = services.GetRequiredService<LinkGenerator>();
+public class SingleRouteWithParametersBenchmark : EndpointRoutingBenchmarkBase
+{
+ private TreeRouter _treeRouter;
+ private LinkGenerator _linkGenerator;
+ private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
- // Attribute routing related
- var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
- CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
- _treeRouter = treeRouteBuilder.Build();
+ [GlobalSetup]
+ public void Setup()
+ {
+ var template = "Customers/Details/{category}/{region}/{id}";
+ var defaults = new { controller = "Customers", action = "Details" };
+ var requiredValues = new { controller = "Customers", action = "Details" };
- _requestContext = CreateCurrentRequestContext();
- }
+ // Endpoint routing related
+ SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
+ var services = CreateServices();
+ _linkGenerator = services.GetRequiredService<LinkGenerator>();
- [Benchmark(Baseline = true)]
- public void TreeRouter()
- {
- var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
- _requestContext.HttpContext,
- ambientValues: _requestContext.AmbientValues,
- values: new RouteValueDictionary(
- new
- {
- controller = "Customers",
- action = "Details",
- category = "Administration",
- region = "US",
- id = 10
- })));
+ // Attribute routing related
+ var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
+ CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
+ _treeRouter = treeRouteBuilder.Build();
- AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
- }
+ _requestContext = CreateCurrentRequestContext();
+ }
- [Benchmark]
- public void EndpointRouting()
- {
- var actualUrl = _linkGenerator.GetPathByRouteValues(
- _requestContext.HttpContext,
- routeName: null,
- values: new
+ [Benchmark(Baseline = true)]
+ public void TreeRouter()
+ {
+ var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
+ _requestContext.HttpContext,
+ ambientValues: _requestContext.AmbientValues,
+ values: new RouteValueDictionary(
+ new
{
controller = "Customers",
action = "Details",
category = "Administration",
region = "US",
id = 10
- });
+ })));
+
+ AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
+ }
+
+ [Benchmark]
+ public void EndpointRouting()
+ {
+ var actualUrl = _linkGenerator.GetPathByRouteValues(
+ _requestContext.HttpContext,
+ routeName: null,
+ values: new
+ {
+ controller = "Customers",
+ action = "Details",
+ category = "Administration",
+ region = "US",
+ id = 10
+ });
- AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
- }
+ AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerBenchmarkBase.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerBenchmarkBase.cs
index e70c950eda..f265f263c3 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerBenchmarkBase.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerBenchmarkBase.cs
@@ -1,34 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public abstract class FastPathTokenizerBenchmarkBase
{
- public abstract class FastPathTokenizerBenchmarkBase
+ internal unsafe void NaiveBaseline(string path, PathSegment* segments, int maxCount)
{
- internal unsafe void NaiveBaseline(string path, PathSegment* segments, int maxCount)
+ int count = 0;
+ int start = 1; // Paths always start with a leading /
+ int end;
+ while ((end = path.IndexOf('/', start)) >= 0 && count < maxCount)
{
- int count = 0;
- int start = 1; // Paths always start with a leading /
- int end;
- while ((end = path.IndexOf('/', start)) >= 0 && count < maxCount)
- {
- segments[count++] = new PathSegment(start, end - start);
- start = end + 1; // resume search after the current character
- }
-
- // Residue
- var length = path.Length - start;
- if (length > 0 && count < maxCount)
- {
- segments[count++] = new PathSegment(start, length);
- }
+ segments[count++] = new PathSegment(start, end - start);
+ start = end + 1; // resume search after the current character
}
- internal unsafe void MinimalBaseline(string path, PathSegment* segments, int maxCount)
+ // Residue
+ var length = path.Length - start;
+ if (length > 0 && count < maxCount)
{
- var start = 1;
- var length = path.Length - start;
- segments[0] = new PathSegment(start, length);
+ segments[count++] = new PathSegment(start, length);
}
}
+
+ internal unsafe void MinimalBaseline(string path, PathSegment* segments, int maxCount)
+ {
+ var start = 1;
+ var length = path.Length - start;
+ segments[0] = new PathSegment(start, length);
+ }
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerEmptyBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerEmptyBenchmark.cs
index 8bb7b77779..4d6fe55a66 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerEmptyBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerEmptyBenchmark.cs
@@ -4,30 +4,29 @@
using System;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class FastPathTokenizerEmptyBenchmark : FastPathTokenizerBenchmarkBase
{
- public class FastPathTokenizerEmptyBenchmark : FastPathTokenizerBenchmarkBase
- {
- private const int MaxCount = 32;
- private static readonly string Input = "/";
+ private const int MaxCount = 32;
+ private static readonly string Input = "/";
- // This is super hardcoded implementation for comparison, we dont't expect to do better.
- [Benchmark(Baseline = true)]
- public unsafe void Baseline()
- {
- var path = Input;
- var segments = stackalloc PathSegment[MaxCount];
+ // This is super hardcoded implementation for comparison, we dont't expect to do better.
+ [Benchmark(Baseline = true)]
+ public unsafe void Baseline()
+ {
+ var path = Input;
+ var segments = stackalloc PathSegment[MaxCount];
- MinimalBaseline(path, segments, MaxCount);
- }
+ MinimalBaseline(path, segments, MaxCount);
+ }
- [Benchmark]
- public void Implementation()
- {
- var path = Input;
- Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
+ [Benchmark]
+ public void Implementation()
+ {
+ var path = Input;
+ Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
- FastPathTokenizer.Tokenize(path, segments);
- }
+ FastPathTokenizer.Tokenize(path, segments);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerLargeBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerLargeBenchmark.cs
index 96ec8683b8..085e150c94 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerLargeBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerLargeBenchmark.cs
@@ -4,34 +4,33 @@
using System;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class FastPathTokenizerLargeBenchmark : FastPathTokenizerBenchmarkBase
{
- public class FastPathTokenizerLargeBenchmark : FastPathTokenizerBenchmarkBase
- {
- private static readonly int MaxCount = 32;
- private static readonly string Input =
- "/heeeeeeeeeeyyyyyyyyyyy/this/is/a/string/with/lots/of/segments" +
- "/hoooooooooooooooooooooooooooooooooow long/do you think it should be?/I think" +
- "/like/32/segments/is /a/goood/number/dklfl/20303/dlflkf" +
- "/Im/tired/of/thinking/of/more/things/to/so";
+ private static readonly int MaxCount = 32;
+ private static readonly string Input =
+ "/heeeeeeeeeeyyyyyyyyyyy/this/is/a/string/with/lots/of/segments" +
+ "/hoooooooooooooooooooooooooooooooooow long/do you think it should be?/I think" +
+ "/like/32/segments/is /a/goood/number/dklfl/20303/dlflkf" +
+ "/Im/tired/of/thinking/of/more/things/to/so";
- // This is a naive reference implementation. We expect to do better.
- [Benchmark(Baseline = true)]
- public unsafe void Baseline()
- {
- var path = Input;
- var segments = stackalloc PathSegment[MaxCount];
+ // This is a naive reference implementation. We expect to do better.
+ [Benchmark(Baseline = true)]
+ public unsafe void Baseline()
+ {
+ var path = Input;
+ var segments = stackalloc PathSegment[MaxCount];
- NaiveBaseline(path, segments, MaxCount);
- }
+ NaiveBaseline(path, segments, MaxCount);
+ }
- [Benchmark]
- public void Implementation()
- {
- var path = Input;
- Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
+ [Benchmark]
+ public void Implementation()
+ {
+ var path = Input;
+ Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
- FastPathTokenizer.Tokenize(path, segments);
- }
+ FastPathTokenizer.Tokenize(path, segments);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerPlaintextBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerPlaintextBenchmark.cs
index df16357fec..fd1b881156 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerPlaintextBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerPlaintextBenchmark.cs
@@ -4,30 +4,29 @@
using System;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class FastPathTokenizerPlaintextBenchmark : FastPathTokenizerBenchmarkBase
{
- public class FastPathTokenizerPlaintextBenchmark : FastPathTokenizerBenchmarkBase
- {
- private const int MaxCount = 32;
- private static readonly string Input = "/plaintext";
+ private const int MaxCount = 32;
+ private static readonly string Input = "/plaintext";
- // This is super hardcoded implementation for comparison, we dont't expect to do better.
- [Benchmark(Baseline = true)]
- public unsafe void Baseline()
- {
- var path = Input;
- var segments = stackalloc PathSegment[MaxCount];
+ // This is super hardcoded implementation for comparison, we dont't expect to do better.
+ [Benchmark(Baseline = true)]
+ public unsafe void Baseline()
+ {
+ var path = Input;
+ var segments = stackalloc PathSegment[MaxCount];
- MinimalBaseline(path, segments, MaxCount);
- }
+ MinimalBaseline(path, segments, MaxCount);
+ }
- [Benchmark]
- public void Implementation()
- {
- var path = Input;
- Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
+ [Benchmark]
+ public void Implementation()
+ {
+ var path = Input;
+ Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
- FastPathTokenizer.Tokenize(path, segments);
- }
+ FastPathTokenizer.Tokenize(path, segments);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerSmallBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerSmallBenchmark.cs
index 4b56abc201..cf26edc3c5 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerSmallBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/FastPathTokenizerSmallBenchmark.cs
@@ -4,30 +4,29 @@
using System;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class FastPathTokenizerSmallBenchmark : FastPathTokenizerBenchmarkBase
{
- public class FastPathTokenizerSmallBenchmark : FastPathTokenizerBenchmarkBase
- {
- private const int MaxCount = 32;
- private static readonly string Input = "/hello/world/cool";
+ private const int MaxCount = 32;
+ private static readonly string Input = "/hello/world/cool";
- // This is a naive reference implementation. We expect to do better.
- [Benchmark(Baseline = true)]
- public unsafe void Baseline()
- {
- var path = Input;
- var segments = stackalloc PathSegment[MaxCount];
+ // This is a naive reference implementation. We expect to do better.
+ [Benchmark(Baseline = true)]
+ public unsafe void Baseline()
+ {
+ var path = Input;
+ var segments = stackalloc PathSegment[MaxCount];
- NaiveBaseline(path, segments, MaxCount);
- }
+ NaiveBaseline(path, segments, MaxCount);
+ }
- [Benchmark]
- public void Implementation()
- {
- var path = Input;
- Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
+ [Benchmark]
+ public void Implementation()
+ {
+ var path = Input;
+ Span<PathSegment> segments = stackalloc PathSegment[MaxCount];
- FastPathTokenizer.Tokenize(path, segments);
- }
+ FastPathTokenizer.Tokenize(path, segments);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/HttpMethodPolicyJumpTableBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/HttpMethodPolicyJumpTableBenchmark.cs
index 40eb396587..21392a7d86 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/HttpMethodPolicyJumpTableBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/HttpMethodPolicyJumpTableBenchmark.cs
@@ -5,50 +5,49 @@ using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class HttpMethodPolicyJumpTableBenchmark
{
- public class HttpMethodPolicyJumpTableBenchmark
- {
- private PolicyJumpTable _dictionaryJumptable;
- private PolicyJumpTable _singleEntryJumptable;
- private DefaultHttpContext _httpContext;
+ private PolicyJumpTable _dictionaryJumptable;
+ private PolicyJumpTable _singleEntryJumptable;
+ private DefaultHttpContext _httpContext;
- [GlobalSetup]
- public void Setup()
- {
- _dictionaryJumptable = new HttpMethodDictionaryPolicyJumpTable(
- 0,
- new Dictionary<string, int>
- {
- [HttpMethods.Get] = 1
- },
- -1,
- new Dictionary<string, int>
- {
- [HttpMethods.Get] = 2
- });
- _singleEntryJumptable = new HttpMethodSingleEntryPolicyJumpTable(
- 0,
- HttpMethods.Get,
- -1,
- supportsCorsPreflight: true,
- -1,
- 2);
+ [GlobalSetup]
+ public void Setup()
+ {
+ _dictionaryJumptable = new HttpMethodDictionaryPolicyJumpTable(
+ 0,
+ new Dictionary<string, int>
+ {
+ [HttpMethods.Get] = 1
+ },
+ -1,
+ new Dictionary<string, int>
+ {
+ [HttpMethods.Get] = 2
+ });
+ _singleEntryJumptable = new HttpMethodSingleEntryPolicyJumpTable(
+ 0,
+ HttpMethods.Get,
+ -1,
+ supportsCorsPreflight: true,
+ -1,
+ 2);
- _httpContext = new DefaultHttpContext();
- _httpContext.Request.Method = HttpMethods.Get;
- }
+ _httpContext = new DefaultHttpContext();
+ _httpContext.Request.Method = HttpMethods.Get;
+ }
- [Benchmark]
- public int DictionaryPolicyJumpTable()
- {
- return _dictionaryJumptable.GetDestination(_httpContext);
- }
+ [Benchmark]
+ public int DictionaryPolicyJumpTable()
+ {
+ return _dictionaryJumptable.GetDestination(_httpContext);
+ }
- [Benchmark]
- public int SingleEntryPolicyJumpTable()
- {
- return _singleEntryJumptable.GetDestination(_httpContext);
- }
+ [Benchmark]
+ public int SingleEntryPolicyJumpTable()
+ {
+ return _singleEntryJumptable.GetDestination(_httpContext);
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableMultipleEntryBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableMultipleEntryBenchmark.cs
index e674028998..81f2520c6e 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableMultipleEntryBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableMultipleEntryBenchmark.cs
@@ -5,173 +5,172 @@ using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class JumpTableMultipleEntryBenchmark
{
- public class JumpTableMultipleEntryBenchmark
+ private string[] _strings;
+ private PathSegment[] _segments;
+
+ private JumpTable _linearSearch;
+ private JumpTable _dictionary;
+ private JumpTable _trie;
+ private JumpTable _vectorTrie;
+
+ // All factors of 100 to support sampling
+ [Params(2, 5, 10, 25, 50, 100)]
+ public int Count;
+
+ [GlobalSetup]
+ public void Setup()
{
- private string[] _strings;
- private PathSegment[] _segments;
+ _strings = GetStrings(100);
+ _segments = new PathSegment[100];
- private JumpTable _linearSearch;
- private JumpTable _dictionary;
- private JumpTable _trie;
- private JumpTable _vectorTrie;
+ for (var i = 0; i < _strings.Length; i++)
+ {
+ _segments[i] = new PathSegment(0, _strings[i].Length);
+ }
- // All factors of 100 to support sampling
- [Params(2, 5, 10, 25, 50, 100)]
- public int Count;
+ var samples = new int[Count];
+ for (var i = 0; i < samples.Length; i++)
+ {
+ samples[i] = i * (_strings.Length / Count);
+ }
- [GlobalSetup]
- public void Setup()
+ var entries = new List<(string text, int _)>();
+ for (var i = 0; i < samples.Length; i++)
{
- _strings = GetStrings(100);
- _segments = new PathSegment[100];
+ entries.Add((_strings[samples[i]], i));
+ }
+
+ _linearSearch = new LinearSearchJumpTable(0, -1, entries.ToArray());
+ _dictionary = new DictionaryJumpTable(0, -1, entries.ToArray());
+ _trie = new ILEmitTrieJumpTable(0, -1, entries.ToArray(), vectorize: false, _dictionary);
+ _vectorTrie = new ILEmitTrieJumpTable(0, -1, entries.ToArray(), vectorize: true, _dictionary);
+ }
+
+ // This baseline is similar to SingleEntryJumpTable. We just want
+ // something stable to compare against.
+ [Benchmark(Baseline = true, OperationsPerInvoke = 100)]
+ public int Baseline()
+ {
+ var strings = _strings;
+ var segments = _segments;
- for (var i = 0; i < _strings.Length; i++)
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ var @string = strings[i];
+ var segment = segments[i];
+
+ if (segment.Length == 0)
{
- _segments[i] = new PathSegment(0, _strings[i].Length);
+ destination = -1;
}
-
- var samples = new int[Count];
- for (var i = 0; i < samples.Length; i++)
+ else if (segment.Length != @string.Length)
{
- samples[i] = i * (_strings.Length / Count);
+ destination = 1;
}
-
- var entries = new List<(string text, int _)>();
- for (var i = 0; i < samples.Length; i++)
+ else
{
- entries.Add((_strings[samples[i]], i));
+ destination = string.Compare(
+ @string,
+ segment.Start,
+ @string,
+ 0,
+ segment.Length,
+ StringComparison.OrdinalIgnoreCase);
}
-
- _linearSearch = new LinearSearchJumpTable(0, -1, entries.ToArray());
- _dictionary = new DictionaryJumpTable(0, -1, entries.ToArray());
- _trie = new ILEmitTrieJumpTable(0, -1, entries.ToArray(), vectorize: false, _dictionary);
- _vectorTrie = new ILEmitTrieJumpTable(0, -1, entries.ToArray(), vectorize: true, _dictionary);
}
- // This baseline is similar to SingleEntryJumpTable. We just want
- // something stable to compare against.
- [Benchmark(Baseline = true, OperationsPerInvoke = 100)]
- public int Baseline()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- var @string = strings[i];
- var segment = segments[i];
-
- if (segment.Length == 0)
- {
- destination = -1;
- }
- else if (segment.Length != @string.Length)
- {
- destination = 1;
- }
- else
- {
- destination = string.Compare(
- @string,
- segment.Start,
- @string,
- 0,
- segment.Length,
- StringComparison.OrdinalIgnoreCase);
- }
- }
+ [Benchmark(OperationsPerInvoke = 100)]
+ public int LinearSearch()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _linearSearch.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 100)]
- public int LinearSearch()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _linearSearch.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 100)]
+ public int Dictionary()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _dictionary.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 100)]
- public int Dictionary()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _dictionary.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 100)]
+ public int Trie()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _trie.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 100)]
- public int Trie()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _trie.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 100)]
+ public int VectorTrie()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _vectorTrie.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 100)]
- public int VectorTrie()
+ return destination;
+ }
+
+ private static string[] GetStrings(int count)
+ {
+ var strings = new string[count];
+ for (var i = 0; i < count; i++)
{
- var strings = _strings;
- var segments = _segments;
+ var guid = Guid.NewGuid().ToString();
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
+ // Between 5 and 36 characters
+ var text = guid.Substring(0, Math.Max(5, Math.Min(i, 36)));
+ if (char.IsDigit(text[0]))
{
- destination = _vectorTrie.GetDestination(strings[i], segments[i]);
+ // Convert first character to a letter.
+ text = ((char)(text[0] + ('G' - '0'))) + text.Substring(1);
}
- return destination;
- }
-
- private static string[] GetStrings(int count)
- {
- var strings = new string[count];
- for (var i = 0; i < count; i++)
+ if (i % 2 == 0)
{
- var guid = Guid.NewGuid().ToString();
-
- // Between 5 and 36 characters
- var text = guid.Substring(0, Math.Max(5, Math.Min(i, 36)));
- if (char.IsDigit(text[0]))
- {
- // Convert first character to a letter.
- text = ((char)(text[0] + ('G' - '0'))) + text.Substring(1);
- }
-
- if (i % 2 == 0)
- {
- // Lowercase half of them
- text = text.ToLowerInvariant();
- }
-
- strings[i] = text;
+ // Lowercase half of them
+ text = text.ToLowerInvariant();
}
- return strings;
+ strings[i] = text;
}
+
+ return strings;
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableSingleEntryBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableSingleEntryBenchmark.cs
index 9c1ce9842f..dee68914fb 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableSingleEntryBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableSingleEntryBenchmark.cs
@@ -6,137 +6,136 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class JumpTableSingleEntryBenchmark
{
- public class JumpTableSingleEntryBenchmark
- {
- private JumpTable _default;
- private JumpTable _trie;
- private JumpTable _vectorTrie;
- private JumpTable _ascii;
+ private JumpTable _default;
+ private JumpTable _trie;
+ private JumpTable _vectorTrie;
+ private JumpTable _ascii;
- private string[] _strings;
- private PathSegment[] _segments;
+ private string[] _strings;
+ private PathSegment[] _segments;
- [GlobalSetup]
- public void Setup()
- {
- _default = new SingleEntryJumpTable(0, -1, "hello-world", 1);
- _trie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: false, _default);
- _vectorTrie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: true, _default);
- _ascii = new SingleEntryAsciiJumpTable(0, -1, "hello-world", 1);
+ [GlobalSetup]
+ public void Setup()
+ {
+ _default = new SingleEntryJumpTable(0, -1, "hello-world", 1);
+ _trie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: false, _default);
+ _vectorTrie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: true, _default);
+ _ascii = new SingleEntryAsciiJumpTable(0, -1, "hello-world", 1);
- _strings = new string[]
- {
+ _strings = new string[]
+ {
"index/foo/2",
"index/hello-world1/2",
"index/hello-world/2",
"index//2",
"index/hillo-goodbye/2",
- };
- _segments = new PathSegment[]
- {
+ };
+ _segments = new PathSegment[]
+ {
new PathSegment(6, 3),
new PathSegment(6, 12),
new PathSegment(6, 11),
new PathSegment(6, 0),
new PathSegment(6, 13),
- };
- }
+ };
+ }
- [Benchmark(Baseline = true, OperationsPerInvoke = 5)]
- public int Baseline()
+ [Benchmark(Baseline = true, OperationsPerInvoke = 5)]
+ public int Baseline()
+ {
+ var strings = _strings;
+ var segments = _segments;
+
+ int destination = 0;
+ for (var i = 0; i < strings.Length; i++)
{
- var strings = _strings;
- var segments = _segments;
+ var @string = strings[i];
+ var segment = segments[i];
- int destination = 0;
- for (var i = 0; i < strings.Length; i++)
+ if (segment.Length == 0)
{
- var @string = strings[i];
- var segment = segments[i];
-
- if (segment.Length == 0)
- {
- destination = -1;
- }
- else if (segment.Length != "hello-world".Length)
- {
- destination = 1;
- }
- else
- {
- destination = string.Compare(
- @string,
- segment.Start,
- "hello-world",
- 0,
- segment.Length,
- StringComparison.OrdinalIgnoreCase);
- }
+ destination = -1;
+ }
+ else if (segment.Length != "hello-world".Length)
+ {
+ destination = 1;
+ }
+ else
+ {
+ destination = string.Compare(
+ @string,
+ segment.Start,
+ "hello-world",
+ 0,
+ segment.Length,
+ StringComparison.OrdinalIgnoreCase);
}
-
- return destination;
}
- [Benchmark(OperationsPerInvoke = 5)]
- public int Default()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _default.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 5)]
+ public int Default()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _default.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 5)]
- public int Ascii()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _ascii.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 5)]
+ public int Ascii()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _ascii.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 5)]
- public int Trie()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _trie.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 5)]
+ public int Trie()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _trie.GetDestination(strings[i], segments[i]);
}
- [Benchmark(OperationsPerInvoke = 5)]
- public int VectorTrie()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _vectorTrie.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 5)]
+ public int VectorTrie()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _vectorTrie.GetDestination(strings[i], segments[i]);
}
+
+ return destination;
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableZeroEntryBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableZeroEntryBenchmark.cs
index 4371f1c7c6..ece9820d77 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableZeroEntryBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/JumpTableZeroEntryBenchmark.cs
@@ -3,64 +3,63 @@
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class JumpTableZeroEntryBenchmark
{
- public class JumpTableZeroEntryBenchmark
- {
- private JumpTable _table;
- private string[] _strings;
- private PathSegment[] _segments;
+ private JumpTable _table;
+ private string[] _strings;
+ private PathSegment[] _segments;
- [GlobalSetup]
- public void Setup()
+ [GlobalSetup]
+ public void Setup()
+ {
+ _table = new ZeroEntryJumpTable(0, -1);
+ _strings = new string[]
{
- _table = new ZeroEntryJumpTable(0, -1);
- _strings = new string[]
- {
"index/foo/2",
"index/hello-world1/2",
"index/hello-world/2",
"index//2",
"index/hillo-goodbye/2",
- };
- _segments = new PathSegment[]
- {
+ };
+ _segments = new PathSegment[]
+ {
new PathSegment(6, 3),
new PathSegment(6, 12),
new PathSegment(6, 11),
new PathSegment(6, 0),
new PathSegment(6, 13),
- };
- }
-
- [Benchmark(Baseline=true, OperationsPerInvoke = 5)]
- public int Baseline()
- {
- var strings = _strings;
- var segments = _segments;
+ };
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = segments[i].Length == 0 ? -1 : 0;
- }
+ [Benchmark(Baseline = true, OperationsPerInvoke = 5)]
+ public int Baseline()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = segments[i].Length == 0 ? -1 : 0;
}
- [Benchmark(OperationsPerInvoke = 5)]
- public int Implementation()
- {
- var strings = _strings;
- var segments = _segments;
+ return destination;
+ }
- var destination = 0;
- for (var i = 0; i < strings.Length; i++)
- {
- destination = _table.GetDestination(strings[i], segments[i]);
- }
+ [Benchmark(OperationsPerInvoke = 5)]
+ public int Implementation()
+ {
+ var strings = _strings;
+ var segments = _segments;
- return destination;
+ var destination = 0;
+ for (var i = 0; i < strings.Length; i++)
+ {
+ destination = _table.GetDestination(strings[i], segments[i]);
}
+
+ return destination;
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherAzureBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherAzureBenchmark.cs
index 5d3b802be8..a7b45af5da 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherAzureBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherAzureBenchmark.cs
@@ -5,55 +5,54 @@ using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Generated from https://github.com/Azure/azure-rest-api-specs
+public class MatcherAzureBenchmark : MatcherAzureBenchmarkBase
{
- // Generated from https://github.com/Azure/azure-rest-api-specs
- public class MatcherAzureBenchmark : MatcherAzureBenchmarkBase
- {
- private const int SampleCount = 100;
+ private const int SampleCount = 100;
- private BarebonesMatcher _baseline;
- private Matcher _dfa;
+ private BarebonesMatcher _baseline;
+ private Matcher _dfa;
- private int[] _samples;
+ private int[] _samples;
- [GlobalSetup]
- public void Setup()
- {
- SetupEndpoints();
+ [GlobalSetup]
+ public void Setup()
+ {
+ SetupEndpoints();
- SetupRequests();
+ SetupRequests();
- // The perf is kinda slow for these benchmarks, so we do some sampling
- // of the request data.
- _samples = SampleRequests(EndpointCount, SampleCount);
+ // The perf is kinda slow for these benchmarks, so we do some sampling
+ // of the request data.
+ _samples = SampleRequests(EndpointCount, SampleCount);
- _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
- _dfa = SetupMatcher(CreateDfaMatcherBuilder());
- }
+ _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
+ _dfa = SetupMatcher(CreateDfaMatcherBuilder());
+ }
- [Benchmark(Baseline = true, OperationsPerInvoke = SampleCount)]
- public async Task Baseline()
+ [Benchmark(Baseline = true, OperationsPerInvoke = SampleCount)]
+ public async Task Baseline()
+ {
+ for (var i = 0; i < SampleCount; i++)
{
- for (var i = 0; i < SampleCount; i++)
- {
- var sample = _samples[i];
- var httpContext = Requests[sample];
- await _baseline.Matchers[sample].MatchAsync(httpContext);
- Validate(httpContext, Endpoints[sample], httpContext.GetEndpoint());
- }
+ var sample = _samples[i];
+ var httpContext = Requests[sample];
+ await _baseline.Matchers[sample].MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[sample], httpContext.GetEndpoint());
}
+ }
- [Benchmark(OperationsPerInvoke = SampleCount)]
- public async Task Dfa()
+ [Benchmark(OperationsPerInvoke = SampleCount)]
+ public async Task Dfa()
+ {
+ for (var i = 0; i < SampleCount; i++)
{
- for (var i = 0; i < SampleCount; i++)
- {
- var sample = _samples[i];
- var httpContext = Requests[sample];
- await _dfa.MatchAsync(httpContext);
- Validate(httpContext, Endpoints[sample], httpContext.GetEndpoint());
- }
+ var sample = _samples[i];
+ var httpContext = Requests[sample];
+ await _dfa.MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[sample], httpContext.GetEndpoint());
}
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderAzureBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderAzureBenchmark.cs
index f6ed69f098..75656bd641 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderAzureBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderAzureBenchmark.cs
@@ -5,27 +5,26 @@ using System;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Generated from https://github.com/APIs-guru/openapi-directory
+// Use https://editor2.swagger.io/ to convert from yaml to json-
+public class MatcherBuilderAzureBenchmark : MatcherAzureBenchmarkBase
{
- // Generated from https://github.com/APIs-guru/openapi-directory
- // Use https://editor2.swagger.io/ to convert from yaml to json-
- public class MatcherBuilderAzureBenchmark : MatcherAzureBenchmarkBase
- {
- private IServiceProvider _services;
+ private IServiceProvider _services;
- [GlobalSetup]
- public void Setup()
- {
- SetupEndpoints();
+ [GlobalSetup]
+ public void Setup()
+ {
+ SetupEndpoints();
- _services = CreateServices();
- }
+ _services = CreateServices();
+ }
- [Benchmark]
- public void Dfa()
- {
- var builder = _services.GetRequiredService<DfaMatcherBuilder>();
- SetupMatcher(builder);
- }
+ [Benchmark]
+ public void Dfa()
+ {
+ var builder = _services.GetRequiredService<DfaMatcherBuilder>();
+ SetupMatcher(builder);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderGithubBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderGithubBenchmark.cs
index 372c7ff2dd..43bc4f4a7f 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderGithubBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderGithubBenchmark.cs
@@ -5,27 +5,26 @@ using System;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Generated from https://github.com/APIs-guru/openapi-directory
+// Use https://editor2.swagger.io/ to convert from yaml to json-
+public class MatcherBuilderGithubBenchmark : MatcherGithubBenchmarkBase
{
- // Generated from https://github.com/APIs-guru/openapi-directory
- // Use https://editor2.swagger.io/ to convert from yaml to json-
- public class MatcherBuilderGithubBenchmark : MatcherGithubBenchmarkBase
- {
- private IServiceProvider _services;
+ private IServiceProvider _services;
- [GlobalSetup]
- public void Setup()
- {
- SetupEndpoints();
+ [GlobalSetup]
+ public void Setup()
+ {
+ SetupEndpoints();
- _services = CreateServices();
- }
+ _services = CreateServices();
+ }
- [Benchmark]
- public void Dfa()
- {
- var builder = _services.GetRequiredService<DfaMatcherBuilder>();
- SetupMatcher(builder);
- }
+ [Benchmark]
+ public void Dfa()
+ {
+ var builder = _services.GetRequiredService<DfaMatcherBuilder>();
+ SetupMatcher(builder);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderMultipleEntryBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderMultipleEntryBenchmark.cs
index 2973af0e09..861cb2b8ca 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderMultipleEntryBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherBuilderMultipleEntryBenchmark.cs
@@ -12,36 +12,36 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public partial class MatcherBuilderMultipleEntryBenchmark : EndpointRoutingBenchmarkBase
{
- public partial class MatcherBuilderMultipleEntryBenchmark : EndpointRoutingBenchmarkBase
+ private IServiceProvider _services;
+ private List<MatcherPolicy> _policies;
+ private ILoggerFactory _loggerFactory;
+ private DefaultEndpointSelector _selector;
+ private DefaultParameterPolicyFactory _parameterPolicyFactory;
+
+ [GlobalSetup]
+ public void Setup()
{
- private IServiceProvider _services;
- private List<MatcherPolicy> _policies;
- private ILoggerFactory _loggerFactory;
- private DefaultEndpointSelector _selector;
- private DefaultParameterPolicyFactory _parameterPolicyFactory;
-
- [GlobalSetup]
- public void Setup()
- {
- Endpoints = new RouteEndpoint[10];
- Endpoints[0] = CreateEndpoint("/product", "GET");
- Endpoints[1] = CreateEndpoint("/product/{id}", "GET");
-
- Endpoints[2] = CreateEndpoint("/account", "GET");
- Endpoints[3] = CreateEndpoint("/account/{id}");
- Endpoints[4] = CreateEndpoint("/account/{id}", "POST");
- Endpoints[5] = CreateEndpoint("/account/{id}", "UPDATE");
-
- Endpoints[6] = CreateEndpoint("/v2/account", "GET");
- Endpoints[7] = CreateEndpoint("/v2/account/{id}");
- Endpoints[8] = CreateEndpoint("/v2/account/{id}", "POST");
- Endpoints[9] = CreateEndpoint("/v2/account/{id}", "UPDATE");
-
- // Define an unordered mixture of policies that implement INodeBuilderPolicy,
- // IEndpointComparerPolicy and/or IEndpointSelectorPolicy
- _policies = new List<MatcherPolicy>()
+ Endpoints = new RouteEndpoint[10];
+ Endpoints[0] = CreateEndpoint("/product", "GET");
+ Endpoints[1] = CreateEndpoint("/product/{id}", "GET");
+
+ Endpoints[2] = CreateEndpoint("/account", "GET");
+ Endpoints[3] = CreateEndpoint("/account/{id}");
+ Endpoints[4] = CreateEndpoint("/account/{id}", "POST");
+ Endpoints[5] = CreateEndpoint("/account/{id}", "UPDATE");
+
+ Endpoints[6] = CreateEndpoint("/v2/account", "GET");
+ Endpoints[7] = CreateEndpoint("/v2/account/{id}");
+ Endpoints[8] = CreateEndpoint("/v2/account/{id}", "POST");
+ Endpoints[9] = CreateEndpoint("/v2/account/{id}", "UPDATE");
+
+ // Define an unordered mixture of policies that implement INodeBuilderPolicy,
+ // IEndpointComparerPolicy and/or IEndpointSelectorPolicy
+ _policies = new List<MatcherPolicy>()
{
CreateNodeBuilderPolicy(4),
CreateUberPolicy(2),
@@ -55,154 +55,153 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateUberPolicy(12),
CreateEndpointComparerPolicy(11)
};
- _loggerFactory = NullLoggerFactory.Instance;
- _selector = new DefaultEndpointSelector();
- _parameterPolicyFactory = new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), new TestServiceProvider());
+ _loggerFactory = NullLoggerFactory.Instance;
+ _selector = new DefaultEndpointSelector();
+ _parameterPolicyFactory = new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), new TestServiceProvider());
- _services = CreateServices();
- }
+ _services = CreateServices();
+ }
- private Matcher SetupMatcher(MatcherBuilder builder)
+ private Matcher SetupMatcher(MatcherBuilder builder)
+ {
+ for (int i = 0; i < Endpoints.Length; i++)
{
- for (int i = 0; i < Endpoints.Length; i++)
- {
- builder.AddEndpoint(Endpoints[i]);
- }
- return builder.Build();
+ builder.AddEndpoint(Endpoints[i]);
}
+ return builder.Build();
+ }
+
+ [Benchmark]
+ public void Dfa()
+ {
+ var builder = _services.GetRequiredService<DfaMatcherBuilder>();
+ SetupMatcher(builder);
+ }
+
+ [Benchmark]
+ public void Constructor_Policies()
+ {
+ new DfaMatcherBuilder(_loggerFactory, _parameterPolicyFactory, _selector, _policies);
+ }
+
+ private static MatcherPolicy CreateNodeBuilderPolicy(int order)
+ {
+ return new TestNodeBuilderPolicy(order);
+ }
+ private static MatcherPolicy CreateEndpointComparerPolicy(int order)
+ {
+ return new TestEndpointComparerPolicy(order);
+ }
- [Benchmark]
- public void Dfa()
+ private static MatcherPolicy CreateEndpointSelectorPolicy(int order)
+ {
+ return new TestEndpointSelectorPolicy(order);
+ }
+
+ private static MatcherPolicy CreateUberPolicy(int order)
+ {
+ return new TestUberPolicy(order);
+ }
+
+ private class TestUberPolicy : TestMatcherPolicyBase, INodeBuilderPolicy, IEndpointComparerPolicy
+ {
+ public TestUberPolicy(int order) : base(order)
{
- var builder = _services.GetRequiredService<DfaMatcherBuilder>();
- SetupMatcher(builder);
}
- [Benchmark]
- public void Constructor_Policies()
+ public IComparer<Endpoint> Comparer => new TestEndpointComparer();
+
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
- new DfaMatcherBuilder(_loggerFactory, _parameterPolicyFactory, _selector, _policies);
+ return false;
}
- private static MatcherPolicy CreateNodeBuilderPolicy(int order)
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{
- return new TestNodeBuilderPolicy(order);
+ throw new NotImplementedException();
}
- private static MatcherPolicy CreateEndpointComparerPolicy(int order)
+
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{
- return new TestEndpointComparerPolicy(order);
+ throw new NotImplementedException();
}
+ }
- private static MatcherPolicy CreateEndpointSelectorPolicy(int order)
+ private class TestNodeBuilderPolicy : TestMatcherPolicyBase, INodeBuilderPolicy
+ {
+ public TestNodeBuilderPolicy(int order) : base(order)
{
- return new TestEndpointSelectorPolicy(order);
}
- private static MatcherPolicy CreateUberPolicy(int order)
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
- return new TestUberPolicy(order);
+ return false;
}
- private class TestUberPolicy : TestMatcherPolicyBase, INodeBuilderPolicy, IEndpointComparerPolicy
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{
- public TestUberPolicy(int order) : base(order)
- {
- }
-
- public IComparer<Endpoint> Comparer => new TestEndpointComparer();
-
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return false;
- }
-
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
- {
- throw new NotImplementedException();
- }
-
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
- private class TestNodeBuilderPolicy : TestMatcherPolicyBase, INodeBuilderPolicy
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{
- public TestNodeBuilderPolicy(int order) : base(order)
- {
- }
-
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return false;
- }
-
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
- {
- throw new NotImplementedException();
- }
-
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
+ }
- private class TestEndpointComparerPolicy : TestMatcherPolicyBase, IEndpointComparerPolicy
+ private class TestEndpointComparerPolicy : TestMatcherPolicyBase, IEndpointComparerPolicy
+ {
+ public TestEndpointComparerPolicy(int order) : base(order)
{
- public TestEndpointComparerPolicy(int order) : base(order)
- {
- }
-
- public IComparer<Endpoint> Comparer => new TestEndpointComparer();
+ }
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return false;
- }
+ public IComparer<Endpoint> Comparer => new TestEndpointComparer();
- public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
- {
- throw new NotImplementedException();
- }
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ return false;
}
- private class TestEndpointSelectorPolicy : TestMatcherPolicyBase, IEndpointSelectorPolicy
+ public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{
- public TestEndpointSelectorPolicy(int order) : base(order)
- {
- }
+ throw new NotImplementedException();
+ }
+ }
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return false;
- }
+ private class TestEndpointSelectorPolicy : TestMatcherPolicyBase, IEndpointSelectorPolicy
+ {
+ public TestEndpointSelectorPolicy(int order) : base(order)
+ {
+ }
- public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
- {
- throw new NotImplementedException();
- }
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ return false;
}
- private abstract class TestMatcherPolicyBase : MatcherPolicy
+ public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{
- private readonly int _order;
+ throw new NotImplementedException();
+ }
+ }
- protected TestMatcherPolicyBase(int order)
- {
- _order = order;
- }
+ private abstract class TestMatcherPolicyBase : MatcherPolicy
+ {
+ private readonly int _order;
- public override int Order { get { return _order; } }
+ protected TestMatcherPolicyBase(int order)
+ {
+ _order = order;
}
- private class TestEndpointComparer : IComparer<Endpoint>
+ public override int Order { get { return _order; } }
+ }
+
+ private class TestEndpointComparer : IComparer<Endpoint>
+ {
+ public int Compare(Endpoint x, Endpoint y)
{
- public int Compare(Endpoint x, Endpoint y)
- {
- return 0;
- }
+ return 0;
}
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherGithubBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherGithubBenchmark.cs
index 6161a411fa..1afd6ba95e 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherGithubBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherGithubBenchmark.cs
@@ -6,46 +6,45 @@ using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Generated from https://github.com/APIs-guru/openapi-directory
+// Use https://editor2.swagger.io/ to convert from yaml to json-
+public class MatcherGithubBenchmark : MatcherGithubBenchmarkBase
{
- // Generated from https://github.com/APIs-guru/openapi-directory
- // Use https://editor2.swagger.io/ to convert from yaml to json-
- public class MatcherGithubBenchmark : MatcherGithubBenchmarkBase
- {
- private BarebonesMatcher _baseline;
- private Matcher _dfa;
+ private BarebonesMatcher _baseline;
+ private Matcher _dfa;
- [GlobalSetup]
- public void Setup()
- {
- SetupEndpoints();
+ [GlobalSetup]
+ public void Setup()
+ {
+ SetupEndpoints();
- SetupRequests();
+ SetupRequests();
- _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
- _dfa = SetupMatcher(CreateDfaMatcherBuilder());
- }
+ _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
+ _dfa = SetupMatcher(CreateDfaMatcherBuilder());
+ }
- [Benchmark(Baseline = true, OperationsPerInvoke = EndpointCount)]
- public async Task Baseline()
+ [Benchmark(Baseline = true, OperationsPerInvoke = EndpointCount)]
+ public async Task Baseline()
+ {
+ for (var i = 0; i < EndpointCount; i++)
{
- for (var i = 0; i < EndpointCount; i++)
- {
- var httpContext = Requests[i];
- await _baseline.Matchers[i].MatchAsync(httpContext);
- Validate(httpContext, Endpoints[i], httpContext.GetEndpoint());
- }
+ var httpContext = Requests[i];
+ await _baseline.Matchers[i].MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[i], httpContext.GetEndpoint());
}
+ }
- [Benchmark( OperationsPerInvoke = EndpointCount)]
- public async Task Dfa()
+ [Benchmark(OperationsPerInvoke = EndpointCount)]
+ public async Task Dfa()
+ {
+ for (var i = 0; i < EndpointCount; i++)
{
- for (var i = 0; i < EndpointCount; i++)
- {
- var httpContext = Requests[i];
- await _dfa.MatchAsync(httpContext);
- Validate(httpContext, Endpoints[i], httpContext.GetEndpoint());
- }
+ var httpContext = Requests[i];
+ await _dfa.MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[i], httpContext.GetEndpoint());
}
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherSingleEntryBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherSingleEntryBenchmark.cs
index 6ce2ccecaf..fcaefdf658 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherSingleEntryBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/MatcherSingleEntryBenchmark.cs
@@ -6,73 +6,72 @@ using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Just like TechEmpower Plaintext
+public partial class MatcherSingleEntryBenchmark : EndpointRoutingBenchmarkBase
{
- // Just like TechEmpower Plaintext
- public partial class MatcherSingleEntryBenchmark : EndpointRoutingBenchmarkBase
- {
- private BarebonesMatcher _baseline;
- private Matcher _dfa;
- private Matcher _route;
- private Matcher _tree;
+ private BarebonesMatcher _baseline;
+ private Matcher _dfa;
+ private Matcher _route;
+ private Matcher _tree;
- [GlobalSetup]
- public void Setup()
- {
- Endpoints = new RouteEndpoint[1];
- Endpoints[0] = CreateEndpoint("/plaintext");
+ [GlobalSetup]
+ public void Setup()
+ {
+ Endpoints = new RouteEndpoint[1];
+ Endpoints[0] = CreateEndpoint("/plaintext");
- Requests = new HttpContext[1];
- Requests[0] = new DefaultHttpContext();
- Requests[0].RequestServices = CreateServices();
- Requests[0].Request.Path = "/plaintext";
+ Requests = new HttpContext[1];
+ Requests[0] = new DefaultHttpContext();
+ Requests[0].RequestServices = CreateServices();
+ Requests[0].Request.Path = "/plaintext";
- _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
- _dfa = SetupMatcher(CreateDfaMatcherBuilder());
- _route = SetupMatcher(new RouteMatcherBuilder());
- _tree = SetupMatcher(new TreeRouterMatcherBuilder());
- }
+ _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder());
+ _dfa = SetupMatcher(CreateDfaMatcherBuilder());
+ _route = SetupMatcher(new RouteMatcherBuilder());
+ _tree = SetupMatcher(new TreeRouterMatcherBuilder());
+ }
- private Matcher SetupMatcher(MatcherBuilder builder)
- {
- builder.AddEndpoint(Endpoints[0]);
- return builder.Build();
- }
+ private Matcher SetupMatcher(MatcherBuilder builder)
+ {
+ builder.AddEndpoint(Endpoints[0]);
+ return builder.Build();
+ }
- [Benchmark(Baseline = true)]
- public async Task Baseline()
- {
- var httpContext = Requests[0];
+ [Benchmark(Baseline = true)]
+ public async Task Baseline()
+ {
+ var httpContext = Requests[0];
- await _baseline.MatchAsync(httpContext);
- Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
- }
+ await _baseline.MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
+ }
- [Benchmark]
- public async Task Dfa()
- {
- var httpContext = Requests[0];
+ [Benchmark]
+ public async Task Dfa()
+ {
+ var httpContext = Requests[0];
- await _dfa.MatchAsync(httpContext);
- Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
- }
+ await _dfa.MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
+ }
- [Benchmark]
- public async Task LegacyTreeRouter()
- {
- var httpContext = Requests[0];
+ [Benchmark]
+ public async Task LegacyTreeRouter()
+ {
+ var httpContext = Requests[0];
- await _tree.MatchAsync(httpContext);
- Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
- }
+ await _tree.MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
+ }
- [Benchmark]
- public async Task LegacyRouter()
- {
- var httpContext = Requests[0];
+ [Benchmark]
+ public async Task LegacyRouter()
+ {
+ var httpContext = Requests[0];
- await _route.MatchAsync(httpContext);
- Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
- }
+ await _route.MatchAsync(httpContext);
+ Validate(httpContext, Endpoints[0], httpContext.GetEndpoint());
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/RouteEndpointAzureBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/RouteEndpointAzureBenchmark.cs
index 392d82070b..0652ba306b 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/RouteEndpointAzureBenchmark.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/RouteEndpointAzureBenchmark.cs
@@ -3,14 +3,13 @@
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class RouteEndpointAzureBenchmark : MatcherAzureBenchmarkBase
{
- public class RouteEndpointAzureBenchmark : MatcherAzureBenchmarkBase
+ [Benchmark]
+ public void CreateEndpoints()
{
- [Benchmark]
- public void CreateEndpoints()
- {
- SetupEndpoints();
- }
+ SetupEndpoints();
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcher.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcher.cs
index 3543a2bb38..775e81c544 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcher.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcher.cs
@@ -6,49 +6,48 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// A test-only matcher implementation - used as a baseline for simpler
+// perf tests. The idea with this matcher is that we can cheat on the requirements
+// to establish a lower bound for perf comparisons.
+internal sealed class TrivialMatcher : Matcher
{
- // A test-only matcher implementation - used as a baseline for simpler
- // perf tests. The idea with this matcher is that we can cheat on the requirements
- // to establish a lower bound for perf comparisons.
- internal sealed class TrivialMatcher : Matcher
+ private readonly RouteEndpoint _endpoint;
+ private readonly Candidate[] _candidates;
+
+ public TrivialMatcher(RouteEndpoint endpoint)
{
- private readonly RouteEndpoint _endpoint;
- private readonly Candidate[] _candidates;
+ _endpoint = endpoint;
- public TrivialMatcher(RouteEndpoint endpoint)
- {
- _endpoint = endpoint;
+ _candidates = new Candidate[] { new Candidate(endpoint), };
+ }
- _candidates = new Candidate[] { new Candidate(endpoint), };
+ public sealed override Task MatchAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
}
- public sealed override Task MatchAsync(HttpContext httpContext)
+ var path = httpContext.Request.Path.Value;
+ if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var path = httpContext.Request.Path.Value;
- if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
- {
- httpContext.SetEndpoint(_endpoint);
- httpContext.Request.RouteValues = new RouteValueDictionary();
- }
-
- return Task.CompletedTask;
+ httpContext.SetEndpoint(_endpoint);
+ httpContext.Request.RouteValues = new RouteValueDictionary();
}
- // This is here so this can be tested alongside DFA matcher.
- internal Candidate[] FindCandidateSet(string path, ReadOnlySpan<PathSegment> segments)
- {
- if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
- {
- return _candidates;
- }
+ return Task.CompletedTask;
+ }
- return Array.Empty<Candidate>();
+ // This is here so this can be tested alongside DFA matcher.
+ internal Candidate[] FindCandidateSet(string path, ReadOnlySpan<PathSegment> segments)
+ {
+ if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
+ {
+ return _candidates;
}
+
+ return Array.Empty<Candidate>();
}
}
diff --git a/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcherBuilder.cs b/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcherBuilder.cs
index b8bac787a7..48bca301c0 100644
--- a/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcherBuilder.cs
+++ b/src/Http/Routing/perf/Microbenchmarks/Matching/TrivialMatcherBuilder.cs
@@ -4,20 +4,19 @@
using System.Collections.Generic;
using System.Linq;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class TrivialMatcherBuilder : MatcherBuilder
{
- internal class TrivialMatcherBuilder : MatcherBuilder
- {
- private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
+ private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
- public override void AddEndpoint(RouteEndpoint endpoint)
- {
- _endpoints.Add(endpoint);
- }
+ public override void AddEndpoint(RouteEndpoint endpoint)
+ {
+ _endpoints.Add(endpoint);
+ }
- public override Matcher Build()
- {
- return new TrivialMatcher(_endpoints.Last());
- }
+ public override Matcher Build()
+ {
+ return new TrivialMatcher(_endpoints.Last());
}
}
diff --git a/src/Http/Routing/src/ArrayBuilder.cs b/src/Http/Routing/src/ArrayBuilder.cs
index 21129fc800..225cefcf4a 100644
--- a/src/Http/Routing/src/ArrayBuilder.cs
+++ b/src/Http/Routing/src/ArrayBuilder.cs
@@ -10,160 +10,159 @@
using System;
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Helper type for avoiding allocations while building arrays.
+/// </summary>
+/// <typeparam name="T">The element type.</typeparam>
+internal struct ArrayBuilder<T>
{
+ private const int DefaultCapacity = 4;
+ private const int MaxCoreClrArrayLength = 0x7fefffff; // For byte arrays the limit is slightly larger
+
+ private T[] _array; // Starts out null, initialized on first Add.
+ private int _count; // Number of items into _array we're using.
+
/// <summary>
- /// Helper type for avoiding allocations while building arrays.
+ /// Initializes the <see cref="ArrayBuilder{T}"/> with a specified capacity.
/// </summary>
- /// <typeparam name="T">The element type.</typeparam>
- internal struct ArrayBuilder<T>
+ /// <param name="capacity">The capacity of the array to allocate.</param>
+ public ArrayBuilder(int capacity) : this()
{
- private const int DefaultCapacity = 4;
- private const int MaxCoreClrArrayLength = 0x7fefffff; // For byte arrays the limit is slightly larger
+ Debug.Assert(capacity >= 0);
+ if (capacity > 0)
+ {
+ _array = new T[capacity];
+ }
+ }
- private T[] _array; // Starts out null, initialized on first Add.
- private int _count; // Number of items into _array we're using.
+ /// <summary>
+ /// Gets the number of items this instance can store without re-allocating,
+ /// or 0 if the backing array is <c>null</c>.
+ /// </summary>
+ public int Capacity => _array?.Length ?? 0;
+
+ /// <summary>Gets the current underlying array.</summary>
+ public T[] Buffer => _array;
+
+ /// <summary>
+ /// Gets the number of items in the array currently in use.
+ /// </summary>
+ public int Count => _count;
- /// <summary>
- /// Initializes the <see cref="ArrayBuilder{T}"/> with a specified capacity.
- /// </summary>
- /// <param name="capacity">The capacity of the array to allocate.</param>
- public ArrayBuilder(int capacity) : this()
+ /// <summary>
+ /// Gets or sets the item at a certain index in the array.
+ /// </summary>
+ /// <param name="index">The index into the array.</param>
+ public T this[int index]
+ {
+ get
{
- Debug.Assert(capacity >= 0);
- if (capacity > 0)
- {
- _array = new T[capacity];
- }
+ Debug.Assert(index >= 0 && index < _count);
+ return _array[index];
}
+ }
- /// <summary>
- /// Gets the number of items this instance can store without re-allocating,
- /// or 0 if the backing array is <c>null</c>.
- /// </summary>
- public int Capacity => _array?.Length ?? 0;
-
- /// <summary>Gets the current underlying array.</summary>
- public T[] Buffer => _array;
-
- /// <summary>
- /// Gets the number of items in the array currently in use.
- /// </summary>
- public int Count => _count;
-
- /// <summary>
- /// Gets or sets the item at a certain index in the array.
- /// </summary>
- /// <param name="index">The index into the array.</param>
- public T this[int index]
+ /// <summary>
+ /// Adds an item to the backing array, resizing it if necessary.
+ /// </summary>
+ /// <param name="item">The item to add.</param>
+ public void Add(T item)
+ {
+ if (_count == Capacity)
{
- get
- {
- Debug.Assert(index >= 0 && index < _count);
- return _array[index];
- }
+ EnsureCapacity(_count + 1);
}
- /// <summary>
- /// Adds an item to the backing array, resizing it if necessary.
- /// </summary>
- /// <param name="item">The item to add.</param>
- public void Add(T item)
- {
- if (_count == Capacity)
- {
- EnsureCapacity(_count + 1);
- }
+ UncheckedAdd(item);
+ }
- UncheckedAdd(item);
- }
+ /// <summary>
+ /// Gets the first item in this builder.
+ /// </summary>
+ public T First()
+ {
+ Debug.Assert(_count > 0);
+ return _array[0];
+ }
- /// <summary>
- /// Gets the first item in this builder.
- /// </summary>
- public T First()
- {
- Debug.Assert(_count > 0);
- return _array[0];
- }
+ /// <summary>
+ /// Gets the last item in this builder.
+ /// </summary>
+ public T Last()
+ {
+ Debug.Assert(_count > 0);
+ return _array[_count - 1];
+ }
- /// <summary>
- /// Gets the last item in this builder.
- /// </summary>
- public T Last()
+ /// <summary>
+ /// Creates an array from the contents of this builder.
+ /// </summary>
+ /// <remarks>
+ /// Do not call this method twice on the same builder.
+ /// </remarks>
+ public T[] ToArray()
+ {
+ if (_count == 0)
{
- Debug.Assert(_count > 0);
- return _array[_count - 1];
+ return Array.Empty<T>();
}
- /// <summary>
- /// Creates an array from the contents of this builder.
- /// </summary>
- /// <remarks>
- /// Do not call this method twice on the same builder.
- /// </remarks>
- public T[] ToArray()
+ Debug.Assert(_array != null); // Nonzero _count should imply this
+
+ T[] result = _array;
+ if (_count < result.Length)
{
- if (_count == 0)
- {
- return Array.Empty<T>();
- }
-
- Debug.Assert(_array != null); // Nonzero _count should imply this
-
- T[] result = _array;
- if (_count < result.Length)
- {
- // Avoid a bit of overhead (method call, some branches, extra codegen)
- // which would be incurred by using Array.Resize
- result = new T[_count];
- Array.Copy(_array, 0, result, 0, _count);
- }
+ // Avoid a bit of overhead (method call, some branches, extra codegen)
+ // which would be incurred by using Array.Resize
+ result = new T[_count];
+ Array.Copy(_array, 0, result, 0, _count);
+ }
#if DEBUG
- // Try to prevent callers from using the ArrayBuilder after ToArray, if _count != 0.
- _count = -1;
- _array = null;
+ // Try to prevent callers from using the ArrayBuilder after ToArray, if _count != 0.
+ _count = -1;
+ _array = null;
#endif
- return result;
- }
+ return result;
+ }
- /// <summary>
- /// Adds an item to the backing array, without checking if there is room.
- /// </summary>
- /// <param name="item">The item to add.</param>
- /// <remarks>
- /// Use this method if you know there is enough space in the <see cref="ArrayBuilder{T}"/>
- /// for another item, and you are writing performance-sensitive code.
- /// </remarks>
- public void UncheckedAdd(T item)
- {
- Debug.Assert(_count < Capacity);
+ /// <summary>
+ /// Adds an item to the backing array, without checking if there is room.
+ /// </summary>
+ /// <param name="item">The item to add.</param>
+ /// <remarks>
+ /// Use this method if you know there is enough space in the <see cref="ArrayBuilder{T}"/>
+ /// for another item, and you are writing performance-sensitive code.
+ /// </remarks>
+ public void UncheckedAdd(T item)
+ {
+ Debug.Assert(_count < Capacity);
- _array[_count++] = item;
- }
+ _array[_count++] = item;
+ }
- private void EnsureCapacity(int minimum)
- {
- Debug.Assert(minimum > Capacity);
+ private void EnsureCapacity(int minimum)
+ {
+ Debug.Assert(minimum > Capacity);
- int capacity = Capacity;
- int nextCapacity = capacity == 0 ? DefaultCapacity : 2 * capacity;
+ int capacity = Capacity;
+ int nextCapacity = capacity == 0 ? DefaultCapacity : 2 * capacity;
- if ((uint)nextCapacity > (uint)MaxCoreClrArrayLength)
- {
- nextCapacity = Math.Max(capacity + 1, MaxCoreClrArrayLength);
- }
+ if ((uint)nextCapacity > (uint)MaxCoreClrArrayLength)
+ {
+ nextCapacity = Math.Max(capacity + 1, MaxCoreClrArrayLength);
+ }
- nextCapacity = Math.Max(nextCapacity, minimum);
+ nextCapacity = Math.Max(nextCapacity, minimum);
- T[] next = new T[nextCapacity];
- if (_count > 0)
- {
- Array.Copy(_array, 0, next, 0, _count);
- }
- _array = next;
+ T[] next = new T[nextCapacity];
+ if (_count > 0)
+ {
+ Array.Copy(_array, 0, next, 0, _count);
}
+ _array = next;
}
}
diff --git a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs
index e7f92e4354..e99250d89c 100644
--- a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs
@@ -11,535 +11,534 @@ using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add endpoints.
+/// </summary>
+public static class EndpointRouteBuilderExtensions
{
+ // Avoid creating a new array every call
+ private static readonly string[] GetVerb = new[] { "GET" };
+ private static readonly string[] PostVerb = new[] { "POST" };
+ private static readonly string[] PutVerb = new[] { "PUT" };
+ private static readonly string[] DeleteVerb = new[] { "DELETE" };
+ private static readonly string[] PatchVerb = new[] { "PATCH" };
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder MapGet(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ return MapMethods(endpoints, pattern, GetVerb, requestDelegate);
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder MapPost(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ return MapMethods(endpoints, pattern, PostVerb, requestDelegate);
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder MapPut(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ return MapMethods(endpoints, pattern, PutVerb, requestDelegate);
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder MapDelete(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ return MapMethods(endpoints, pattern, DeleteVerb, requestDelegate);
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder MapPatch(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ return MapMethods(endpoints, pattern, PatchVerb, requestDelegate);
+ }
+
/// <summary>
- /// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add endpoints.
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
+ /// for the specified HTTP methods and pattern.
/// </summary>
- public static class EndpointRouteBuilderExtensions
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder MapMethods(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ IEnumerable<string> httpMethods,
+ RequestDelegate requestDelegate)
{
- // Avoid creating a new array every call
- private static readonly string[] GetVerb = new[] { "GET" };
- private static readonly string[] PostVerb = new[] { "POST" };
- private static readonly string[] PutVerb = new[] { "PUT" };
- private static readonly string[] DeleteVerb = new[] { "DELETE" };
- private static readonly string[] PatchVerb = new[] { "PATCH" };
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder MapGet(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ if (httpMethods == null)
{
- return MapMethods(endpoints, pattern, GetVerb, requestDelegate);
+ throw new ArgumentNullException(nameof(httpMethods));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder MapPost(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), requestDelegate);
+ builder.WithDisplayName($"{pattern} HTTP: {string.Join(", ", httpMethods)}");
+ builder.WithMetadata(new HttpMethodMetadata(httpMethods));
+ return builder;
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder Map(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ return Map(endpoints, RoutePatternFactory.Parse(pattern), requestDelegate);
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static IEndpointConventionBuilder Map(
+ this IEndpointRouteBuilder endpoints,
+ RoutePattern pattern,
+ RequestDelegate requestDelegate)
+ {
+ if (endpoints == null)
{
- return MapMethods(endpoints, pattern, PostVerb, requestDelegate);
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder MapPut(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ if (pattern == null)
{
- return MapMethods(endpoints, pattern, PutVerb, requestDelegate);
+ throw new ArgumentNullException(nameof(pattern));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder MapDelete(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ if (requestDelegate == null)
{
- return MapMethods(endpoints, pattern, DeleteVerb, requestDelegate);
+ throw new ArgumentNullException(nameof(requestDelegate));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder MapPatch(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ const int defaultOrder = 0;
+
+ var builder = new RouteEndpointBuilder(
+ requestDelegate,
+ pattern,
+ defaultOrder)
{
- return MapMethods(endpoints, pattern, PatchVerb, requestDelegate);
- }
+ DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
+ };
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
- /// for the specified HTTP methods and pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder MapMethods(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- IEnumerable<string> httpMethods,
- RequestDelegate requestDelegate)
+ // Add delegate attributes as metadata
+ var attributes = requestDelegate.Method.GetCustomAttributes();
+
+ // This can be null if the delegate is a dynamic method or compiled from an expression tree
+ if (attributes != null)
{
- if (httpMethods == null)
+ foreach (var attribute in attributes)
{
- throw new ArgumentNullException(nameof(httpMethods));
+ builder.Metadata.Add(attribute);
}
-
- var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), requestDelegate);
- builder.WithDisplayName($"{pattern} HTTP: {string.Join(", ", httpMethods)}");
- builder.WithMetadata(new HttpMethodMetadata(httpMethods));
- return builder;
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder Map(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
+ if (dataSource == null)
{
- return Map(endpoints, RoutePatternFactory.Parse(pattern), requestDelegate);
+ dataSource = new ModelEndpointDataSource();
+ endpoints.DataSources.Add(dataSource);
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- public static IEndpointConventionBuilder Map(
- this IEndpointRouteBuilder endpoints,
- RoutePattern pattern,
- RequestDelegate requestDelegate)
- {
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
-
- if (requestDelegate == null)
- {
- throw new ArgumentNullException(nameof(requestDelegate));
- }
+ return dataSource.AddEndpointBuilder(builder);
+ }
- const int defaultOrder = 0;
- var builder = new RouteEndpointBuilder(
- requestDelegate,
- pattern,
- defaultOrder)
- {
- DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
- };
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder MapGet(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ return MapMethods(endpoints, pattern, GetVerb, handler);
+ }
- // Add delegate attributes as metadata
- var attributes = requestDelegate.Method.GetCustomAttributes();
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder MapPost(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ return MapMethods(endpoints, pattern, PostVerb, handler);
+ }
- // This can be null if the delegate is a dynamic method or compiled from an expression tree
- if (attributes != null)
- {
- foreach (var attribute in attributes)
- {
- builder.Metadata.Add(attribute);
- }
- }
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder MapPut(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ return MapMethods(endpoints, pattern, PutVerb, handler);
+ }
- var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
- if (dataSource == null)
- {
- dataSource = new ModelEndpointDataSource();
- endpoints.DataSources.Add(dataSource);
- }
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder MapDelete(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ return MapMethods(endpoints, pattern, DeleteVerb, handler);
+ }
- return dataSource.AddEndpointBuilder(builder);
- }
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The <see cref="Delegate" /> executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder MapPatch(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ return MapMethods(endpoints, pattern, PatchVerb, handler);
+ }
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP GET requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder MapGet(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
+ /// for the specified HTTP methods and pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder MapMethods(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ IEnumerable<string> httpMethods,
+ Delegate handler)
+ {
+ if (httpMethods is null)
{
- return MapMethods(endpoints, pattern, GetVerb, handler);
+ throw new ArgumentNullException(nameof(httpMethods));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP POST requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder MapPost(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
+ var disableInferredBody = false;
+ foreach (var method in httpMethods)
{
- return MapMethods(endpoints, pattern, PostVerb, handler);
+ disableInferredBody = ShouldDisableInferredBody(method);
+ if (disableInferredBody is true)
+ {
+ break;
+ }
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PUT requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder MapPut(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
- {
- return MapMethods(endpoints, pattern, PutVerb, handler);
- }
+ var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), handler, disableInferredBody);
+ // Prepends the HTTP method to the DisplayName produced with pattern + method name
+ builder.Add(b => b.DisplayName = $"HTTP: {string.Join(", ", httpMethods)} {b.DisplayName}");
+ builder.WithMetadata(new HttpMethodMetadata(httpMethods));
+ return builder;
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP DELETE requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder MapDelete(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
+ static bool ShouldDisableInferredBody(string method)
{
- return MapMethods(endpoints, pattern, DeleteVerb, handler);
+ // GET, DELETE, HEAD, CONNECT, TRACE, and OPTIONS normally do not contain bodies
+ return method.Equals(HttpMethods.Get, StringComparison.Ordinal) ||
+ method.Equals(HttpMethods.Delete, StringComparison.Ordinal) ||
+ method.Equals(HttpMethods.Head, StringComparison.Ordinal) ||
+ method.Equals(HttpMethods.Options, StringComparison.Ordinal) ||
+ method.Equals(HttpMethods.Trace, StringComparison.Ordinal) ||
+ method.Equals(HttpMethods.Connect, StringComparison.Ordinal);
}
+ }
+
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder Map(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ return Map(endpoints, RoutePatternFactory.Parse(pattern), handler);
+ }
+ /// <summary>
+ /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
+ /// for the specified pattern.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder Map(
+ this IEndpointRouteBuilder endpoints,
+ RoutePattern pattern,
+ Delegate handler)
+ {
+ return Map(endpoints, pattern, handler, disableInferBodyFromParameters: false);
+ }
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP PATCH requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The <see cref="Delegate" /> executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder MapPatch(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
+ /// <summary>
+ /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
+ /// requests for non-file-names with the lowest possible priority.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ /// <remarks>
+ /// <para>
+ /// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> is intended to handle cases where URL path of
+ /// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing
+ /// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to
+ /// result in an HTTP 404.
+ /// </para>
+ /// <para>
+ /// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> registers an endpoint using the pattern
+ /// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.
+ /// </para>
+ /// </remarks>
+ public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate handler)
+ {
+ if (endpoints == null)
{
- return MapMethods(endpoints, pattern, PatchVerb, handler);
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
- /// for the specified HTTP methods and pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <param name="httpMethods">HTTP methods that the endpoint will match.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder MapMethods(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- IEnumerable<string> httpMethods,
- Delegate handler)
+ if (handler == null)
{
- if (httpMethods is null)
- {
- throw new ArgumentNullException(nameof(httpMethods));
- }
-
- var disableInferredBody = false;
- foreach (var method in httpMethods)
- {
- disableInferredBody = ShouldDisableInferredBody(method);
- if (disableInferredBody is true)
- {
- break;
- }
- }
-
- var builder = endpoints.Map(RoutePatternFactory.Parse(pattern), handler, disableInferredBody);
- // Prepends the HTTP method to the DisplayName produced with pattern + method name
- builder.Add(b => b.DisplayName = $"HTTP: {string.Join(", ", httpMethods)} {b.DisplayName}");
- builder.WithMetadata(new HttpMethodMetadata(httpMethods));
- return builder;
-
- static bool ShouldDisableInferredBody(string method)
- {
- // GET, DELETE, HEAD, CONNECT, TRACE, and OPTIONS normally do not contain bodies
- return method.Equals(HttpMethods.Get, StringComparison.Ordinal) ||
- method.Equals(HttpMethods.Delete, StringComparison.Ordinal) ||
- method.Equals(HttpMethods.Head, StringComparison.Ordinal) ||
- method.Equals(HttpMethods.Options, StringComparison.Ordinal) ||
- method.Equals(HttpMethods.Trace, StringComparison.Ordinal) ||
- method.Equals(HttpMethods.Connect, StringComparison.Ordinal);
- }
+ throw new ArgumentNullException(nameof(handler));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder Map(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
+ return endpoints.MapFallback("{*path:nonfile}", handler);
+ }
+
+ /// <summary>
+ /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
+ /// the provided pattern with the lowest possible priority.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="handler">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ /// <remarks>
+ /// <para>
+ /// <see cref="MapFallback(IEndpointRouteBuilder, string, Delegate)"/> is intended to handle cases where no
+ /// other endpoint has matched. This is convenient for routing requests to a SPA framework.
+ /// </para>
+ /// <para>
+ /// The order of the registered endpoint will be <c>int.MaxValue</c>.
+ /// </para>
+ /// <para>
+ /// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint
+ /// to exclude requests for static files.
+ /// </para>
+ /// </remarks>
+ public static RouteHandlerBuilder MapFallback(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ Delegate handler)
+ {
+ if (endpoints == null)
{
- return Map(endpoints, RoutePatternFactory.Parse(pattern), handler);
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Adds a <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that matches HTTP requests
- /// for the specified pattern.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder Map(
- this IEndpointRouteBuilder endpoints,
- RoutePattern pattern,
- Delegate handler)
+ if (pattern == null)
{
- return Map(endpoints, pattern, handler, disableInferBodyFromParameters: false);
+ throw new ArgumentNullException(nameof(pattern));
}
- /// <summary>
- /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
- /// requests for non-file-names with the lowest possible priority.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- /// <remarks>
- /// <para>
- /// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> is intended to handle cases where URL path of
- /// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing
- /// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to
- /// result in an HTTP 404.
- /// </para>
- /// <para>
- /// <see cref="MapFallback(IEndpointRouteBuilder, Delegate)"/> registers an endpoint using the pattern
- /// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.
- /// </para>
- /// </remarks>
- public static RouteHandlerBuilder MapFallback(this IEndpointRouteBuilder endpoints, Delegate handler)
+ if (handler == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
+ throw new ArgumentNullException(nameof(handler));
+ }
- if (handler == null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
+ var conventionBuilder = endpoints.Map(pattern, handler);
+ conventionBuilder.WithDisplayName("Fallback " + pattern);
+ conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);
+ return conventionBuilder;
+ }
- return endpoints.MapFallback("{*path:nonfile}", handler);
+ private static RouteHandlerBuilder Map(
+ this IEndpointRouteBuilder endpoints,
+ RoutePattern pattern,
+ Delegate handler,
+ bool disableInferBodyFromParameters)
+ {
+ if (endpoints is null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
- /// the provided pattern with the lowest possible priority.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="handler">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- /// <remarks>
- /// <para>
- /// <see cref="MapFallback(IEndpointRouteBuilder, string, Delegate)"/> is intended to handle cases where no
- /// other endpoint has matched. This is convenient for routing requests to a SPA framework.
- /// </para>
- /// <para>
- /// The order of the registered endpoint will be <c>int.MaxValue</c>.
- /// </para>
- /// <para>
- /// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint
- /// to exclude requests for static files.
- /// </para>
- /// </remarks>
- public static RouteHandlerBuilder MapFallback(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- Delegate handler)
+ if (pattern is null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
-
- if (handler == null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
-
- var conventionBuilder = endpoints.Map(pattern, handler);
- conventionBuilder.WithDisplayName("Fallback " + pattern);
- conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);
- return conventionBuilder;
+ throw new ArgumentNullException(nameof(pattern));
}
- private static RouteHandlerBuilder Map(
- this IEndpointRouteBuilder endpoints,
- RoutePattern pattern,
- Delegate handler,
- bool disableInferBodyFromParameters)
+ if (handler is null)
{
- if (endpoints is null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- if (pattern is null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
-
- if (handler is null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
+ throw new ArgumentNullException(nameof(handler));
+ }
- const int defaultOrder = 0;
+ const int defaultOrder = 0;
- var routeParams = new List<string>(pattern.Parameters.Count);
- foreach (var part in pattern.Parameters)
- {
- routeParams.Add(part.Name);
- }
-
- var routeHandlerOptions = endpoints.ServiceProvider?.GetService<IOptions<RouteHandlerOptions>>();
+ var routeParams = new List<string>(pattern.Parameters.Count);
+ foreach (var part in pattern.Parameters)
+ {
+ routeParams.Add(part.Name);
+ }
- var options = new RequestDelegateFactoryOptions
- {
- ServiceProvider = endpoints.ServiceProvider,
- RouteParameterNames = routeParams,
- ThrowOnBadRequest = routeHandlerOptions?.Value.ThrowOnBadRequest ?? false,
- DisableInferBodyFromParameters = disableInferBodyFromParameters,
- };
-
- var requestDelegateResult = RequestDelegateFactory.Create(handler, options);
-
- var builder = new RouteEndpointBuilder(
- requestDelegateResult.RequestDelegate,
- pattern,
- defaultOrder)
- {
- DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
- };
+ var routeHandlerOptions = endpoints.ServiceProvider?.GetService<IOptions<RouteHandlerOptions>>();
- // REVIEW: Should we add an IActionMethodMetadata with just MethodInfo on it so we are
- // explicit about the MethodInfo representing the "handler" and not the RequestDelegate?
+ var options = new RequestDelegateFactoryOptions
+ {
+ ServiceProvider = endpoints.ServiceProvider,
+ RouteParameterNames = routeParams,
+ ThrowOnBadRequest = routeHandlerOptions?.Value.ThrowOnBadRequest ?? false,
+ DisableInferBodyFromParameters = disableInferBodyFromParameters,
+ };
+
+ var requestDelegateResult = RequestDelegateFactory.Create(handler, options);
+
+ var builder = new RouteEndpointBuilder(
+ requestDelegateResult.RequestDelegate,
+ pattern,
+ defaultOrder)
+ {
+ DisplayName = pattern.RawText ?? pattern.DebuggerToString(),
+ };
- // Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint.
- builder.Metadata.Add(handler.Method);
+ // REVIEW: Should we add an IActionMethodMetadata with just MethodInfo on it so we are
+ // explicit about the MethodInfo representing the "handler" and not the RequestDelegate?
- // Methods defined in a top-level program are generated as statics so the delegate
- // target will be null. Inline lambdas are compiler generated method so they can
- // be filtered that way.
- if (GeneratedNameParser.TryParseLocalFunctionName(handler.Method.Name, out var endpointName)
- || !TypeHelper.IsCompilerGeneratedMethod(handler.Method))
- {
- endpointName ??= handler.Method.Name;
- builder.DisplayName = $"{builder.DisplayName} => {endpointName}";
- }
+ // Add MethodInfo as metadata to assist with OpenAPI generation for the endpoint.
+ builder.Metadata.Add(handler.Method);
- // Add delegate attributes as metadata
- var attributes = handler.Method.GetCustomAttributes();
+ // Methods defined in a top-level program are generated as statics so the delegate
+ // target will be null. Inline lambdas are compiler generated method so they can
+ // be filtered that way.
+ if (GeneratedNameParser.TryParseLocalFunctionName(handler.Method.Name, out var endpointName)
+ || !TypeHelper.IsCompilerGeneratedMethod(handler.Method))
+ {
+ endpointName ??= handler.Method.Name;
+ builder.DisplayName = $"{builder.DisplayName} => {endpointName}";
+ }
- // Add add request delegate metadata
- foreach (var metadata in requestDelegateResult.EndpointMetadata)
- {
- builder.Metadata.Add(metadata);
- }
+ // Add delegate attributes as metadata
+ var attributes = handler.Method.GetCustomAttributes();
- // This can be null if the delegate is a dynamic method or compiled from an expression tree
- if (attributes is not null)
- {
- foreach (var attribute in attributes)
- {
- builder.Metadata.Add(attribute);
- }
- }
+ // Add add request delegate metadata
+ foreach (var metadata in requestDelegateResult.EndpointMetadata)
+ {
+ builder.Metadata.Add(metadata);
+ }
- var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
- if (dataSource is null)
+ // This can be null if the delegate is a dynamic method or compiled from an expression tree
+ if (attributes is not null)
+ {
+ foreach (var attribute in attributes)
{
- dataSource = new ModelEndpointDataSource();
- endpoints.DataSources.Add(dataSource);
+ builder.Metadata.Add(attribute);
}
+ }
- return new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder));
+ var dataSource = endpoints.DataSources.OfType<ModelEndpointDataSource>().FirstOrDefault();
+ if (dataSource is null)
+ {
+ dataSource = new ModelEndpointDataSource();
+ endpoints.DataSources.Add(dataSource);
}
+
+ return new RouteHandlerBuilder(dataSource.AddEndpointBuilder(builder));
}
}
diff --git a/src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs b/src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs
index 8bde422846..15db1a6068 100644
--- a/src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/EndpointRoutingApplicationBuilderExtensions.cs
@@ -7,155 +7,154 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Constains extensions for configuring routing on an <see cref="IApplicationBuilder"/>.
+/// </summary>
+public static class EndpointRoutingApplicationBuilderExtensions
{
+ private const string EndpointRouteBuilder = "__EndpointRouteBuilder";
+ private const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";
+
/// <summary>
- /// Constains extensions for configuring routing on an <see cref="IApplicationBuilder"/>.
+ /// Adds a <see cref="EndpointRoutingMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>.
/// </summary>
- public static class EndpointRoutingApplicationBuilderExtensions
+ /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ /// <remarks>
+ /// <para>
+ /// A call to <see cref="UseRouting(IApplicationBuilder)"/> must be followed by a call to
+ /// <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> for the same <see cref="IApplicationBuilder"/>
+ /// instance.
+ /// </para>
+ /// <para>
+ /// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are
+ /// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>
+ /// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between
+ /// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the
+ /// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
+ /// </para>
+ /// </remarks>
+ public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
{
- private const string EndpointRouteBuilder = "__EndpointRouteBuilder";
- private const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";
-
- /// <summary>
- /// Adds a <see cref="EndpointRoutingMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- /// <remarks>
- /// <para>
- /// A call to <see cref="UseRouting(IApplicationBuilder)"/> must be followed by a call to
- /// <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> for the same <see cref="IApplicationBuilder"/>
- /// instance.
- /// </para>
- /// <para>
- /// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are
- /// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>
- /// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between
- /// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the
- /// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
- /// </para>
- /// </remarks>
- public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
+ if (builder == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
+ throw new ArgumentNullException(nameof(builder));
+ }
- VerifyRoutingServicesAreRegistered(builder);
+ VerifyRoutingServicesAreRegistered(builder);
- IEndpointRouteBuilder endpointRouteBuilder;
- if (builder.Properties.TryGetValue(GlobalEndpointRouteBuilderKey, out var obj))
- {
- endpointRouteBuilder = (IEndpointRouteBuilder)obj!;
- // Let interested parties know if UseRouting() was called while a global route builder was set
- builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
- }
- else
- {
- endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);
- builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
- }
-
- return builder.UseMiddleware<EndpointRoutingMiddleware>(endpointRouteBuilder);
+ IEndpointRouteBuilder endpointRouteBuilder;
+ if (builder.Properties.TryGetValue(GlobalEndpointRouteBuilderKey, out var obj))
+ {
+ endpointRouteBuilder = (IEndpointRouteBuilder)obj!;
+ // Let interested parties know if UseRouting() was called while a global route builder was set
+ builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
+ }
+ else
+ {
+ endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);
+ builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder;
}
- /// <summary>
- /// Adds a <see cref="EndpointMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>
- /// with the <see cref="EndpointDataSource"/> instances built from configured <see cref="IEndpointRouteBuilder"/>.
- /// The <see cref="EndpointMiddleware"/> will execute the <see cref="Endpoint"/> associated with the current
- /// request.
- /// </summary>
- /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
- /// <param name="configure">An <see cref="Action{IEndpointRouteBuilder}"/> to configure the provided <see cref="IEndpointRouteBuilder"/>.</param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- /// <remarks>
- /// <para>
- /// A call to <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> must be preceded by a call to
- /// <see cref="UseRouting(IApplicationBuilder)"/> for the same <see cref="IApplicationBuilder"/>
- /// instance.
- /// </para>
- /// <para>
- /// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are
- /// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>
- /// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between
- /// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the
- /// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
- /// </para>
- /// </remarks>
- public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
+ return builder.UseMiddleware<EndpointRoutingMiddleware>(endpointRouteBuilder);
+ }
+
+ /// <summary>
+ /// Adds a <see cref="EndpointMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>
+ /// with the <see cref="EndpointDataSource"/> instances built from configured <see cref="IEndpointRouteBuilder"/>.
+ /// The <see cref="EndpointMiddleware"/> will execute the <see cref="Endpoint"/> associated with the current
+ /// request.
+ /// </summary>
+ /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
+ /// <param name="configure">An <see cref="Action{IEndpointRouteBuilder}"/> to configure the provided <see cref="IEndpointRouteBuilder"/>.</param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ /// <remarks>
+ /// <para>
+ /// A call to <see cref="UseEndpoints(IApplicationBuilder, Action{IEndpointRouteBuilder})"/> must be preceded by a call to
+ /// <see cref="UseRouting(IApplicationBuilder)"/> for the same <see cref="IApplicationBuilder"/>
+ /// instance.
+ /// </para>
+ /// <para>
+ /// The <see cref="EndpointRoutingMiddleware"/> defines a point in the middleware pipeline where routing decisions are
+ /// made, and an <see cref="Endpoint"/> is associated with the <see cref="HttpContext"/>. The <see cref="EndpointMiddleware"/>
+ /// defines a point in the middleware pipeline where the current <see cref="Endpoint"/> is executed. Middleware between
+ /// the <see cref="EndpointRoutingMiddleware"/> and <see cref="EndpointMiddleware"/> may observe or change the
+ /// <see cref="Endpoint"/> associated with the <see cref="HttpContext"/>.
+ /// </para>
+ /// </remarks>
+ public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure)
+ {
+ if (builder == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
+ throw new ArgumentNullException(nameof(builder));
+ }
- if (configure == null)
- {
- throw new ArgumentNullException(nameof(configure));
- }
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
- VerifyRoutingServicesAreRegistered(builder);
+ VerifyRoutingServicesAreRegistered(builder);
- VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder);
+ VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder);
- configure(endpointRouteBuilder);
+ configure(endpointRouteBuilder);
- // Yes, this mutates an IOptions. We're registering data sources in a global collection which
- // can be used for discovery of endpoints or URL generation.
- //
- // Each middleware gets its own collection of data sources, and all of those data sources also
- // get added to a global collection.
- var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
- foreach (var dataSource in endpointRouteBuilder.DataSources)
+ // Yes, this mutates an IOptions. We're registering data sources in a global collection which
+ // can be used for discovery of endpoints or URL generation.
+ //
+ // Each middleware gets its own collection of data sources, and all of those data sources also
+ // get added to a global collection.
+ var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
+ foreach (var dataSource in endpointRouteBuilder.DataSources)
+ {
+ if (!routeOptions.Value.EndpointDataSources.Contains(dataSource))
{
- if (!routeOptions.Value.EndpointDataSources.Contains(dataSource))
- {
- routeOptions.Value.EndpointDataSources.Add(dataSource);
- }
+ routeOptions.Value.EndpointDataSources.Add(dataSource);
}
-
- return builder.UseMiddleware<EndpointMiddleware>();
}
- private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
+ return builder.UseMiddleware<EndpointMiddleware>();
+ }
+
+ private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
+ {
+ // Verify if AddRouting was done before calling UseEndpointRouting/UseEndpoint
+ // We use the RoutingMarkerService to make sure if all the services were added.
+ if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
{
- // Verify if AddRouting was done before calling UseEndpointRouting/UseEndpoint
- // We use the RoutingMarkerService to make sure if all the services were added.
- if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
- {
- throw new InvalidOperationException(Resources.FormatUnableToFindServices(
- nameof(IServiceCollection),
- nameof(RoutingServiceCollectionExtensions.AddRouting),
- "ConfigureServices(...)"));
- }
+ throw new InvalidOperationException(Resources.FormatUnableToFindServices(
+ nameof(IServiceCollection),
+ nameof(RoutingServiceCollectionExtensions.AddRouting),
+ "ConfigureServices(...)"));
}
+ }
- private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out IEndpointRouteBuilder endpointRouteBuilder)
+ private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out IEndpointRouteBuilder endpointRouteBuilder)
+ {
+ if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj))
{
- if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj))
- {
- var message =
- $"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " +
- $"execution pipeline before {nameof(EndpointMiddleware)}. " +
- $"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " +
- $"to 'Configure(...)' in the application startup code.";
- throw new InvalidOperationException(message);
- }
+ var message =
+ $"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " +
+ $"execution pipeline before {nameof(EndpointMiddleware)}. " +
+ $"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " +
+ $"to 'Configure(...)' in the application startup code.";
+ throw new InvalidOperationException(message);
+ }
- endpointRouteBuilder = (IEndpointRouteBuilder)obj!;
+ endpointRouteBuilder = (IEndpointRouteBuilder)obj!;
- // This check handles the case where Map or something else that forks the pipeline is called between the two
- // routing middleware.
- if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultRouteBuilder && !object.ReferenceEquals(app, defaultRouteBuilder.ApplicationBuilder))
- {
- var message =
- $"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " +
- $"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " +
- $"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline.";
- throw new InvalidOperationException(message);
- }
+ // This check handles the case where Map or something else that forks the pipeline is called between the two
+ // routing middleware.
+ if (endpointRouteBuilder is DefaultEndpointRouteBuilder defaultRouteBuilder && !object.ReferenceEquals(app, defaultRouteBuilder.ApplicationBuilder))
+ {
+ var message =
+ $"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " +
+ $"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " +
+ $"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline.";
+ throw new InvalidOperationException(message);
}
}
}
diff --git a/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs
index ed146c36cb..8367d9a78d 100644
--- a/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/FallbackEndpointRouteBuilderExtensions.cs
@@ -5,97 +5,96 @@ using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Contains extension methods for <see cref="IEndpointRouteBuilder"/>.
+/// </summary>
+public static class FallbackEndpointRouteBuilderExtensions
{
/// <summary>
- /// Contains extension methods for <see cref="IEndpointRouteBuilder"/>.
+ /// The default route pattern used by fallback routing. <c>{*path:nonfile}</c>
/// </summary>
- public static class FallbackEndpointRouteBuilderExtensions
+ public static readonly string DefaultPattern = "{*path:nonfile}";
+
+ /// <summary>
+ /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
+ /// requests for non-file-names with the lowest possible priority.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ /// <remarks>
+ /// <para>
+ /// <see cref="MapFallback(IEndpointRouteBuilder, RequestDelegate)"/> is intended to handle cases where URL path of
+ /// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing
+ /// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to
+ /// result in an HTTP 404.
+ /// </para>
+ /// <para>
+ /// <see cref="MapFallback(IEndpointRouteBuilder, RequestDelegate)"/> registers an endpoint using the pattern
+ /// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.
+ /// </para>
+ /// </remarks>
+ public static IEndpointConventionBuilder MapFallback(this IEndpointRouteBuilder endpoints, RequestDelegate requestDelegate)
{
- /// <summary>
- /// The default route pattern used by fallback routing. <c>{*path:nonfile}</c>
- /// </summary>
- public static readonly string DefaultPattern = "{*path:nonfile}";
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- /// <summary>
- /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
- /// requests for non-file-names with the lowest possible priority.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- /// <remarks>
- /// <para>
- /// <see cref="MapFallback(IEndpointRouteBuilder, RequestDelegate)"/> is intended to handle cases where URL path of
- /// the request does not contain a file name, and no other endpoint has matched. This is convenient for routing
- /// requests for dynamic content to a SPA framework, while also allowing requests for non-existent files to
- /// result in an HTTP 404.
- /// </para>
- /// <para>
- /// <see cref="MapFallback(IEndpointRouteBuilder, RequestDelegate)"/> registers an endpoint using the pattern
- /// <c>{*path:nonfile}</c>. The order of the registered endpoint will be <c>int.MaxValue</c>.
- /// </para>
- /// </remarks>
- public static IEndpointConventionBuilder MapFallback(this IEndpointRouteBuilder endpoints, RequestDelegate requestDelegate)
+ if (requestDelegate == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
+ throw new ArgumentNullException(nameof(requestDelegate));
+ }
- if (requestDelegate == null)
- {
- throw new ArgumentNullException(nameof(requestDelegate));
- }
+ return endpoints.MapFallback("{*path:nonfile}", requestDelegate);
+ }
- return endpoints.MapFallback("{*path:nonfile}", requestDelegate);
+ /// <summary>
+ /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
+ /// the provided pattern with the lowest possible priority.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
+ /// <param name="pattern">The route pattern.</param>
+ /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
+ /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
+ /// <remarks>
+ /// <para>
+ /// <see cref="MapFallback(IEndpointRouteBuilder, string, RequestDelegate)"/> is intended to handle cases where no
+ /// other endpoint has matched. This is convenient for routing requests to a SPA framework.
+ /// </para>
+ /// <para>
+ /// The order of the registered endpoint will be <c>int.MaxValue</c>.
+ /// </para>
+ /// <para>
+ /// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint
+ /// to exclude requests for static files.
+ /// </para>
+ /// </remarks>
+ public static IEndpointConventionBuilder MapFallback(
+ this IEndpointRouteBuilder endpoints,
+ string pattern,
+ RequestDelegate requestDelegate)
+ {
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
- /// the provided pattern with the lowest possible priority.
- /// </summary>
- /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
- /// <param name="pattern">The route pattern.</param>
- /// <param name="requestDelegate">The delegate executed when the endpoint is matched.</param>
- /// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
- /// <remarks>
- /// <para>
- /// <see cref="MapFallback(IEndpointRouteBuilder, string, RequestDelegate)"/> is intended to handle cases where no
- /// other endpoint has matched. This is convenient for routing requests to a SPA framework.
- /// </para>
- /// <para>
- /// The order of the registered endpoint will be <c>int.MaxValue</c>.
- /// </para>
- /// <para>
- /// This overload will use the provided <paramref name="pattern"/> verbatim. Use the <c>:nonfile</c> route constraint
- /// to exclude requests for static files.
- /// </para>
- /// </remarks>
- public static IEndpointConventionBuilder MapFallback(
- this IEndpointRouteBuilder endpoints,
- string pattern,
- RequestDelegate requestDelegate)
+ if (pattern == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
-
- if (requestDelegate == null)
- {
- throw new ArgumentNullException(nameof(requestDelegate));
- }
+ throw new ArgumentNullException(nameof(pattern));
+ }
- var conventionBuilder = endpoints.Map(pattern, requestDelegate);
- conventionBuilder.WithDisplayName("Fallback " + pattern);
- conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);
- return conventionBuilder;
+ if (requestDelegate == null)
+ {
+ throw new ArgumentNullException(nameof(requestDelegate));
}
+
+ var conventionBuilder = endpoints.Map(pattern, requestDelegate);
+ conventionBuilder.WithDisplayName("Fallback " + pattern);
+ conventionBuilder.Add(b => ((RouteEndpointBuilder)b).Order = int.MaxValue);
+ return conventionBuilder;
}
}
diff --git a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs
index 34a9d897ad..46daeb65f7 100644
--- a/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/OpenApiRouteHandlerBuilderExtensions.cs
@@ -6,221 +6,220 @@ using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+/// <summary>
+/// Extension methods for adding <see cref="Endpoint.Metadata"/> that is
+/// meant to be consumed by OpenAPI libraries.
+/// </summary>
+public static class OpenApiRouteHandlerBuilderExtensions
{
+ private static readonly ExcludeFromDescriptionAttribute _excludeFromDescriptionMetadataAttribute = new();
+
/// <summary>
- /// Extension methods for adding <see cref="Endpoint.Metadata"/> that is
- /// meant to be consumed by OpenAPI libraries.
+ /// Adds the <see cref="IExcludeFromDescriptionMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
/// </summary>
- public static class OpenApiRouteHandlerBuilderExtensions
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder ExcludeFromDescription(this RouteHandlerBuilder builder)
{
- private static readonly ExcludeFromDescriptionAttribute _excludeFromDescriptionMetadataAttribute = new();
-
- /// <summary>
- /// Adds the <see cref="IExcludeFromDescriptionMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder ExcludeFromDescription(this RouteHandlerBuilder builder)
- {
- builder.WithMetadata(_excludeFromDescriptionMetadataAttribute);
+ builder.WithMetadata(_excludeFromDescriptionMetadataAttribute);
- return builder;
- }
+ return builder;
+ }
- /// <summary>
- /// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <typeparam name="TResponse">The type of the response.</typeparam>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status200OK"/>.</param>
- /// <param name="contentType">The response content type. Defaults to "application/json".</param>
- /// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ /// <summary>
+ /// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <typeparam name="TResponse">The type of the response.</typeparam>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status200OK"/>.</param>
+ /// <param name="contentType">The response content type. Defaults to "application/json".</param>
+ /// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
#pragma warning disable RS0026
- public static RouteHandlerBuilder Produces<TResponse>(this RouteHandlerBuilder builder,
+ public static RouteHandlerBuilder Produces<TResponse>(this RouteHandlerBuilder builder,
#pragma warning restore RS0026
int statusCode = StatusCodes.Status200OK,
- string? contentType = null,
- params string[] additionalContentTypes)
- {
- return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes);
- }
+ string? contentType = null,
+ params string[] additionalContentTypes)
+ {
+ return Produces(builder, statusCode, typeof(TResponse), contentType, additionalContentTypes);
+ }
- /// <summary>
- /// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="statusCode">The response status code.</param>
- /// <param name="responseType">The type of the response. Defaults to null.</param>
- /// <param name="contentType">The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null.</param>
- /// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ /// <summary>
+ /// Adds an <see cref="IProducesResponseTypeMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="statusCode">The response status code.</param>
+ /// <param name="responseType">The type of the response. Defaults to null.</param>
+ /// <param name="contentType">The response content type. Defaults to "application/json" if responseType is not null, otherwise defaults to null.</param>
+ /// <param name="additionalContentTypes">Additional response content types the endpoint produces for the supplied status code.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
#pragma warning disable RS0026
- public static RouteHandlerBuilder Produces(this RouteHandlerBuilder builder,
+ public static RouteHandlerBuilder Produces(this RouteHandlerBuilder builder,
#pragma warning restore RS0026
int statusCode,
- Type? responseType = null,
- string? contentType = null,
- params string[] additionalContentTypes)
+ Type? responseType = null,
+ string? contentType = null,
+ params string[] additionalContentTypes)
+ {
+ if (responseType is Type && string.IsNullOrEmpty(contentType))
{
- if (responseType is Type && string.IsNullOrEmpty(contentType))
- {
- contentType = "application/json";
- }
-
- if (contentType is null)
- {
- builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode));
- return builder;
- }
-
- builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode, contentType, additionalContentTypes));
+ contentType = "application/json";
+ }
+ if (contentType is null)
+ {
+ builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode));
return builder;
}
- /// <summary>
- /// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="ProblemDetails"/> type
- /// to <see cref="EndpointBuilder.Metadata"/> for all builders produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="statusCode">The response status code.</param>
- /// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder builder,
- int statusCode,
- string? contentType = null)
- {
- if (string.IsNullOrEmpty(contentType))
- {
- contentType = "application/problem+json";
- }
+ builder.WithMetadata(new ProducesResponseTypeMetadata(responseType ?? typeof(void), statusCode, contentType, additionalContentTypes));
- return Produces<ProblemDetails>(builder, statusCode, contentType);
- }
+ return builder;
+ }
- /// <summary>
- /// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="HttpValidationProblemDetails"/> type
- /// to <see cref="EndpointBuilder.Metadata"/> for all builders produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status400BadRequest"/>.</param>
- /// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder ProducesValidationProblem(this RouteHandlerBuilder builder,
- int statusCode = StatusCodes.Status400BadRequest,
- string? contentType = null)
+ /// <summary>
+ /// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="ProblemDetails"/> type
+ /// to <see cref="EndpointBuilder.Metadata"/> for all builders produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="statusCode">The response status code.</param>
+ /// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder ProducesProblem(this RouteHandlerBuilder builder,
+ int statusCode,
+ string? contentType = null)
+ {
+ if (string.IsNullOrEmpty(contentType))
{
- if (string.IsNullOrEmpty(contentType))
- {
- contentType = "application/problem+json";
- }
-
- return Produces<HttpValidationProblemDetails>(builder, statusCode, contentType);
+ contentType = "application/problem+json";
}
- /// <summary>
- /// Adds the <see cref="ITagsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <remarks>
- /// The OpenAPI specification supports a tags classification to categorize operations
- /// into related groups. These tags are typically included in the generated specification
- /// and are typically used to group operations by tags in the UI.
- /// </remarks>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="tags">A collection of tags to be associated with the endpoint.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder WithTags(this RouteHandlerBuilder builder, params string[] tags)
+ return Produces<ProblemDetails>(builder, statusCode, contentType);
+ }
+
+ /// <summary>
+ /// Adds an <see cref="IProducesResponseTypeMetadata"/> with a <see cref="HttpValidationProblemDetails"/> type
+ /// to <see cref="EndpointBuilder.Metadata"/> for all builders produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="statusCode">The response status code. Defaults to <see cref="StatusCodes.Status400BadRequest"/>.</param>
+ /// <param name="contentType">The response content type. Defaults to "application/problem+json".</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder ProducesValidationProblem(this RouteHandlerBuilder builder,
+ int statusCode = StatusCodes.Status400BadRequest,
+ string? contentType = null)
+ {
+ if (string.IsNullOrEmpty(contentType))
{
- builder.WithMetadata(new TagsAttribute(tags));
- return builder;
+ contentType = "application/problem+json";
}
- /// <summary>
- /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <typeparam name="TRequest">The type of the request body.</typeparam>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="contentType">The request content type that the endpoint accepts.</param>
- /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder Accepts<TRequest>(this RouteHandlerBuilder builder,
- string contentType, params string[] additionalContentTypes) where TRequest : notnull
- {
- Accepts(builder, typeof(TRequest), contentType, additionalContentTypes);
+ return Produces<HttpValidationProblemDetails>(builder, statusCode, contentType);
+ }
- return builder;
- }
+ /// <summary>
+ /// Adds the <see cref="ITagsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <remarks>
+ /// The OpenAPI specification supports a tags classification to categorize operations
+ /// into related groups. These tags are typically included in the generated specification
+ /// and are typically used to group operations by tags in the UI.
+ /// </remarks>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="tags">A collection of tags to be associated with the endpoint.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder WithTags(this RouteHandlerBuilder builder, params string[] tags)
+ {
+ builder.WithMetadata(new TagsAttribute(tags));
+ return builder;
+ }
- /// <summary>
- /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <typeparam name="TRequest">The type of the request body.</typeparam>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="isOptional">Sets a value that determines if the request body is optional.</param>
- /// <param name="contentType">The request content type that the endpoint accepts.</param>
- /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder Accepts<TRequest>(this RouteHandlerBuilder builder,
- bool isOptional, string contentType, params string[] additionalContentTypes) where TRequest : notnull
- {
- Accepts(builder, typeof(TRequest), isOptional, contentType, additionalContentTypes);
+ /// <summary>
+ /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <typeparam name="TRequest">The type of the request body.</typeparam>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="contentType">The request content type that the endpoint accepts.</param>
+ /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder Accepts<TRequest>(this RouteHandlerBuilder builder,
+ string contentType, params string[] additionalContentTypes) where TRequest : notnull
+ {
+ Accepts(builder, typeof(TRequest), contentType, additionalContentTypes);
- return builder;
- }
+ return builder;
+ }
- /// <summary>
- /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="requestType">The type of the request body.</param>
- /// <param name="contentType">The request content type that the endpoint accepts.</param>
- /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder,
- Type requestType, string contentType, params string[] additionalContentTypes)
- {
- builder.WithMetadata(new AcceptsMetadata(requestType, false, GetAllContentTypes(contentType, additionalContentTypes)));
- return builder;
- }
+ /// <summary>
+ /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <typeparam name="TRequest">The type of the request body.</typeparam>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="isOptional">Sets a value that determines if the request body is optional.</param>
+ /// <param name="contentType">The request content type that the endpoint accepts.</param>
+ /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder Accepts<TRequest>(this RouteHandlerBuilder builder,
+ bool isOptional, string contentType, params string[] additionalContentTypes) where TRequest : notnull
+ {
+ Accepts(builder, typeof(TRequest), isOptional, contentType, additionalContentTypes);
+ return builder;
+ }
- /// <summary>
- /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
- /// <param name="requestType">The type of the request body.</param>
- /// <param name="isOptional">Sets a value that determines if the request body is optional.</param>
- /// <param name="contentType">The request content type that the endpoint accepts.</param>
- /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
- /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
- public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder,
- Type requestType, bool isOptional, string contentType, params string[] additionalContentTypes)
- {
- builder.WithMetadata(new AcceptsMetadata(requestType, isOptional, GetAllContentTypes(contentType, additionalContentTypes)));
- return builder;
- }
+ /// <summary>
+ /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="requestType">The type of the request body.</param>
+ /// <param name="contentType">The request content type that the endpoint accepts.</param>
+ /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder,
+ Type requestType, string contentType, params string[] additionalContentTypes)
+ {
+ builder.WithMetadata(new AcceptsMetadata(requestType, false, GetAllContentTypes(contentType, additionalContentTypes)));
+ return builder;
+ }
- private static string[] GetAllContentTypes(string contentType, string[] additionalContentTypes)
- {
- var allContentTypes = new string[additionalContentTypes.Length + 1];
- allContentTypes[0] = contentType;
- for (var i = 0; i < additionalContentTypes.Length; i++)
- {
- allContentTypes[i + 1] = additionalContentTypes[i];
- }
+ /// <summary>
+ /// Adds <see cref="IAcceptsMetadata"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="RouteHandlerBuilder"/>.</param>
+ /// <param name="requestType">The type of the request body.</param>
+ /// <param name="isOptional">Sets a value that determines if the request body is optional.</param>
+ /// <param name="contentType">The request content type that the endpoint accepts.</param>
+ /// <param name="additionalContentTypes">The list of additional request content types that the endpoint accepts.</param>
+ /// <returns>A <see cref="RouteHandlerBuilder"/> that can be used to further customize the endpoint.</returns>
+ public static RouteHandlerBuilder Accepts(this RouteHandlerBuilder builder,
+ Type requestType, bool isOptional, string contentType, params string[] additionalContentTypes)
+ {
+ builder.WithMetadata(new AcceptsMetadata(requestType, isOptional, GetAllContentTypes(contentType, additionalContentTypes)));
+ return builder;
+ }
- return allContentTypes;
+ private static string[] GetAllContentTypes(string contentType, string[] additionalContentTypes)
+ {
+ var allContentTypes = new string[additionalContentTypes.Length + 1];
+ allContentTypes[0] = contentType;
+
+ for (var i = 0; i < additionalContentTypes.Length; i++)
+ {
+ allContentTypes[i + 1] = additionalContentTypes[i];
}
+
+ return allContentTypes;
}
}
diff --git a/src/Http/Routing/src/Builder/RouteHandlerBuilder.cs b/src/Http/Routing/src/Builder/RouteHandlerBuilder.cs
index d1e778d99d..8879fe39c1 100644
--- a/src/Http/Routing/src/Builder/RouteHandlerBuilder.cs
+++ b/src/Http/Routing/src/Builder/RouteHandlerBuilder.cs
@@ -4,52 +4,51 @@
using System;
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Builds conventions that will be used for customization of MapAction <see cref="EndpointBuilder"/> instances.
+/// </summary>
+public sealed class RouteHandlerBuilder : IEndpointConventionBuilder
{
+ private readonly IEnumerable<IEndpointConventionBuilder>? _endpointConventionBuilders;
+ private readonly IEndpointConventionBuilder? _endpointConventionBuilder;
+
/// <summary>
- /// Builds conventions that will be used for customization of MapAction <see cref="EndpointBuilder"/> instances.
+ /// Instantiates a new <see cref="RouteHandlerBuilder" /> given a single
+ /// <see cref="IEndpointConventionBuilder" />.
/// </summary>
- public sealed class RouteHandlerBuilder : IEndpointConventionBuilder
+ /// <param name="endpointConventionBuilder">The <see cref="IEndpointConventionBuilder" /> to instantiate with.</param>
+ internal RouteHandlerBuilder(IEndpointConventionBuilder endpointConventionBuilder)
{
- private readonly IEnumerable<IEndpointConventionBuilder>? _endpointConventionBuilders;
- private readonly IEndpointConventionBuilder? _endpointConventionBuilder;
+ _endpointConventionBuilder = endpointConventionBuilder;
+ }
- /// <summary>
- /// Instantiates a new <see cref="RouteHandlerBuilder" /> given a single
- /// <see cref="IEndpointConventionBuilder" />.
- /// </summary>
- /// <param name="endpointConventionBuilder">The <see cref="IEndpointConventionBuilder" /> to instantiate with.</param>
- internal RouteHandlerBuilder(IEndpointConventionBuilder endpointConventionBuilder)
- {
- _endpointConventionBuilder = endpointConventionBuilder;
- }
+ /// <summary>
+ /// Instantiates a new <see cref="RouteHandlerBuilder" /> given multiple
+ /// <see cref="IEndpointConventionBuilder" /> instances.
+ /// </summary>
+ /// <param name="endpointConventionBuilders">A sequence of <see cref="IEndpointConventionBuilder" /> instances.</param>
+ public RouteHandlerBuilder(IEnumerable<IEndpointConventionBuilder> endpointConventionBuilders)
+ {
+ _endpointConventionBuilders = endpointConventionBuilders;
+ }
- /// <summary>
- /// Instantiates a new <see cref="RouteHandlerBuilder" /> given multiple
- /// <see cref="IEndpointConventionBuilder" /> instances.
- /// </summary>
- /// <param name="endpointConventionBuilders">A sequence of <see cref="IEndpointConventionBuilder" /> instances.</param>
- public RouteHandlerBuilder(IEnumerable<IEndpointConventionBuilder> endpointConventionBuilders)
+ /// <summary>
+ /// Adds the specified convention to the builder. Conventions are used to customize <see cref="EndpointBuilder"/> instances.
+ /// </summary>
+ /// <param name="convention">The convention to add to the builder.</param>
+ public void Add(Action<EndpointBuilder> convention)
+ {
+ if (_endpointConventionBuilder != null)
{
- _endpointConventionBuilders = endpointConventionBuilders;
+ _endpointConventionBuilder.Add(convention);
}
-
- /// <summary>
- /// Adds the specified convention to the builder. Conventions are used to customize <see cref="EndpointBuilder"/> instances.
- /// </summary>
- /// <param name="convention">The convention to add to the builder.</param>
- public void Add(Action<EndpointBuilder> convention)
+ else
{
- if (_endpointConventionBuilder != null)
- {
- _endpointConventionBuilder.Add(convention);
- }
- else
+ foreach (var endpointConventionBuilder in _endpointConventionBuilders!)
{
- foreach (var endpointConventionBuilder in _endpointConventionBuilders!)
- {
- endpointConventionBuilder.Add(convention);
- }
+ endpointConventionBuilder.Add(convention);
}
}
}
diff --git a/src/Http/Routing/src/Builder/RoutingBuilderExtensions.cs b/src/Http/Routing/src/Builder/RoutingBuilderExtensions.cs
index edf3adf18e..2722752758 100644
--- a/src/Http/Routing/src/Builder/RoutingBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/RoutingBuilderExtensions.cs
@@ -5,73 +5,72 @@ using System;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Extension methods for adding the <see cref="RouterMiddleware"/> middleware to an <see cref="IApplicationBuilder"/>.
+/// </summary>
+public static class RoutingBuilderExtensions
{
/// <summary>
- /// Extension methods for adding the <see cref="RouterMiddleware"/> middleware to an <see cref="IApplicationBuilder"/>.
+ /// Adds a <see cref="RouterMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/> with the specified <see cref="IRouter"/>.
/// </summary>
- public static class RoutingBuilderExtensions
+ /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
+ /// <param name="router">The <see cref="IRouter"/> to use for routing requests.</param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
{
- /// <summary>
- /// Adds a <see cref="RouterMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/> with the specified <see cref="IRouter"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
- /// <param name="router">The <see cref="IRouter"/> to use for routing requests.</param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router)
+ if (builder == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- if (router == null)
- {
- throw new ArgumentNullException(nameof(router));
- }
-
- if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
- {
- throw new InvalidOperationException(Resources.FormatUnableToFindServices(
- nameof(IServiceCollection),
- nameof(RoutingServiceCollectionExtensions.AddRouting),
- "ConfigureServices(...)"));
- }
+ throw new ArgumentNullException(nameof(builder));
+ }
- return builder.UseMiddleware<RouterMiddleware>(router);
+ if (router == null)
+ {
+ throw new ArgumentNullException(nameof(router));
}
- /// <summary>
- /// Adds a <see cref="RouterMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>
- /// with the <see cref="IRouter"/> built from configured <see cref="IRouteBuilder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
- /// <param name="action">An <see cref="Action{IRouteBuilder}"/> to configure the provided <see cref="IRouteBuilder"/>.</param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, Action<IRouteBuilder> action)
+ if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
+ throw new InvalidOperationException(Resources.FormatUnableToFindServices(
+ nameof(IServiceCollection),
+ nameof(RoutingServiceCollectionExtensions.AddRouting),
+ "ConfigureServices(...)"));
+ }
- if (action == null)
- {
- throw new ArgumentNullException(nameof(action));
- }
+ return builder.UseMiddleware<RouterMiddleware>(router);
+ }
- if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
- {
- throw new InvalidOperationException(Resources.FormatUnableToFindServices(
- nameof(IServiceCollection),
- nameof(RoutingServiceCollectionExtensions.AddRouting),
- "ConfigureServices(...)"));
- }
+ /// <summary>
+ /// Adds a <see cref="RouterMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>
+ /// with the <see cref="IRouter"/> built from configured <see cref="IRouteBuilder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
+ /// <param name="action">An <see cref="Action{IRouteBuilder}"/> to configure the provided <see cref="IRouteBuilder"/>.</param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, Action<IRouteBuilder> action)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
- var routeBuilder = new RouteBuilder(builder);
- action(routeBuilder);
+ if (action == null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
- return builder.UseRouter(routeBuilder.Build());
+ if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
+ {
+ throw new InvalidOperationException(Resources.FormatUnableToFindServices(
+ nameof(IServiceCollection),
+ nameof(RoutingServiceCollectionExtensions.AddRouting),
+ "ConfigureServices(...)"));
}
+
+ var routeBuilder = new RouteBuilder(builder);
+ action(routeBuilder);
+
+ return builder.UseRouter(routeBuilder.Build());
}
}
diff --git a/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs b/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs
index c99ae45d88..2901f44491 100644
--- a/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs
+++ b/src/Http/Routing/src/Builder/RoutingEndpointConventionBuilderExtensions.cs
@@ -4,149 +4,148 @@
using System;
using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Extension methods for adding routing metadata to endpoint instances using <see cref="IEndpointConventionBuilder"/>.
+/// </summary>
+public static class RoutingEndpointConventionBuilderExtensions
{
/// <summary>
- /// Extension methods for adding routing metadata to endpoint instances using <see cref="IEndpointConventionBuilder"/>.
+ /// Requires that endpoints match one of the specified hosts during routing.
/// </summary>
- public static class RoutingEndpointConventionBuilderExtensions
+ /// <param name="builder">The <see cref="IEndpointConventionBuilder"/> to add the metadata to.</param>
+ /// <param name="hosts">
+ /// The hosts used during routing.
+ /// Hosts should be Unicode rather than punycode, and may have a port.
+ /// An empty collection means any host will be accepted.
+ /// </param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static TBuilder RequireHost<TBuilder>(this TBuilder builder, params string[] hosts) where TBuilder : IEndpointConventionBuilder
{
- /// <summary>
- /// Requires that endpoints match one of the specified hosts during routing.
- /// </summary>
- /// <param name="builder">The <see cref="IEndpointConventionBuilder"/> to add the metadata to.</param>
- /// <param name="hosts">
- /// The hosts used during routing.
- /// Hosts should be Unicode rather than punycode, and may have a port.
- /// An empty collection means any host will be accepted.
- /// </param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static TBuilder RequireHost<TBuilder>(this TBuilder builder, params string[] hosts) where TBuilder : IEndpointConventionBuilder
+ if (builder == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- if (hosts == null)
- {
- throw new ArgumentNullException(nameof(hosts));
- }
-
- builder.Add(endpointBuilder =>
- {
- endpointBuilder.Metadata.Add(new HostAttribute(hosts));
- });
- return builder;
+ throw new ArgumentNullException(nameof(builder));
}
- /// <summary>
- /// Sets the <see cref="EndpointBuilder.DisplayName"/> to the provided <paramref name="displayName"/> for all
- /// builders created by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
- /// <param name="displayName">The display name.</param>
- /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
- public static TBuilder WithDisplayName<TBuilder>(this TBuilder builder, string displayName) where TBuilder : IEndpointConventionBuilder
+ if (hosts == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
+ throw new ArgumentNullException(nameof(hosts));
+ }
- builder.Add(b =>
- {
- b.DisplayName = displayName;
- });
+ builder.Add(endpointBuilder =>
+ {
+ endpointBuilder.Metadata.Add(new HostAttribute(hosts));
+ });
+ return builder;
+ }
- return builder;
+ /// <summary>
+ /// Sets the <see cref="EndpointBuilder.DisplayName"/> to the provided <paramref name="displayName"/> for all
+ /// builders created by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
+ /// <param name="displayName">The display name.</param>
+ /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
+ public static TBuilder WithDisplayName<TBuilder>(this TBuilder builder, string displayName) where TBuilder : IEndpointConventionBuilder
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
}
- /// <summary>
- /// Sets the <see cref="EndpointBuilder.DisplayName"/> using the provided <paramref name="func"/> for all
- /// builders created by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
- /// <param name="func">A delegate that produces the display name for each <see cref="EndpointBuilder"/>.</param>
- /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
- public static TBuilder WithDisplayName<TBuilder>(this TBuilder builder, Func<EndpointBuilder, string> func) where TBuilder : IEndpointConventionBuilder
+ builder.Add(b =>
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
-
- if (func == null)
- {
- throw new ArgumentNullException(nameof(func));
- }
+ b.DisplayName = displayName;
+ });
- builder.Add(b =>
- {
- b.DisplayName = func(b);
- });
+ return builder;
+ }
- return builder;
+ /// <summary>
+ /// Sets the <see cref="EndpointBuilder.DisplayName"/> using the provided <paramref name="func"/> for all
+ /// builders created by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
+ /// <param name="func">A delegate that produces the display name for each <see cref="EndpointBuilder"/>.</param>
+ /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
+ public static TBuilder WithDisplayName<TBuilder>(this TBuilder builder, Func<EndpointBuilder, string> func) where TBuilder : IEndpointConventionBuilder
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
}
- /// <summary>
- /// Adds the provided metadata <paramref name="items"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
- /// produced by <paramref name="builder"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
- /// <param name="items">A collection of metadata items.</param>
- /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
- public static TBuilder WithMetadata<TBuilder>(this TBuilder builder, params object[] items) where TBuilder : IEndpointConventionBuilder
+ if (func == null)
{
- if (builder == null)
- {
- throw new ArgumentNullException(nameof(builder));
- }
+ throw new ArgumentNullException(nameof(func));
+ }
- if (items == null)
- {
- throw new ArgumentNullException(nameof(items));
- }
+ builder.Add(b =>
+ {
+ b.DisplayName = func(b);
+ });
- builder.Add(b =>
- {
- foreach (var item in items)
- {
- b.Metadata.Add(item);
- }
- });
+ return builder;
+ }
- return builder;
+ /// <summary>
+ /// Adds the provided metadata <paramref name="items"/> to <see cref="EndpointBuilder.Metadata"/> for all builders
+ /// produced by <paramref name="builder"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
+ /// <param name="items">A collection of metadata items.</param>
+ /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
+ public static TBuilder WithMetadata<TBuilder>(this TBuilder builder, params object[] items) where TBuilder : IEndpointConventionBuilder
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
}
- /// <summary>
- /// Sets the <see cref="EndpointNameAttribute"/> for all endpoints produced
- /// on the target <see cref="IEndpointConventionBuilder"/> given the <paramref name="endpointName" />.
- /// The <see cref="IEndpointNameMetadata" /> on the endpoint is used for link generation and
- /// is treated as the operation ID in the given endpoint's OpenAPI specification.
- /// </summary>
- /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
- /// <param name="endpointName">The endpoint name.</param>
- /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
- public static TBuilder WithName<TBuilder>(this TBuilder builder, string endpointName) where TBuilder : IEndpointConventionBuilder
+ if (items == null)
{
- builder.WithMetadata(new EndpointNameMetadata(endpointName), new RouteNameMetadata(endpointName));
- return builder;
+ throw new ArgumentNullException(nameof(items));
}
- /// <summary>
- /// Sets the <see cref="EndpointGroupNameAttribute"/> for all endpoints produced
- /// on the target <see cref="IEndpointConventionBuilder"/> given the <paramref name="endpointGroupName" />.
- /// The <see cref="IEndpointGroupNameMetadata" /> on the endpoint is used to set the endpoint's
- /// GroupName in the OpenAPI specification.
- /// </summary>
- /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
- /// <param name="endpointGroupName">The endpoint group name.</param>
- /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
- public static TBuilder WithGroupName<TBuilder>(this TBuilder builder, string endpointGroupName) where TBuilder : IEndpointConventionBuilder
+ builder.Add(b =>
{
- builder.WithMetadata(new EndpointGroupNameAttribute(endpointGroupName));
- return builder;
- }
+ foreach (var item in items)
+ {
+ b.Metadata.Add(item);
+ }
+ });
+
+ return builder;
+ }
+
+ /// <summary>
+ /// Sets the <see cref="EndpointNameAttribute"/> for all endpoints produced
+ /// on the target <see cref="IEndpointConventionBuilder"/> given the <paramref name="endpointName" />.
+ /// The <see cref="IEndpointNameMetadata" /> on the endpoint is used for link generation and
+ /// is treated as the operation ID in the given endpoint's OpenAPI specification.
+ /// </summary>
+ /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
+ /// <param name="endpointName">The endpoint name.</param>
+ /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
+ public static TBuilder WithName<TBuilder>(this TBuilder builder, string endpointName) where TBuilder : IEndpointConventionBuilder
+ {
+ builder.WithMetadata(new EndpointNameMetadata(endpointName), new RouteNameMetadata(endpointName));
+ return builder;
+ }
+
+ /// <summary>
+ /// Sets the <see cref="EndpointGroupNameAttribute"/> for all endpoints produced
+ /// on the target <see cref="IEndpointConventionBuilder"/> given the <paramref name="endpointGroupName" />.
+ /// The <see cref="IEndpointGroupNameMetadata" /> on the endpoint is used to set the endpoint's
+ /// GroupName in the OpenAPI specification.
+ /// </summary>
+ /// <param name="builder">The <see cref="IEndpointConventionBuilder"/>.</param>
+ /// <param name="endpointGroupName">The endpoint group name.</param>
+ /// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
+ public static TBuilder WithGroupName<TBuilder>(this TBuilder builder, string endpointGroupName) where TBuilder : IEndpointConventionBuilder
+ {
+ builder.WithMetadata(new EndpointGroupNameAttribute(endpointGroupName));
+ return builder;
}
}
diff --git a/src/Http/Routing/src/CompositeEndpointDataSource.cs b/src/Http/Routing/src/CompositeEndpointDataSource.cs
index e38b22be24..fa9f30c9a1 100644
--- a/src/Http/Routing/src/CompositeEndpointDataSource.cs
+++ b/src/Http/Routing/src/CompositeEndpointDataSource.cs
@@ -13,215 +13,214 @@ using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents an <see cref="EndpointDataSource"/> whose values come from a collection of <see cref="EndpointDataSource"/> instances.
+/// </summary>
+[DebuggerDisplay("{DebuggerDisplayString,nq}")]
+public sealed class CompositeEndpointDataSource : EndpointDataSource
{
- /// <summary>
- /// Represents an <see cref="EndpointDataSource"/> whose values come from a collection of <see cref="EndpointDataSource"/> instances.
- /// </summary>
- [DebuggerDisplay("{DebuggerDisplayString,nq}")]
- public sealed class CompositeEndpointDataSource : EndpointDataSource
+ private readonly object _lock;
+ private readonly ICollection<EndpointDataSource> _dataSources = default!;
+ private IReadOnlyList<Endpoint> _endpoints = default!;
+ private IChangeToken _consumerChangeToken;
+ private CancellationTokenSource _cts;
+
+ private CompositeEndpointDataSource()
{
- private readonly object _lock;
- private readonly ICollection<EndpointDataSource> _dataSources = default!;
- private IReadOnlyList<Endpoint> _endpoints = default!;
- private IChangeToken _consumerChangeToken;
- private CancellationTokenSource _cts;
+ CreateChangeToken();
+ _lock = new object();
+ }
- private CompositeEndpointDataSource()
- {
- CreateChangeToken();
- _lock = new object();
- }
+ internal CompositeEndpointDataSource(ObservableCollection<EndpointDataSource> dataSources) : this()
+ {
+ dataSources.CollectionChanged += OnDataSourcesChanged;
- internal CompositeEndpointDataSource(ObservableCollection<EndpointDataSource> dataSources) : this()
- {
- dataSources.CollectionChanged += OnDataSourcesChanged;
+ _dataSources = dataSources;
+ }
- _dataSources = dataSources;
- }
+ /// <summary>
+ /// Instantiates a <see cref="CompositeEndpointDataSource"/> object from <paramref name="endpointDataSources"/>.
+ /// </summary>
+ /// <param name="endpointDataSources">An collection of <see cref="EndpointDataSource" /> objects.</param>
+ /// <returns>A <see cref="CompositeEndpointDataSource"/> </returns>
+ public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
+ {
+ _dataSources = new List<EndpointDataSource>();
- /// <summary>
- /// Instantiates a <see cref="CompositeEndpointDataSource"/> object from <paramref name="endpointDataSources"/>.
- /// </summary>
- /// <param name="endpointDataSources">An collection of <see cref="EndpointDataSource" /> objects.</param>
- /// <returns>A <see cref="CompositeEndpointDataSource"/> </returns>
- public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
+ foreach (var dataSource in endpointDataSources)
{
- _dataSources = new List<EndpointDataSource>();
-
- foreach (var dataSource in endpointDataSources)
- {
- _dataSources.Add(dataSource);
- }
+ _dataSources.Add(dataSource);
}
+ }
- private void OnDataSourcesChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ private void OnDataSourcesChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ lock (_lock)
{
- lock (_lock)
+ // Only trigger changes if composite data source has already initialized endpoints
+ if (_endpoints != null)
{
- // Only trigger changes if composite data source has already initialized endpoints
- if (_endpoints != null)
- {
- HandleChange();
- }
+ HandleChange();
}
}
+ }
+
+ /// <summary>
+ /// Returns the collection of <see cref="EndpointDataSource"/> instances associated with the object.
+ /// </summary>
+ public IEnumerable<EndpointDataSource> DataSources => _dataSources;
+
+ /// <summary>
+ /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
+ /// instances.
+ /// </summary>
+ /// <returns>The <see cref="IChangeToken"/>.</returns>
+ public override IChangeToken GetChangeToken()
+ {
+ EnsureInitialized();
+ return _consumerChangeToken;
+ }
- /// <summary>
- /// Returns the collection of <see cref="EndpointDataSource"/> instances associated with the object.
- /// </summary>
- public IEnumerable<EndpointDataSource> DataSources => _dataSources;
-
- /// <summary>
- /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
- /// instances.
- /// </summary>
- /// <returns>The <see cref="IChangeToken"/>.</returns>
- public override IChangeToken GetChangeToken()
+ /// <summary>
+ /// Returns a read-only collection of <see cref="Endpoint"/> instances.
+ /// </summary>
+ public override IReadOnlyList<Endpoint> Endpoints
+ {
+ get
{
EnsureInitialized();
- return _consumerChangeToken;
+ return _endpoints;
}
+ }
- /// <summary>
- /// Returns a read-only collection of <see cref="Endpoint"/> instances.
- /// </summary>
- public override IReadOnlyList<Endpoint> Endpoints
+ // Defer initialization to avoid doing lots of reflection on startup.
+ private void EnsureInitialized()
+ {
+ if (_endpoints == null)
{
- get
- {
- EnsureInitialized();
- return _endpoints;
- }
+ Initialize();
}
+ }
- // Defer initialization to avoid doing lots of reflection on startup.
- private void EnsureInitialized()
+ // Note: we can't use DataSourceDependentCache here because we also need to handle a list of change
+ // tokens, which is a complication most of our code doesn't have.
+ private void Initialize()
+ {
+ lock (_lock)
{
if (_endpoints == null)
{
- Initialize();
- }
- }
+ _endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
- // Note: we can't use DataSourceDependentCache here because we also need to handle a list of change
- // tokens, which is a complication most of our code doesn't have.
- private void Initialize()
- {
- lock (_lock)
- {
- if (_endpoints == null)
+ foreach (var dataSource in _dataSources)
{
- _endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
-
- foreach (var dataSource in _dataSources)
- {
- ChangeToken.OnChange(
- dataSource.GetChangeToken,
- HandleChange);
- }
+ ChangeToken.OnChange(
+ dataSource.GetChangeToken,
+ HandleChange);
}
}
}
+ }
- private void HandleChange()
+ private void HandleChange()
+ {
+ lock (_lock)
{
- lock (_lock)
- {
- // Refresh the endpoints from datasource so that callbacks can get the latest endpoints
- _endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
+ // Refresh the endpoints from datasource so that callbacks can get the latest endpoints
+ _endpoints = _dataSources.SelectMany(d => d.Endpoints).ToArray();
+
+ // Prevent consumers from re-registering callback to inflight events as that can
+ // cause a stackoverflow
+ // Example:
+ // 1. B registers A
+ // 2. A fires event causing B's callback to get called
+ // 3. B executes some code in its callback, but needs to re-register callback
+ // in the same callback
+ var oldTokenSource = _cts;
+ var oldToken = _consumerChangeToken;
- // Prevent consumers from re-registering callback to inflight events as that can
- // cause a stackoverflow
- // Example:
- // 1. B registers A
- // 2. A fires event causing B's callback to get called
- // 3. B executes some code in its callback, but needs to re-register callback
- // in the same callback
- var oldTokenSource = _cts;
- var oldToken = _consumerChangeToken;
-
- CreateChangeToken();
-
- // Raise consumer callbacks. Any new callback registration would happen on the new token
- // created in earlier step.
- oldTokenSource.Cancel();
- }
- }
+ CreateChangeToken();
- [MemberNotNull(nameof(_cts), nameof(_consumerChangeToken))]
- private void CreateChangeToken()
- {
- _cts = new CancellationTokenSource();
- _consumerChangeToken = new CancellationChangeToken(_cts.Token);
+ // Raise consumer callbacks. Any new callback registration would happen on the new token
+ // created in earlier step.
+ oldTokenSource.Cancel();
}
+ }
+
+ [MemberNotNull(nameof(_cts), nameof(_consumerChangeToken))]
+ private void CreateChangeToken()
+ {
+ _cts = new CancellationTokenSource();
+ _consumerChangeToken = new CancellationChangeToken(_cts.Token);
+ }
- private string DebuggerDisplayString
+ private string DebuggerDisplayString
+ {
+ get
{
- get
+ // Try using private variable '_endpoints' to avoid initialization
+ if (_endpoints == null)
{
- // Try using private variable '_endpoints' to avoid initialization
- if (_endpoints == null)
- {
- return "No endpoints";
- }
+ return "No endpoints";
+ }
- var sb = new StringBuilder();
- foreach (var endpoint in _endpoints)
+ var sb = new StringBuilder();
+ foreach (var endpoint in _endpoints)
+ {
+ if (endpoint is RouteEndpoint routeEndpoint)
{
- if (endpoint is RouteEndpoint routeEndpoint)
+ var template = routeEndpoint.RoutePattern.RawText;
+ template = string.IsNullOrEmpty(template) ? "\"\"" : template;
+ sb.Append(template);
+ sb.Append(", Defaults: new { ");
+ sb.AppendJoin(", ", FormatValues(routeEndpoint.RoutePattern.Defaults));
+ sb.Append(" }");
+ var routeNameMetadata = routeEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
+ sb.Append(", Route Name: ");
+ sb.Append(routeNameMetadata?.RouteName);
+ var routeValues = routeEndpoint.RoutePattern.RequiredValues;
+ if (routeValues.Count > 0)
{
- var template = routeEndpoint.RoutePattern.RawText;
- template = string.IsNullOrEmpty(template) ? "\"\"" : template;
- sb.Append(template);
- sb.Append(", Defaults: new { ");
- sb.AppendJoin(", ", FormatValues(routeEndpoint.RoutePattern.Defaults));
+ sb.Append(", Required Values: new { ");
+ sb.AppendJoin(", ", FormatValues(routeValues));
sb.Append(" }");
- var routeNameMetadata = routeEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
- sb.Append(", Route Name: ");
- sb.Append(routeNameMetadata?.RouteName);
- var routeValues = routeEndpoint.RoutePattern.RequiredValues;
- if (routeValues.Count > 0)
- {
- sb.Append(", Required Values: new { ");
- sb.AppendJoin(", ", FormatValues(routeValues));
- sb.Append(" }");
- }
- sb.Append(", Order: ");
- sb.Append(routeEndpoint.Order);
-
- var httpMethodMetadata = routeEndpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- if (httpMethodMetadata != null)
- {
- sb.Append(", Http Methods: ");
- sb.AppendJoin(", ", httpMethodMetadata.HttpMethods);
- }
- sb.Append(", Display Name: ");
- sb.Append(routeEndpoint.DisplayName);
- sb.AppendLine();
}
- else
+ sb.Append(", Order: ");
+ sb.Append(routeEndpoint.Order);
+
+ var httpMethodMetadata = routeEndpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ if (httpMethodMetadata != null)
{
- sb.Append("Non-RouteEndpoint. DisplayName:");
- sb.AppendLine(endpoint.DisplayName);
+ sb.Append(", Http Methods: ");
+ sb.AppendJoin(", ", httpMethodMetadata.HttpMethods);
}
+ sb.Append(", Display Name: ");
+ sb.Append(routeEndpoint.DisplayName);
+ sb.AppendLine();
}
- return sb.ToString();
-
- IEnumerable<string> FormatValues(IEnumerable<KeyValuePair<string, object?>> values)
+ else
{
- return values.Select(
- kvp =>
- {
- var value = "null";
- if (kvp.Value != null)
- {
- value = "\"" + kvp.Value.ToString() + "\"";
- }
- return kvp.Key + " = " + value;
- });
+ sb.Append("Non-RouteEndpoint. DisplayName:");
+ sb.AppendLine(endpoint.DisplayName);
}
}
+ return sb.ToString();
+
+ IEnumerable<string> FormatValues(IEnumerable<KeyValuePair<string, object?>> values)
+ {
+ return values.Select(
+ kvp =>
+ {
+ var value = "null";
+ if (kvp.Value != null)
+ {
+ value = "\"" + kvp.Value.ToString() + "\"";
+ }
+ return kvp.Key + " = " + value;
+ });
+ }
}
}
}
diff --git a/src/Http/Routing/src/ConfigureRouteHandlerOptions.cs b/src/Http/Routing/src/ConfigureRouteHandlerOptions.cs
index a3781ca7e5..a88bd37dc4 100644
--- a/src/Http/Routing/src/ConfigureRouteHandlerOptions.cs
+++ b/src/Http/Routing/src/ConfigureRouteHandlerOptions.cs
@@ -9,23 +9,22 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed class ConfigureRouteHandlerOptions : IConfigureOptions<RouteHandlerOptions>
{
- internal sealed class ConfigureRouteHandlerOptions : IConfigureOptions<RouteHandlerOptions>
- {
- private readonly IHostEnvironment _environment;
+ private readonly IHostEnvironment _environment;
- public ConfigureRouteHandlerOptions(IHostEnvironment environment)
- {
- _environment = environment;
- }
+ public ConfigureRouteHandlerOptions(IHostEnvironment environment)
+ {
+ _environment = environment;
+ }
- public void Configure(RouteHandlerOptions options)
+ public void Configure(RouteHandlerOptions options)
+ {
+ if (_environment.IsDevelopment())
{
- if (_environment.IsDevelopment())
- {
- options.ThrowOnBadRequest = true;
- }
+ options.ThrowOnBadRequest = true;
}
}
}
diff --git a/src/Http/Routing/src/ConfigureRouteOptions.cs b/src/Http/Routing/src/ConfigureRouteOptions.cs
index eb89e526f4..e8a4b96aa9 100644
--- a/src/Http/Routing/src/ConfigureRouteOptions.cs
+++ b/src/Http/Routing/src/ConfigureRouteOptions.cs
@@ -7,30 +7,29 @@ using System.Collections.ObjectModel;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
-namespace Microsoft.Extensions.DependencyInjection
+namespace Microsoft.Extensions.DependencyInjection;
+
+internal class ConfigureRouteOptions : IConfigureOptions<RouteOptions>
{
- internal class ConfigureRouteOptions : IConfigureOptions<RouteOptions>
- {
- private readonly ICollection<EndpointDataSource> _dataSources;
+ private readonly ICollection<EndpointDataSource> _dataSources;
- public ConfigureRouteOptions(ICollection<EndpointDataSource> dataSources)
+ public ConfigureRouteOptions(ICollection<EndpointDataSource> dataSources)
+ {
+ if (dataSources == null)
{
- if (dataSources == null)
- {
- throw new ArgumentNullException(nameof(dataSources));
- }
-
- _dataSources = dataSources;
+ throw new ArgumentNullException(nameof(dataSources));
}
- public void Configure(RouteOptions options)
- {
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ _dataSources = dataSources;
+ }
- options.EndpointDataSources = _dataSources;
+ public void Configure(RouteOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
}
+
+ options.EndpointDataSources = _dataSources;
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/src/Constraints/AlphaRouteConstraint.cs b/src/Http/Routing/src/Constraints/AlphaRouteConstraint.cs
index 23758a95d0..abb5031396 100644
--- a/src/Http/Routing/src/Constraints/AlphaRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/AlphaRouteConstraint.cs
@@ -1,18 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to contain only lowercase or uppercase letters A through Z in the English alphabet.
+/// </summary>
+public class AlphaRouteConstraint : RegexRouteConstraint
{
/// <summary>
- /// Constrains a route parameter to contain only lowercase or uppercase letters A through Z in the English alphabet.
+ /// Initializes a new instance of the <see cref="AlphaRouteConstraint" /> class.
/// </summary>
- public class AlphaRouteConstraint : RegexRouteConstraint
+ public AlphaRouteConstraint() : base(@"^[a-z]*$")
{
- /// <summary>
- /// Initializes a new instance of the <see cref="AlphaRouteConstraint" /> class.
- /// </summary>
- public AlphaRouteConstraint() : base(@"^[a-z]*$")
- {
- }
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/src/Constraints/BoolRouteConstraint.cs b/src/Http/Routing/src/Constraints/BoolRouteConstraint.cs
index 99e70d4721..8abe828f88 100644
--- a/src/Http/Routing/src/Constraints/BoolRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/BoolRouteConstraint.cs
@@ -6,53 +6,52 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only Boolean values.
+/// </summary>
+public class BoolRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only Boolean values.
- /// </summary>
- public class BoolRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is bool)
{
- if (value is bool)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return bool.TryParse(valueString, out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return bool.TryParse(valueString, out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/CompositeRouteConstraint.cs b/src/Http/Routing/src/Constraints/CompositeRouteConstraint.cs
index 0b141ee191..94a0589889 100644
--- a/src/Http/Routing/src/Constraints/CompositeRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/CompositeRouteConstraint.cs
@@ -6,72 +6,71 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route by several child constraints.
+/// </summary>
+public class CompositeRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constrains a route by several child constraints.
+ /// Initializes a new instance of the <see cref="CompositeRouteConstraint" /> class.
/// </summary>
- public class CompositeRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="constraints">The child constraints that must match for this constraint to match.</param>
+ public CompositeRouteConstraint(IEnumerable<IRouteConstraint> constraints)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="CompositeRouteConstraint" /> class.
- /// </summary>
- /// <param name="constraints">The child constraints that must match for this constraint to match.</param>
- public CompositeRouteConstraint(IEnumerable<IRouteConstraint> constraints)
+ if (constraints == null)
{
- if (constraints == null)
- {
- throw new ArgumentNullException(nameof(constraints));
- }
-
- Constraints = constraints;
+ throw new ArgumentNullException(nameof(constraints));
}
- /// <summary>
- /// Gets the child constraints that must match for this constraint to match.
- /// </summary>
- public IEnumerable<IRouteConstraint> Constraints { get; private set; }
+ Constraints = constraints;
+ }
+
+ /// <summary>
+ /// Gets the child constraints that must match for this constraint to match.
+ /// </summary>
+ public IEnumerable<IRouteConstraint> Constraints { get; private set; }
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- foreach (var constraint in Constraints)
+ foreach (var constraint in Constraints)
+ {
+ if (!constraint.Match(httpContext, route, routeKey, values, routeDirection))
{
- if (!constraint.Match(httpContext, route, routeKey, values, routeDirection))
- {
- return false;
- }
+ return false;
}
-
- return true;
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ return true;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ foreach (var constraint in Constraints)
{
- foreach (var constraint in Constraints)
+ if (constraint is IParameterLiteralNodeMatchingPolicy literalConstraint && !literalConstraint.MatchesLiteral(parameterName, literal))
{
- if (constraint is IParameterLiteralNodeMatchingPolicy literalConstraint && !literalConstraint.MatchesLiteral(parameterName, literal))
- {
- return false;
- }
+ return false;
}
-
- return true;
}
+
+ return true;
}
}
diff --git a/src/Http/Routing/src/Constraints/DateTimeRouteConstraint.cs b/src/Http/Routing/src/Constraints/DateTimeRouteConstraint.cs
index 0f7b53dbfb..9d012719dc 100644
--- a/src/Http/Routing/src/Constraints/DateTimeRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/DateTimeRouteConstraint.cs
@@ -6,59 +6,58 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only <see cref="DateTime"/> values.
+/// </summary>
+/// <remarks>
+/// This constraint tries to parse strings by using all of the formats returned by the
+/// CultureInfo.InvariantCulture.DateTimeFormat.GetAllDateTimePatterns() method.
+/// For a sample on how to list all formats which are considered, please visit
+/// http://msdn.microsoft.com/en-us/library/aszyst2c(v=vs.110).aspx
+/// </remarks>
+public class DateTimeRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only <see cref="DateTime"/> values.
- /// </summary>
- /// <remarks>
- /// This constraint tries to parse strings by using all of the formats returned by the
- /// CultureInfo.InvariantCulture.DateTimeFormat.GetAllDateTimePatterns() method.
- /// For a sample on how to list all formats which are considered, please visit
- /// http://msdn.microsoft.com/en-us/library/aszyst2c(v=vs.110).aspx
- /// </remarks>
- public class DateTimeRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is DateTime)
{
- if (value is DateTime)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return DateTime.TryParse(valueString, CultureInfo.InvariantCulture, DateTimeStyles.None, out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/DecimalRouteConstraint.cs b/src/Http/Routing/src/Constraints/DecimalRouteConstraint.cs
index ff142ba9d9..d5d2b3f24f 100644
--- a/src/Http/Routing/src/Constraints/DecimalRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/DecimalRouteConstraint.cs
@@ -6,53 +6,52 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only decimal values.
+/// </summary>
+public class DecimalRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only decimal values.
- /// </summary>
- public class DecimalRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is decimal)
{
- if (value is decimal)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return decimal.TryParse(valueString, NumberStyles.Number, CultureInfo.InvariantCulture, out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/DoubleRouteConstraint.cs b/src/Http/Routing/src/Constraints/DoubleRouteConstraint.cs
index 8fd6d6b5b0..fc27b421d0 100644
--- a/src/Http/Routing/src/Constraints/DoubleRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/DoubleRouteConstraint.cs
@@ -6,57 +6,56 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only 64-bit floating-point values.
+/// </summary>
+public class DoubleRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only 64-bit floating-point values.
- /// </summary>
- public class DoubleRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is double)
{
- if (value is double)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return double.TryParse(
- valueString,
- NumberStyles.Float | NumberStyles.AllowThousands,
- CultureInfo.InvariantCulture,
- out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return double.TryParse(
+ valueString,
+ NumberStyles.Float | NumberStyles.AllowThousands,
+ CultureInfo.InvariantCulture,
+ out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/FileNameRouteConstraint.cs b/src/Http/Routing/src/Constraints/FileNameRouteConstraint.cs
index f716ae6b05..49c50fbb62 100644
--- a/src/Http/Routing/src/Constraints/FileNameRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/FileNameRouteConstraint.cs
@@ -6,149 +6,148 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only file name values. Does not validate that
+/// the route value contains valid file system characters, or that the value represents
+/// an actual file on disk.
+/// </summary>
+/// <remarks>
+/// <para>
+/// This constraint can be used to disambiguate requests for static files versus dynamic
+/// content served from the application.
+/// </para>
+/// <para>
+/// This constraint determines whether a route value represents a file name by examining
+/// the last URL Path segment of the value (delimited by <c>/</c>). The last segment
+/// must contain the dot (<c>.</c>) character followed by one or more non-(<c>.</c>) characters.
+/// </para>
+/// <para>
+/// If the route value does not contain a <c>/</c> then the entire value will be interpreted
+/// as the last segment.
+/// </para>
+/// <para>
+/// The <see cref="FileNameRouteConstraint"/> does not attempt to validate that the value contains
+/// a legal file name for the current operating system.
+/// </para>
+/// <para>
+/// The <see cref="FileNameRouteConstraint"/> does not attempt to validate that the value represents
+/// an actual file on disk.
+/// </para>
+/// <para>
+/// <list type="bullet">
+/// <listheader>
+/// <term>Examples of route values that will be matched as file names</term>
+/// <description>description</description>
+/// </listheader>
+/// <item>
+/// <term><c>/a/b/c.txt</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// <item>
+/// <term><c>/hello.world.txt</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// <item>
+/// <term><c>hello.world.txt</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// <item>
+/// <term><c>.gitignore</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// </list>
+/// <list type="bullet">
+/// <listheader>
+/// <term>Examples of route values that will be rejected as non-file-names</term>
+/// <description>description</description>
+/// </listheader>
+/// <item>
+/// <term><c>/a/b/c</c></term>
+/// <description>Final segment does not contain a <c>.</c>.</description>
+/// </item>
+/// <item>
+/// <term><c>/a/b.d/c</c></term>
+/// <description>Final segment does not contain a <c>.</c>.</description>
+/// </item>
+/// <item>
+/// <term><c>/a/b.d/c/</c></term>
+/// <description>Final segment is empty.</description>
+/// </item>
+/// <item>
+/// <term><c></c></term>
+/// <description>Value is empty</description>
+/// </item>
+/// </list>
+/// </para>
+/// </remarks>
+public class FileNameRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only file name values. Does not validate that
- /// the route value contains valid file system characters, or that the value represents
- /// an actual file on disk.
- /// </summary>
- /// <remarks>
- /// <para>
- /// This constraint can be used to disambiguate requests for static files versus dynamic
- /// content served from the application.
- /// </para>
- /// <para>
- /// This constraint determines whether a route value represents a file name by examining
- /// the last URL Path segment of the value (delimited by <c>/</c>). The last segment
- /// must contain the dot (<c>.</c>) character followed by one or more non-(<c>.</c>) characters.
- /// </para>
- /// <para>
- /// If the route value does not contain a <c>/</c> then the entire value will be interpreted
- /// as the last segment.
- /// </para>
- /// <para>
- /// The <see cref="FileNameRouteConstraint"/> does not attempt to validate that the value contains
- /// a legal file name for the current operating system.
- /// </para>
- /// <para>
- /// The <see cref="FileNameRouteConstraint"/> does not attempt to validate that the value represents
- /// an actual file on disk.
- /// </para>
- /// <para>
- /// <list type="bullet">
- /// <listheader>
- /// <term>Examples of route values that will be matched as file names</term>
- /// <description>description</description>
- /// </listheader>
- /// <item>
- /// <term><c>/a/b/c.txt</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// <item>
- /// <term><c>/hello.world.txt</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// <item>
- /// <term><c>hello.world.txt</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// <item>
- /// <term><c>.gitignore</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// </list>
- /// <list type="bullet">
- /// <listheader>
- /// <term>Examples of route values that will be rejected as non-file-names</term>
- /// <description>description</description>
- /// </listheader>
- /// <item>
- /// <term><c>/a/b/c</c></term>
- /// <description>Final segment does not contain a <c>.</c>.</description>
- /// </item>
- /// <item>
- /// <term><c>/a/b.d/c</c></term>
- /// <description>Final segment does not contain a <c>.</c>.</description>
- /// </item>
- /// <item>
- /// <term><c>/a/b.d/c/</c></term>
- /// <description>Final segment is empty.</description>
- /// </item>
- /// <item>
- /// <term><c></c></term>
- /// <description>Value is empty</description>
- /// </item>
- /// </list>
- /// </para>
- /// </remarks>
- public class FileNameRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var obj) && obj != null)
- {
- var value = Convert.ToString(obj, CultureInfo.InvariantCulture);
- return IsFileName(value);
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- // No value or null value.
- return false;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- // This is used both here and in NonFileNameRouteConstraint
- // Any changes to this logic need to update the docs in those places.
- internal static bool IsFileName(ReadOnlySpan<char> value)
+ if (values.TryGetValue(routeKey, out var obj) && obj != null)
{
- if (value.Length == 0)
- {
- // Not a file name because empty.
- return false;
- }
+ var value = Convert.ToString(obj, CultureInfo.InvariantCulture);
+ return IsFileName(value);
+ }
- var lastSlashIndex = value.LastIndexOf('/');
- if (lastSlashIndex >= 0)
- {
- value = value.Slice(lastSlashIndex + 1);
- }
+ // No value or null value.
+ return false;
+ }
- var dotIndex = value.IndexOf('.');
- if (dotIndex == -1)
- {
- // No dot.
- return false;
- }
+ // This is used both here and in NonFileNameRouteConstraint
+ // Any changes to this logic need to update the docs in those places.
+ internal static bool IsFileName(ReadOnlySpan<char> value)
+ {
+ if (value.Length == 0)
+ {
+ // Not a file name because empty.
+ return false;
+ }
- for (var i = dotIndex + 1; i < value.Length; i++)
- {
- if (value[i] != '.')
- {
- return true;
- }
- }
+ var lastSlashIndex = value.LastIndexOf('/');
+ if (lastSlashIndex >= 0)
+ {
+ value = value.Slice(lastSlashIndex + 1);
+ }
+ var dotIndex = value.IndexOf('.');
+ if (dotIndex == -1)
+ {
+ // No dot.
return false;
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ for (var i = dotIndex + 1; i < value.Length; i++)
{
- return IsFileName(literal);
+ if (value[i] != '.')
+ {
+ return true;
+ }
}
+
+ return false;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return IsFileName(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/FloatRouteConstraint.cs b/src/Http/Routing/src/Constraints/FloatRouteConstraint.cs
index 6cb7fc9b60..5e0b7a8722 100644
--- a/src/Http/Routing/src/Constraints/FloatRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/FloatRouteConstraint.cs
@@ -6,57 +6,56 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only 32-bit floating-point values.
+/// </summary>
+public class FloatRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only 32-bit floating-point values.
- /// </summary>
- public class FloatRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is float)
{
- if (value is float)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return float.TryParse(
- valueString,
- NumberStyles.Float | NumberStyles.AllowThousands,
- CultureInfo.InvariantCulture,
- out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return float.TryParse(
+ valueString,
+ NumberStyles.Float | NumberStyles.AllowThousands,
+ CultureInfo.InvariantCulture,
+ out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/GuidRouteConstraint.cs b/src/Http/Routing/src/Constraints/GuidRouteConstraint.cs
index 56678018cb..7abab4891e 100644
--- a/src/Http/Routing/src/Constraints/GuidRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/GuidRouteConstraint.cs
@@ -6,55 +6,54 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only <see cref="Guid"/> values.
+/// Matches values specified in any of the five formats "N", "D", "B", "P", or "X",
+/// supported by Guid.ToString(string) and Guid.ToString(String, IFormatProvider) methods.
+/// </summary>
+public class GuidRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only <see cref="Guid"/> values.
- /// Matches values specified in any of the five formats "N", "D", "B", "P", or "X",
- /// supported by Guid.ToString(string) and Guid.ToString(String, IFormatProvider) methods.
- /// </summary>
- public class GuidRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is Guid)
{
- if (value is Guid)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return Guid.TryParse(valueString, out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return Guid.TryParse(valueString, out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/HttpMethodRouteConstraint.cs b/src/Http/Routing/src/Constraints/HttpMethodRouteConstraint.cs
index 8c76a6172c..827947f7f0 100644
--- a/src/Http/Routing/src/Constraints/HttpMethodRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/HttpMethodRouteConstraint.cs
@@ -7,86 +7,85 @@ using System.Globalization;
using System.Linq;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains the HTTP method of request or a route.
+/// </summary>
+public class HttpMethodRouteConstraint : IRouteConstraint
{
/// <summary>
- /// Constrains the HTTP method of request or a route.
+ /// Creates a new instance of <see cref="HttpMethodRouteConstraint"/> that accepts the HTTP methods specified
+ /// by <paramref name="allowedMethods"/>.
/// </summary>
- public class HttpMethodRouteConstraint : IRouteConstraint
+ /// <param name="allowedMethods">The allowed HTTP methods.</param>
+ public HttpMethodRouteConstraint(params string[] allowedMethods)
{
- /// <summary>
- /// Creates a new instance of <see cref="HttpMethodRouteConstraint"/> that accepts the HTTP methods specified
- /// by <paramref name="allowedMethods"/>.
- /// </summary>
- /// <param name="allowedMethods">The allowed HTTP methods.</param>
- public HttpMethodRouteConstraint(params string[] allowedMethods)
+ if (allowedMethods == null)
{
- if (allowedMethods == null)
- {
- throw new ArgumentNullException(nameof(allowedMethods));
- }
-
- AllowedMethods = new List<string>(allowedMethods);
+ throw new ArgumentNullException(nameof(allowedMethods));
}
- /// <summary>
- /// Gets the HTTP methods allowed by the constraint.
- /// </summary>
- public IList<string> AllowedMethods { get; }
+ AllowedMethods = new List<string>(allowedMethods);
+ }
+
+ /// <summary>
+ /// Gets the HTTP methods allowed by the constraint.
+ /// </summary>
+ public IList<string> AllowedMethods { get; }
- /// <inheritdoc />
- public virtual bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ /// <inheritdoc />
+ public virtual bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- switch (routeDirection)
- {
- case RouteDirection.IncomingRequest:
- // Only required for constraining incoming requests
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
+ switch (routeDirection)
+ {
+ case RouteDirection.IncomingRequest:
+ // Only required for constraining incoming requests
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
- return AllowedMethods.Contains(httpContext.Request.Method, StringComparer.OrdinalIgnoreCase);
+ return AllowedMethods.Contains(httpContext.Request.Method, StringComparer.OrdinalIgnoreCase);
- case RouteDirection.UrlGeneration:
- // We need to see if the user specified the HTTP method explicitly. Consider these two routes:
- //
- // a) Route: template = "/{foo}", Constraints = { httpMethod = new HttpMethodRouteConstraint("GET") }
- // b) Route: template = "/{foo}", Constraints = { httpMethod = new HttpMethodRouteConstraint("POST") }
- //
- // A user might know ahead of time that a URI he/she is generating might be used with a particular HTTP
- // method. If a URI will be used for an HTTP POST but we match on (a) while generating the URI, then
- // the HTTP GET-specific route will be used for URI generation, which might have undesired behavior.
- //
- // To prevent this, a user might call GetVirtualPath(..., { httpMethod = "POST" }) to
- // signal that they are generating a URI that will be used for an HTTP POST, so they want the URI
- // generation to be performed by the (b) route instead of the (a) route, consistent with what would
- // happen on incoming requests.
- if (!values.TryGetValue(routeKey, out var obj))
- {
- return true;
- }
+ case RouteDirection.UrlGeneration:
+ // We need to see if the user specified the HTTP method explicitly. Consider these two routes:
+ //
+ // a) Route: template = "/{foo}", Constraints = { httpMethod = new HttpMethodRouteConstraint("GET") }
+ // b) Route: template = "/{foo}", Constraints = { httpMethod = new HttpMethodRouteConstraint("POST") }
+ //
+ // A user might know ahead of time that a URI he/she is generating might be used with a particular HTTP
+ // method. If a URI will be used for an HTTP POST but we match on (a) while generating the URI, then
+ // the HTTP GET-specific route will be used for URI generation, which might have undesired behavior.
+ //
+ // To prevent this, a user might call GetVirtualPath(..., { httpMethod = "POST" }) to
+ // signal that they are generating a URI that will be used for an HTTP POST, so they want the URI
+ // generation to be performed by the (b) route instead of the (a) route, consistent with what would
+ // happen on incoming requests.
+ if (!values.TryGetValue(routeKey, out var obj))
+ {
+ return true;
+ }
- return AllowedMethods.Contains(Convert.ToString(obj, CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase);
+ return AllowedMethods.Contains(Convert.ToString(obj, CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase);
- default:
- throw new ArgumentOutOfRangeException(nameof(routeDirection));
- }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(routeDirection));
}
}
}
diff --git a/src/Http/Routing/src/Constraints/IntRouteConstraint.cs b/src/Http/Routing/src/Constraints/IntRouteConstraint.cs
index 42d9ae707a..a26f8d1fc1 100644
--- a/src/Http/Routing/src/Constraints/IntRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/IntRouteConstraint.cs
@@ -6,53 +6,52 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only 32-bit integer values.
+/// </summary>
+public class IntRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only 32-bit integer values.
- /// </summary>
- public class IntRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is int)
{
- if (value is int)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return valueString is not null && CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return valueString is not null && CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string valueString)
- {
- return int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string valueString)
+ {
+ return int.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/LengthRouteConstraint.cs b/src/Http/Routing/src/Constraints/LengthRouteConstraint.cs
index 65dc364f0a..2946754e45 100644
--- a/src/Http/Routing/src/Constraints/LengthRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/LengthRouteConstraint.cs
@@ -6,106 +6,105 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to be a string of a given length or within a given range of lengths.
+/// </summary>
+public class LengthRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constrains a route parameter to be a string of a given length or within a given range of lengths.
+ /// Initializes a new instance of the <see cref="LengthRouteConstraint" /> class that constrains
+ /// a route parameter to be a string of a given length.
/// </summary>
- public class LengthRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="length">The length of the route parameter.</param>
+ public LengthRouteConstraint(int length)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="LengthRouteConstraint" /> class that constrains
- /// a route parameter to be a string of a given length.
- /// </summary>
- /// <param name="length">The length of the route parameter.</param>
- public LengthRouteConstraint(int length)
+ if (length < 0)
{
- if (length < 0)
- {
- var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
- throw new ArgumentOutOfRangeException(nameof(length), length, errorMessage);
- }
+ var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
+ throw new ArgumentOutOfRangeException(nameof(length), length, errorMessage);
+ }
+
+ MinLength = MaxLength = length;
+ }
- MinLength = MaxLength = length;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LengthRouteConstraint" /> class that constrains
+ /// a route parameter to be a string of a given length.
+ /// </summary>
+ /// <param name="minLength">The minimum length allowed for the route parameter.</param>
+ /// <param name="maxLength">The maximum length allowed for the route parameter.</param>
+ public LengthRouteConstraint(int minLength, int maxLength)
+ {
+ if (minLength < 0)
+ {
+ var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
+ throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
}
- /// <summary>
- /// Initializes a new instance of the <see cref="LengthRouteConstraint" /> class that constrains
- /// a route parameter to be a string of a given length.
- /// </summary>
- /// <param name="minLength">The minimum length allowed for the route parameter.</param>
- /// <param name="maxLength">The maximum length allowed for the route parameter.</param>
- public LengthRouteConstraint(int minLength, int maxLength)
+ if (maxLength < 0)
{
- if (minLength < 0)
- {
- var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
- throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
- }
-
- if (maxLength < 0)
- {
- var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
- throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, errorMessage);
- }
-
- if (minLength > maxLength)
- {
- var errorMessage =
- Resources.FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax("minLength", "maxLength");
- throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
- }
-
- MinLength = minLength;
- MaxLength = maxLength;
+ var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
+ throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, errorMessage);
}
- /// <summary>
- /// Gets the minimum length allowed for the route parameter.
- /// </summary>
- public int MinLength { get; }
-
- /// <summary>
- /// Gets the maximum length allowed for the route parameter.
- /// </summary>
- public int MaxLength { get; }
-
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (minLength > maxLength)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture)!;
- return CheckConstraintCore(valueString);
- }
-
- return false;
+ var errorMessage =
+ Resources.FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax("minLength", "maxLength");
+ throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
}
- private bool CheckConstraintCore(string valueString)
+ MinLength = minLength;
+ MaxLength = maxLength;
+ }
+
+ /// <summary>
+ /// Gets the minimum length allowed for the route parameter.
+ /// </summary>
+ public int MinLength { get; }
+
+ /// <summary>
+ /// Gets the maximum length allowed for the route parameter.
+ /// </summary>
+ public int MaxLength { get; }
+
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
{
- var length = valueString.Length;
- return length >= MinLength && length <= MaxLength;
+ throw new ArgumentNullException(nameof(routeKey));
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ if (values == null)
{
- return CheckConstraintCore(literal);
+ throw new ArgumentNullException(nameof(values));
}
+
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture)!;
+ return CheckConstraintCore(valueString);
+ }
+
+ return false;
+ }
+
+ private bool CheckConstraintCore(string valueString)
+ {
+ var length = valueString.Length;
+ return length >= MinLength && length <= MaxLength;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/LongRouteConstraint.cs b/src/Http/Routing/src/Constraints/LongRouteConstraint.cs
index 431240809e..2a146bd95e 100644
--- a/src/Http/Routing/src/Constraints/LongRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/LongRouteConstraint.cs
@@ -6,53 +6,52 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only 64-bit integer values.
+/// </summary>
+public class LongRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only 64-bit integer values.
- /// </summary>
- public class LongRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ if (value is long)
{
- if (value is long)
- {
- return true;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
+ return true;
}
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- private static bool CheckConstraintCore(string? valueString)
- {
- return long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out _);
- }
+ return false;
+ }
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
- {
- return CheckConstraintCore(literal);
- }
+ private static bool CheckConstraintCore(string? valueString)
+ {
+ return long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out _);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/MaxLengthRouteConstraint.cs b/src/Http/Routing/src/Constraints/MaxLengthRouteConstraint.cs
index 971627b96b..0d23f0ddce 100644
--- a/src/Http/Routing/src/Constraints/MaxLengthRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/MaxLengthRouteConstraint.cs
@@ -6,68 +6,67 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to be a string with a maximum length.
+/// </summary>
+public class MaxLengthRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constrains a route parameter to be a string with a maximum length.
+ /// Initializes a new instance of the <see cref="MaxLengthRouteConstraint" /> class.
/// </summary>
- public class MaxLengthRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="maxLength">The maximum length allowed for the route parameter.</param>
+ public MaxLengthRouteConstraint(int maxLength)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="MaxLengthRouteConstraint" /> class.
- /// </summary>
- /// <param name="maxLength">The maximum length allowed for the route parameter.</param>
- public MaxLengthRouteConstraint(int maxLength)
+ if (maxLength < 0)
{
- if (maxLength < 0)
- {
- var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
- throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, errorMessage);
- }
-
- MaxLength = maxLength;
+ var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
+ throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, errorMessage);
}
- /// <summary>
- /// Gets the maximum length allowed for the route parameter.
- /// </summary>
- public int MaxLength { get; }
-
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ MaxLength = maxLength;
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture)!;
- return CheckConstraintCore(valueString);
- }
+ /// <summary>
+ /// Gets the maximum length allowed for the route parameter.
+ /// </summary>
+ public int MaxLength { get; }
- return false;
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
+ {
+ throw new ArgumentNullException(nameof(routeKey));
}
- private bool CheckConstraintCore(string valueString)
+ if (values == null)
{
- return valueString.Length <= MaxLength;
+ throw new ArgumentNullException(nameof(values));
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
{
- return CheckConstraintCore(literal);
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture)!;
+ return CheckConstraintCore(valueString);
}
+
+ return false;
+ }
+
+ private bool CheckConstraintCore(string valueString)
+ {
+ return valueString.Length <= MaxLength;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/MaxRouteConstraint.cs b/src/Http/Routing/src/Constraints/MaxRouteConstraint.cs
index 8c05a1b7fa..ede450dce7 100644
--- a/src/Http/Routing/src/Constraints/MaxRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/MaxRouteConstraint.cs
@@ -6,66 +6,65 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to be an integer with a maximum value.
+/// </summary>
+public class MaxRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constrains a route parameter to be an integer with a maximum value.
+ /// Initializes a new instance of the <see cref="MaxRouteConstraint" /> class.
/// </summary>
- public class MaxRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="max">The maximum value allowed for the route parameter.</param>
+ public MaxRouteConstraint(long max)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="MaxRouteConstraint" /> class.
- /// </summary>
- /// <param name="max">The maximum value allowed for the route parameter.</param>
- public MaxRouteConstraint(long max)
- {
- Max = max;
- }
+ Max = max;
+ }
- /// <summary>
- /// Gets the maximum allowed value of the route parameter.
- /// </summary>
- public long Max { get; private set; }
+ /// <summary>
+ /// Gets the maximum allowed value of the route parameter.
+ /// </summary>
+ public long Max { get; private set; }
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- return false;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- private bool CheckConstraintCore(string? valueString)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
{
- if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
- {
- return longValue <= Max;
- }
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ return false;
+ }
+
+ private bool CheckConstraintCore(string? valueString)
+ {
+ if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
{
- return CheckConstraintCore(literal);
+ return longValue <= Max;
}
+ return false;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/MinLengthRouteConstraint.cs b/src/Http/Routing/src/Constraints/MinLengthRouteConstraint.cs
index 7cb782d6d5..ecb4b09f8f 100644
--- a/src/Http/Routing/src/Constraints/MinLengthRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/MinLengthRouteConstraint.cs
@@ -6,68 +6,67 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to be a string with a minimum length.
+/// </summary>
+public class MinLengthRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constrains a route parameter to be a string with a minimum length.
+ /// Initializes a new instance of the <see cref="MinLengthRouteConstraint" /> class.
/// </summary>
- public class MinLengthRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="minLength">The minimum length allowed for the route parameter.</param>
+ public MinLengthRouteConstraint(int minLength)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="MinLengthRouteConstraint" /> class.
- /// </summary>
- /// <param name="minLength">The minimum length allowed for the route parameter.</param>
- public MinLengthRouteConstraint(int minLength)
+ if (minLength < 0)
{
- if (minLength < 0)
- {
- var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
- throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
- }
-
- MinLength = minLength;
+ var errorMessage = Resources.FormatArgumentMustBeGreaterThanOrEqualTo(0);
+ throw new ArgumentOutOfRangeException(nameof(minLength), minLength, errorMessage);
}
- /// <summary>
- /// Gets the minimum length allowed for the route parameter.
- /// </summary>
- public int MinLength { get; private set; }
-
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ MinLength = minLength;
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture)!;
- return CheckConstraintCore(valueString);
- }
+ /// <summary>
+ /// Gets the minimum length allowed for the route parameter.
+ /// </summary>
+ public int MinLength { get; private set; }
- return false;
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
+ {
+ throw new ArgumentNullException(nameof(routeKey));
}
- private bool CheckConstraintCore(string valueString)
+ if (values == null)
{
- return valueString.Length >= MinLength;
+ throw new ArgumentNullException(nameof(values));
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
{
- return CheckConstraintCore(literal);
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture)!;
+ return CheckConstraintCore(valueString);
}
+
+ return false;
+ }
+
+ private bool CheckConstraintCore(string valueString)
+ {
+ return valueString.Length >= MinLength;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/MinRouteConstraint.cs b/src/Http/Routing/src/Constraints/MinRouteConstraint.cs
index 1b45b93233..e7438ee22c 100644
--- a/src/Http/Routing/src/Constraints/MinRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/MinRouteConstraint.cs
@@ -6,66 +6,65 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to be a long with a minimum value.
+/// </summary>
+public class MinRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constrains a route parameter to be a long with a minimum value.
+ /// Initializes a new instance of the <see cref="MinRouteConstraint" /> class.
/// </summary>
- public class MinRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="min">The minimum value allowed for the route parameter.</param>
+ public MinRouteConstraint(long min)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="MinRouteConstraint" /> class.
- /// </summary>
- /// <param name="min">The minimum value allowed for the route parameter.</param>
- public MinRouteConstraint(long min)
- {
- Min = min;
- }
+ Min = min;
+ }
- /// <summary>
- /// Gets the minimum allowed value of the route parameter.
- /// </summary>
- public long Min { get; }
+ /// <summary>
+ /// Gets the minimum allowed value of the route parameter.
+ /// </summary>
+ public long Min { get; }
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- return false;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- private bool CheckConstraintCore(string? valueString)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
{
- if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
- {
- return longValue >= Min;
- }
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ return false;
+ }
+
+ private bool CheckConstraintCore(string? valueString)
+ {
+ if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
{
- return CheckConstraintCore(literal);
+ return longValue >= Min;
}
+ return false;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/NonFileNameRouteConstraint.cs b/src/Http/Routing/src/Constraints/NonFileNameRouteConstraint.cs
index 5bc0d8121e..0ebc693f1c 100644
--- a/src/Http/Routing/src/Constraints/NonFileNameRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/NonFileNameRouteConstraint.cs
@@ -6,115 +6,114 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to represent only non-file-name values. Does not validate that
+/// the route value contains valid file system characters, or that the value represents
+/// an actual file on disk.
+/// </summary>
+/// <remarks>
+/// <para>
+/// This constraint can be used to disambiguate requests for dynamic content versus
+/// static files served from the application.
+/// </para>
+/// <para>
+/// This constraint determines whether a route value represents a file name by examining
+/// the last URL Path segment of the value (delimited by <c>/</c>). The last segment
+/// must contain the dot (<c>.</c>) character followed by one or more non-(<c>.</c>) characters.
+/// </para>
+/// <para>
+/// If the route value does not contain a <c>/</c> then the entire value will be interpreted
+/// as a the last segment.
+/// </para>
+/// <para>
+/// The <see cref="NonFileNameRouteConstraint"/> does not attempt to validate that the value contains
+/// a legal file name for the current operating system.
+/// </para>
+/// <para>
+/// <list type="bullet">
+/// <listheader>
+/// <term>Examples of route values that will be matched as non-file-names</term>
+/// <description>description</description>
+/// </listheader>
+/// <item>
+/// <term><c>/a/b/c</c></term>
+/// <description>Final segment does not contain a <c>.</c>.</description>
+/// </item>
+/// <item>
+/// <term><c>/a/b.d/c</c></term>
+/// <description>Final segment does not contain a <c>.</c>.</description>
+/// </item>
+/// <item>
+/// <term><c>/a/b.d/c/</c></term>
+/// <description>Final segment is empty.</description>
+/// </item>
+/// <item>
+/// <term><c></c></term>
+/// <description>Value is empty</description>
+/// </item>
+/// </list>
+/// <list type="bullet">
+/// <listheader>
+/// <term>Examples of route values that will be rejected as file names</term>
+/// <description>description</description>
+/// </listheader>
+/// <item>
+/// <term><c>/a/b/c.txt</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// <item>
+/// <term><c>/hello.world.txt</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// <item>
+/// <term><c>hello.world.txt</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// <item>
+/// <term><c>.gitignore</c></term>
+/// <description>Final segment contains a <c>.</c> followed by other characters.</description>
+/// </item>
+/// </list>
+/// </para>
+/// </remarks>
+public class NonFileNameRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
- /// <summary>
- /// Constrains a route parameter to represent only non-file-name values. Does not validate that
- /// the route value contains valid file system characters, or that the value represents
- /// an actual file on disk.
- /// </summary>
- /// <remarks>
- /// <para>
- /// This constraint can be used to disambiguate requests for dynamic content versus
- /// static files served from the application.
- /// </para>
- /// <para>
- /// This constraint determines whether a route value represents a file name by examining
- /// the last URL Path segment of the value (delimited by <c>/</c>). The last segment
- /// must contain the dot (<c>.</c>) character followed by one or more non-(<c>.</c>) characters.
- /// </para>
- /// <para>
- /// If the route value does not contain a <c>/</c> then the entire value will be interpreted
- /// as a the last segment.
- /// </para>
- /// <para>
- /// The <see cref="NonFileNameRouteConstraint"/> does not attempt to validate that the value contains
- /// a legal file name for the current operating system.
- /// </para>
- /// <para>
- /// <list type="bullet">
- /// <listheader>
- /// <term>Examples of route values that will be matched as non-file-names</term>
- /// <description>description</description>
- /// </listheader>
- /// <item>
- /// <term><c>/a/b/c</c></term>
- /// <description>Final segment does not contain a <c>.</c>.</description>
- /// </item>
- /// <item>
- /// <term><c>/a/b.d/c</c></term>
- /// <description>Final segment does not contain a <c>.</c>.</description>
- /// </item>
- /// <item>
- /// <term><c>/a/b.d/c/</c></term>
- /// <description>Final segment is empty.</description>
- /// </item>
- /// <item>
- /// <term><c></c></term>
- /// <description>Value is empty</description>
- /// </item>
- /// </list>
- /// <list type="bullet">
- /// <listheader>
- /// <term>Examples of route values that will be rejected as file names</term>
- /// <description>description</description>
- /// </listheader>
- /// <item>
- /// <term><c>/a/b/c.txt</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// <item>
- /// <term><c>/hello.world.txt</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// <item>
- /// <term><c>hello.world.txt</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// <item>
- /// <term><c>.gitignore</c></term>
- /// <description>Final segment contains a <c>.</c> followed by other characters.</description>
- /// </item>
- /// </list>
- /// </para>
- /// </remarks>
- public class NonFileNameRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var obj) && obj != null)
- {
- var value = Convert.ToString(obj, CultureInfo.InvariantCulture);
- return !FileNameRouteConstraint.IsFileName(value);
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- // No value or null value.
- //
- // We want to return true here because the core use-case of the constraint is to *exclude*
- // things that look like file names. There's nothing here that looks like a file name, so
- // let it through.
- return true;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ if (values.TryGetValue(routeKey, out var obj) && obj != null)
{
- return !FileNameRouteConstraint.IsFileName(literal);
+ var value = Convert.ToString(obj, CultureInfo.InvariantCulture);
+ return !FileNameRouteConstraint.IsFileName(value);
}
+
+ // No value or null value.
+ //
+ // We want to return true here because the core use-case of the constraint is to *exclude*
+ // things that look like file names. There's nothing here that looks like a file name, so
+ // let it through.
+ return true;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return !FileNameRouteConstraint.IsFileName(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/NullRouteConstraint.cs b/src/Http/Routing/src/Constraints/NullRouteConstraint.cs
index 1ec7e77f2b..8e80ad4c4b 100644
--- a/src/Http/Routing/src/Constraints/NullRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/NullRouteConstraint.cs
@@ -3,19 +3,18 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+internal class NullRouteConstraint : IRouteConstraint
{
- internal class NullRouteConstraint : IRouteConstraint
- {
- public static readonly NullRouteConstraint Instance = new NullRouteConstraint();
+ public static readonly NullRouteConstraint Instance = new NullRouteConstraint();
- private NullRouteConstraint()
- {
- }
+ private NullRouteConstraint()
+ {
+ }
- public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
- {
- return true;
- }
+ public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
+ {
+ return true;
}
}
diff --git a/src/Http/Routing/src/Constraints/OptionalRouteConstraint.cs b/src/Http/Routing/src/Constraints/OptionalRouteConstraint.cs
index 29170be117..799ae4d316 100644
--- a/src/Http/Routing/src/Constraints/OptionalRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/OptionalRouteConstraint.cs
@@ -4,60 +4,59 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint.
+/// </summary>
+public class OptionalRouteConstraint : IRouteConstraint
{
/// <summary>
- /// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint.
+ /// Creates a new <see cref="OptionalRouteConstraint"/> instance given the <paramref name="innerConstraint"/>.
+ /// </summary>
+ /// <param name="innerConstraint"></param>
+ public OptionalRouteConstraint(IRouteConstraint innerConstraint)
+ {
+ if (innerConstraint == null)
+ {
+ throw new ArgumentNullException(nameof(innerConstraint));
+ }
+
+ InnerConstraint = innerConstraint;
+ }
+
+ /// <summary>
+ /// Gets the <see cref="IRouteConstraint"/> associated with the optional parameter.
/// </summary>
- public class OptionalRouteConstraint : IRouteConstraint
+ public IRouteConstraint InnerConstraint { get; }
+
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <summary>
- /// Creates a new <see cref="OptionalRouteConstraint"/> instance given the <paramref name="innerConstraint"/>.
- /// </summary>
- /// <param name="innerConstraint"></param>
- public OptionalRouteConstraint(IRouteConstraint innerConstraint)
+ if (routeKey == null)
{
- if (innerConstraint == null)
- {
- throw new ArgumentNullException(nameof(innerConstraint));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- InnerConstraint = innerConstraint;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- /// <summary>
- /// Gets the <see cref="IRouteConstraint"/> associated with the optional parameter.
- /// </summary>
- public IRouteConstraint InnerConstraint { get; }
-
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (values.TryGetValue(routeKey, out var value))
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var value))
- {
- return InnerConstraint.Match(httpContext,
- route,
- routeKey,
- values,
- routeDirection);
- }
-
- return true;
+ return InnerConstraint.Match(httpContext,
+ route,
+ routeKey,
+ values,
+ routeDirection);
}
+
+ return true;
}
}
diff --git a/src/Http/Routing/src/Constraints/RangeRouteConstraint.cs b/src/Http/Routing/src/Constraints/RangeRouteConstraint.cs
index 0f8012e539..01004df0f4 100644
--- a/src/Http/Routing/src/Constraints/RangeRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/RangeRouteConstraint.cs
@@ -6,80 +6,79 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constraints a route parameter to be an integer within a given range of values.
+/// </summary>
+public class RangeRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
/// <summary>
- /// Constraints a route parameter to be an integer within a given range of values.
+ /// Initializes a new instance of the <see cref="RangeRouteConstraint" /> class.
/// </summary>
- public class RangeRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="min">The minimum value.</param>
+ /// <param name="max">The maximum value.</param>
+ /// <remarks>The minimum value should be less than or equal to the maximum value.</remarks>
+ public RangeRouteConstraint(long min, long max)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="RangeRouteConstraint" /> class.
- /// </summary>
- /// <param name="min">The minimum value.</param>
- /// <param name="max">The maximum value.</param>
- /// <remarks>The minimum value should be less than or equal to the maximum value.</remarks>
- public RangeRouteConstraint(long min, long max)
+ if (min > max)
{
- if (min > max)
- {
- var errorMessage = Resources.FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax("min", "max");
- throw new ArgumentOutOfRangeException(nameof(min), min, errorMessage);
- }
-
- Min = min;
- Max = max;
+ var errorMessage = Resources.FormatRangeConstraint_MinShouldBeLessThanOrEqualToMax("min", "max");
+ throw new ArgumentOutOfRangeException(nameof(min), min, errorMessage);
}
- /// <summary>
- /// Gets the minimum allowed value of the route parameter.
- /// </summary>
- public long Min { get; private set; }
-
- /// <summary>
- /// Gets the maximum allowed value of the route parameter.
- /// </summary>
- public long Max { get; private set; }
+ Min = min;
+ Max = max;
+ }
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ /// <summary>
+ /// Gets the minimum allowed value of the route parameter.
+ /// </summary>
+ public long Min { get; private set; }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ /// <summary>
+ /// Gets the maximum allowed value of the route parameter.
+ /// </summary>
+ public long Max { get; private set; }
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return CheckConstraintCore(valueString);
- }
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
+ {
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- return false;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- private bool CheckConstraintCore(string? valueString)
+ if (values.TryGetValue(routeKey, out var value) && value != null)
{
- if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
- {
- return longValue >= Min && longValue <= Max;
- }
- return false;
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return CheckConstraintCore(valueString);
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ return false;
+ }
+
+ private bool CheckConstraintCore(string? valueString)
+ {
+ if (long.TryParse(valueString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
{
- return CheckConstraintCore(literal);
+ return longValue >= Min && longValue <= Max;
}
+ return false;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/RegexInlineRouteConstraint.cs b/src/Http/Routing/src/Constraints/RegexInlineRouteConstraint.cs
index e0e82c3f66..465e0a9f9c 100644
--- a/src/Http/Routing/src/Constraints/RegexInlineRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/RegexInlineRouteConstraint.cs
@@ -1,20 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Represents a regex constraint which can be used as an inlineConstraint.
+/// </summary>
+public class RegexInlineRouteConstraint : RegexRouteConstraint
{
/// <summary>
- /// Represents a regex constraint which can be used as an inlineConstraint.
+ /// Initializes a new instance of the <see cref="RegexInlineRouteConstraint" /> class.
/// </summary>
- public class RegexInlineRouteConstraint : RegexRouteConstraint
+ /// <param name="regexPattern">The regular expression pattern to match.</param>
+ public RegexInlineRouteConstraint(string regexPattern)
+ : base(regexPattern)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="RegexInlineRouteConstraint" /> class.
- /// </summary>
- /// <param name="regexPattern">The regular expression pattern to match.</param>
- public RegexInlineRouteConstraint(string regexPattern)
- : base(regexPattern)
- {
- }
}
}
diff --git a/src/Http/Routing/src/Constraints/RegexRouteConstraint.cs b/src/Http/Routing/src/Constraints/RegexRouteConstraint.cs
index 54f78a08fd..de7f28961d 100644
--- a/src/Http/Routing/src/Constraints/RegexRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/RegexRouteConstraint.cs
@@ -7,83 +7,82 @@ using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to match a regular expression.
+/// </summary>
+public class RegexRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
+ private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
+
/// <summary>
- /// Constrains a route parameter to match a regular expression.
+ /// Constructor for a <see cref="RegexRouteConstraint"/> given a <paramref name="regex"/>.
/// </summary>
- public class RegexRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="regex">A <see cref="Regex"/> instance to use as a constraint.</param>
+ public RegexRouteConstraint(Regex regex)
{
- private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
-
- /// <summary>
- /// Constructor for a <see cref="RegexRouteConstraint"/> given a <paramref name="regex"/>.
- /// </summary>
- /// <param name="regex">A <see cref="Regex"/> instance to use as a constraint.</param>
- public RegexRouteConstraint(Regex regex)
+ if (regex == null)
{
- if (regex == null)
- {
- throw new ArgumentNullException(nameof(regex));
- }
-
- Constraint = regex;
+ throw new ArgumentNullException(nameof(regex));
}
- /// <summary>
- /// Constructor for a <see cref="RegexRouteConstraint"/> given a <paramref name="regexPattern"/>.
- /// </summary>
- /// <param name="regexPattern">A string containing the regex pattern.</param>
- public RegexRouteConstraint(string regexPattern)
- {
- if (regexPattern == null)
- {
- throw new ArgumentNullException(nameof(regexPattern));
- }
-
- Constraint = new Regex(
- regexPattern,
- RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
- RegexMatchTimeout);
- }
-
- /// <summary>
- /// Gets the regular expression used in the route constraint.
- /// </summary>
- public Regex Constraint { get; private set; }
+ Constraint = regex;
+ }
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ /// <summary>
+ /// Constructor for a <see cref="RegexRouteConstraint"/> given a <paramref name="regexPattern"/>.
+ /// </summary>
+ /// <param name="regexPattern">A string containing the regex pattern.</param>
+ public RegexRouteConstraint(string regexPattern)
+ {
+ if (regexPattern == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
+ throw new ArgumentNullException(nameof(regexPattern));
+ }
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ Constraint = new Regex(
+ regexPattern,
+ RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
+ RegexMatchTimeout);
+ }
- if (values.TryGetValue(routeKey, out var routeValue)
- && routeValue != null)
- {
- var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture)!;
+ /// <summary>
+ /// Gets the regular expression used in the route constraint.
+ /// </summary>
+ public Regex Constraint { get; private set; }
- return Constraint.IsMatch(parameterValueString);
- }
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ if (routeKey == null)
+ {
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- return false;
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ if (values.TryGetValue(routeKey, out var routeValue)
+ && routeValue != null)
{
- return Constraint.IsMatch(literal);
+ var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture)!;
+
+ return Constraint.IsMatch(parameterValueString);
}
+
+ return false;
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return Constraint.IsMatch(literal);
}
}
diff --git a/src/Http/Routing/src/Constraints/RequiredRouteConstraint.cs b/src/Http/Routing/src/Constraints/RequiredRouteConstraint.cs
index 79423bffc9..d53d4a4c5c 100644
--- a/src/Http/Routing/src/Constraints/RequiredRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/RequiredRouteConstraint.cs
@@ -5,43 +5,42 @@ using System;
using System.Globalization;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constraints a route parameter that must have a value.
+/// </summary>
+/// <remarks>
+/// This constraint is primarily used to enforce that a non-parameter value is present during
+/// URL generation.
+/// </remarks>
+public class RequiredRouteConstraint : IRouteConstraint
{
- /// <summary>
- /// Constraints a route parameter that must have a value.
- /// </summary>
- /// <remarks>
- /// This constraint is primarily used to enforce that a non-parameter value is present during
- /// URL generation.
- /// </remarks>
- public class RequiredRouteConstraint : IRouteConstraint
+ /// <inheritdoc />
+ public bool Match(
+ HttpContext? httpContext,
+ IRouter? route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- /// <inheritdoc />
- public bool Match(
- HttpContext? httpContext,
- IRouter? route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
+ if (routeKey == null)
{
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
+ throw new ArgumentNullException(nameof(routeKey));
+ }
- if (values.TryGetValue(routeKey, out var value) && value != null)
- {
- // In routing the empty string is equivalent to null, which is equivalent to an unset value.
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- return !string.IsNullOrEmpty(valueString);
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- return false;
+ if (values.TryGetValue(routeKey, out var value) && value != null)
+ {
+ // In routing the empty string is equivalent to null, which is equivalent to an unset value.
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ return !string.IsNullOrEmpty(valueString);
}
+
+ return false;
}
}
diff --git a/src/Http/Routing/src/Constraints/StringRouteConstraint.cs b/src/Http/Routing/src/Constraints/StringRouteConstraint.cs
index c638283274..60afff66cd 100644
--- a/src/Http/Routing/src/Constraints/StringRouteConstraint.cs
+++ b/src/Http/Routing/src/Constraints/StringRouteConstraint.cs
@@ -6,60 +6,59 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+/// <summary>
+/// Constrains a route parameter to contain only a specified string.
+/// </summary>
+public class StringRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
{
+ private readonly string _value;
+
/// <summary>
- /// Constrains a route parameter to contain only a specified string.
+ /// Initializes a new instance of the <see cref="StringRouteConstraint"/> class.
/// </summary>
- public class StringRouteConstraint : IRouteConstraint, IParameterLiteralNodeMatchingPolicy
+ /// <param name="value">The constraint value to match.</param>
+ public StringRouteConstraint(string value)
{
- private readonly string _value;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="StringRouteConstraint"/> class.
- /// </summary>
- /// <param name="value">The constraint value to match.</param>
- public StringRouteConstraint(string value)
+ if (value == null)
{
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- _value = value;
+ throw new ArgumentNullException(nameof(value));
}
- /// <inheritdoc />
- public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
- {
- if (routeKey == null)
- {
- throw new ArgumentNullException(nameof(routeKey));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (values.TryGetValue(routeKey, out var routeValue)
- && routeValue != null)
- {
- var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture)!;
- return CheckConstraintCore(parameterValueString);
- }
+ _value = value;
+ }
- return false;
+ /// <inheritdoc />
+ public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
+ {
+ if (routeKey == null)
+ {
+ throw new ArgumentNullException(nameof(routeKey));
}
- private bool CheckConstraintCore(string parameterValueString)
+ if (values == null)
{
- return parameterValueString.Equals(_value, StringComparison.OrdinalIgnoreCase);
+ throw new ArgumentNullException(nameof(values));
}
- bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ if (values.TryGetValue(routeKey, out var routeValue)
+ && routeValue != null)
{
- return CheckConstraintCore(literal);
+ var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture)!;
+ return CheckConstraintCore(parameterValueString);
}
+
+ return false;
+ }
+
+ private bool CheckConstraintCore(string parameterValueString)
+ {
+ return parameterValueString.Equals(_value, StringComparison.OrdinalIgnoreCase);
+ }
+
+ bool IParameterLiteralNodeMatchingPolicy.MatchesLiteral(string parameterName, string literal)
+ {
+ return CheckConstraintCore(literal);
}
}
diff --git a/src/Http/Routing/src/DataSourceDependentCache.cs b/src/Http/Routing/src/DataSourceDependentCache.cs
index a6241036f8..3c56316b92 100644
--- a/src/Http/Routing/src/DataSourceDependentCache.cs
+++ b/src/Http/Routing/src/DataSourceDependentCache.cs
@@ -9,88 +9,87 @@ using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// FYI: This class is also linked into MVC. If you make changes to the API you will
+// also need to change MVC's usage.
+internal sealed class DataSourceDependentCache<T> : IDisposable where T : class
{
- // FYI: This class is also linked into MVC. If you make changes to the API you will
- // also need to change MVC's usage.
- internal sealed class DataSourceDependentCache<T> : IDisposable where T : class
- {
- private readonly EndpointDataSource _dataSource;
- private readonly Func<IReadOnlyList<Endpoint>, T> _initializeCore;
- private readonly Func<T> _initializer;
- private readonly Action<object?> _initializerWithState;
+ private readonly EndpointDataSource _dataSource;
+ private readonly Func<IReadOnlyList<Endpoint>, T> _initializeCore;
+ private readonly Func<T> _initializer;
+ private readonly Action<object?> _initializerWithState;
- private object _lock;
- private bool _initialized;
- private T? _value;
+ private object _lock;
+ private bool _initialized;
+ private T? _value;
- private IDisposable? _disposable;
- private bool _disposed;
+ private IDisposable? _disposable;
+ private bool _disposed;
- public DataSourceDependentCache(EndpointDataSource dataSource, Func<IReadOnlyList<Endpoint>, T> initialize)
+ public DataSourceDependentCache(EndpointDataSource dataSource, Func<IReadOnlyList<Endpoint>, T> initialize)
+ {
+ if (dataSource == null)
{
- if (dataSource == null)
- {
- throw new ArgumentNullException(nameof(dataSource));
- }
+ throw new ArgumentNullException(nameof(dataSource));
+ }
- if (initialize == null)
- {
- throw new ArgumentNullException(nameof(initialize));
- }
+ if (initialize == null)
+ {
+ throw new ArgumentNullException(nameof(initialize));
+ }
- _dataSource = dataSource;
- _initializeCore = initialize;
+ _dataSource = dataSource;
+ _initializeCore = initialize;
- _initializer = Initialize;
- _initializerWithState = (state) => Initialize();
- _lock = new object();
- }
+ _initializer = Initialize;
+ _initializerWithState = (state) => Initialize();
+ _lock = new object();
+ }
- // Note that we don't lock here, and think about that in the context of a 'push'. So when data gets 'pushed'
- // we start computing a new state, but we're still able to perform operations on the old state until we've
- // processed the update.
- [NotNullIfNotNull(nameof(_value))]
- public T? Value => _value;
+ // Note that we don't lock here, and think about that in the context of a 'push'. So when data gets 'pushed'
+ // we start computing a new state, but we're still able to perform operations on the old state until we've
+ // processed the update.
+ [NotNullIfNotNull(nameof(_value))]
+ public T? Value => _value;
- [MemberNotNull(nameof(_value))]
- public T EnsureInitialized()
- {
- return LazyInitializer.EnsureInitialized<T>(ref _value, ref _initialized, ref _lock, _initializer);
- }
+ [MemberNotNull(nameof(_value))]
+ public T EnsureInitialized()
+ {
+ return LazyInitializer.EnsureInitialized<T>(ref _value, ref _initialized, ref _lock, _initializer);
+ }
- private T Initialize()
+ private T Initialize()
+ {
+ lock (_lock)
{
- lock (_lock)
- {
- var changeToken = _dataSource.GetChangeToken();
- _value = _initializeCore(_dataSource.Endpoints);
+ var changeToken = _dataSource.GetChangeToken();
+ _value = _initializeCore(_dataSource.Endpoints);
- // Don't resubscribe if we're already disposed.
- if (_disposed)
- {
- return _value;
- }
-
- _disposable = changeToken.RegisterChangeCallback(_initializerWithState, null);
+ // Don't resubscribe if we're already disposed.
+ if (_disposed)
+ {
return _value;
}
+
+ _disposable = changeToken.RegisterChangeCallback(_initializerWithState, null);
+ return _value;
}
+ }
- public void Dispose()
+ public void Dispose()
+ {
+ lock (_lock)
{
- lock (_lock)
+ if (!_disposed)
{
- if (!_disposed)
- {
- _disposable?.Dispose();
- _disposable = null;
-
- // Tracking whether we're disposed or not prevents a race-condition
- // between disposal and Initialize(). If a change callback fires after
- // we dispose, then we don't want to reregister.
- _disposed = true;
- }
+ _disposable?.Dispose();
+ _disposable = null;
+
+ // Tracking whether we're disposed or not prevents a race-condition
+ // between disposal and Initialize(). If a change callback fires after
+ // we dispose, then we don't want to reregister.
+ _disposed = true;
}
}
}
diff --git a/src/Http/Routing/src/DataTokensMetadata.cs b/src/Http/Routing/src/DataTokensMetadata.cs
index a0568f35ae..6859473704 100644
--- a/src/Http/Routing/src/DataTokensMetadata.cs
+++ b/src/Http/Routing/src/DataTokensMetadata.cs
@@ -7,27 +7,26 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Metadata that defines data tokens for an <see cref="Endpoint"/>. This metadata
+/// type provides data tokens value for <see cref="RouteData.DataTokens"/> associated
+/// with an endpoint.
+/// </summary>
+public sealed class DataTokensMetadata : IDataTokensMetadata
{
/// <summary>
- /// Metadata that defines data tokens for an <see cref="Endpoint"/>. This metadata
- /// type provides data tokens value for <see cref="RouteData.DataTokens"/> associated
- /// with an endpoint.
+ /// Constructor for a new <see cref="DataTokensMetadata"/> given <paramref name="dataTokens"/>.
/// </summary>
- public sealed class DataTokensMetadata : IDataTokensMetadata
+ /// <param name="dataTokens">The data tokens.</param>
+ public DataTokensMetadata(IReadOnlyDictionary<string, object?> dataTokens)
{
- /// <summary>
- /// Constructor for a new <see cref="DataTokensMetadata"/> given <paramref name="dataTokens"/>.
- /// </summary>
- /// <param name="dataTokens">The data tokens.</param>
- public DataTokensMetadata(IReadOnlyDictionary<string, object?> dataTokens)
- {
- DataTokens = dataTokens ?? throw new ArgumentNullException(nameof(dataTokens));
- }
-
- /// <summary>
- /// Get the data tokens.
- /// </summary>
- public IReadOnlyDictionary<string, object?> DataTokens { get; }
+ DataTokens = dataTokens ?? throw new ArgumentNullException(nameof(dataTokens));
}
+
+ /// <summary>
+ /// Get the data tokens.
+ /// </summary>
+ public IReadOnlyDictionary<string, object?> DataTokens { get; }
}
diff --git a/src/Http/Routing/src/DecisionTree/DecisionCriterion.cs b/src/Http/Routing/src/DecisionTree/DecisionCriterion.cs
index 88619b0739..8056288463 100644
--- a/src/Http/Routing/src/DecisionTree/DecisionCriterion.cs
+++ b/src/Http/Routing/src/DecisionTree/DecisionCriterion.cs
@@ -5,12 +5,11 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+internal class DecisionCriterion<TItem>
{
- internal class DecisionCriterion<TItem>
- {
- public string Key { get; set; }
+ public string Key { get; set; }
- public Dictionary<object, DecisionTreeNode<TItem>> Branches { get; set; }
- }
+ public Dictionary<object, DecisionTreeNode<TItem>> Branches { get; set; }
}
diff --git a/src/Http/Routing/src/DecisionTree/DecisionCriterionValue.cs b/src/Http/Routing/src/DecisionTree/DecisionCriterionValue.cs
index c58fbb564a..52061cd760 100644
--- a/src/Http/Routing/src/DecisionTree/DecisionCriterionValue.cs
+++ b/src/Http/Routing/src/DecisionTree/DecisionCriterionValue.cs
@@ -1,20 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+internal readonly struct DecisionCriterionValue
{
- internal readonly struct DecisionCriterionValue
- {
- private readonly object _value;
+ private readonly object _value;
- public DecisionCriterionValue(object value)
- {
- _value = value;
- }
+ public DecisionCriterionValue(object value)
+ {
+ _value = value;
+ }
- public object Value
- {
- get { return _value; }
- }
+ public object Value
+ {
+ get { return _value; }
}
}
diff --git a/src/Http/Routing/src/DecisionTree/DecisionCriterionValueEqualityComparer.cs b/src/Http/Routing/src/DecisionTree/DecisionCriterionValueEqualityComparer.cs
index 054115badf..0e4d2cea88 100644
--- a/src/Http/Routing/src/DecisionTree/DecisionCriterionValueEqualityComparer.cs
+++ b/src/Http/Routing/src/DecisionTree/DecisionCriterionValueEqualityComparer.cs
@@ -3,25 +3,24 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+internal class DecisionCriterionValueEqualityComparer : IEqualityComparer<DecisionCriterionValue>
{
- internal class DecisionCriterionValueEqualityComparer : IEqualityComparer<DecisionCriterionValue>
+ public DecisionCriterionValueEqualityComparer(IEqualityComparer<object> innerComparer)
{
- public DecisionCriterionValueEqualityComparer(IEqualityComparer<object> innerComparer)
- {
- InnerComparer = innerComparer;
- }
+ InnerComparer = innerComparer;
+ }
- public IEqualityComparer<object> InnerComparer { get; private set; }
+ public IEqualityComparer<object> InnerComparer { get; private set; }
- public bool Equals(DecisionCriterionValue x, DecisionCriterionValue y)
- {
- return InnerComparer.Equals(x.Value, y.Value);
- }
+ public bool Equals(DecisionCriterionValue x, DecisionCriterionValue y)
+ {
+ return InnerComparer.Equals(x.Value, y.Value);
+ }
- public int GetHashCode(DecisionCriterionValue obj)
- {
- return InnerComparer.GetHashCode(obj.Value);
- }
+ public int GetHashCode(DecisionCriterionValue obj)
+ {
+ return InnerComparer.GetHashCode(obj.Value);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/src/DecisionTree/DecisionTreeBuilder.cs b/src/Http/Routing/src/DecisionTree/DecisionTreeBuilder.cs
index 20229ed733..86aac66162 100644
--- a/src/Http/Routing/src/DecisionTree/DecisionTreeBuilder.cs
+++ b/src/Http/Routing/src/DecisionTree/DecisionTreeBuilder.cs
@@ -5,222 +5,221 @@ using System;
using System.Collections.Generic;
using System.Linq;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+// This code generates a minimal tree of decision criteria that map known categorical data
+// (key-value-pairs) to a set of inputs. Action Selection is the best example of how this
+// can be used, so the comments here will describe the process from the point-of-view,
+// though the decision tree is generally applicable to like-problems.
+//
+// Care has been taken here to keep the performance of building the data-structure at a
+// reasonable level, as this has an impact on startup cost for action selection. Additionally
+// we want to hold on to the minimal amount of memory needed once we've built the tree.
+//
+// Ex:
+// Given actions like the following, create a decision tree that will help action
+// selection work efficiently.
+//
+// Given any set of route data it should be possible to traverse the tree using the
+// presence our route data keys (like action), and whether or not they match any of
+// the known values for that route data key, to find the set of actions that match
+// the route data.
+//
+// Actions:
+//
+// { controller = "Home", action = "Index" }
+// { controller = "Products", action = "Index" }
+// { controller = "Products", action = "Buy" }
+// { area = "Admin", controller = "Users", action = "AddUser" }
+//
+// The generated tree looks like this (json-like-notation):
+//
+// {
+// action : {
+// "AddUser" : {
+// controller : {
+// "Users" : {
+// area : {
+// "Admin" : match { area = "Admin", controller = "Users", action = "AddUser" }
+// }
+// }
+// }
+// },
+// "Buy" : {
+// controller : {
+// "Products" : {
+// area : {
+// null : match { controller = "Products", action = "Buy" }
+// }
+// }
+// }
+// },
+// "Index" : {
+// controller : {
+// "Home" : {
+// area : {
+// null : match { controller = "Home", action = "Index" }
+// }
+// }
+// "Products" : {
+// area : {
+// "null" : match { controller = "Products", action = "Index" }
+// }
+// }
+// }
+// }
+// }
+// }
+internal static class DecisionTreeBuilder<TItem>
{
- // This code generates a minimal tree of decision criteria that map known categorical data
- // (key-value-pairs) to a set of inputs. Action Selection is the best example of how this
- // can be used, so the comments here will describe the process from the point-of-view,
- // though the decision tree is generally applicable to like-problems.
- //
- // Care has been taken here to keep the performance of building the data-structure at a
- // reasonable level, as this has an impact on startup cost for action selection. Additionally
- // we want to hold on to the minimal amount of memory needed once we've built the tree.
- //
- // Ex:
- // Given actions like the following, create a decision tree that will help action
- // selection work efficiently.
- //
- // Given any set of route data it should be possible to traverse the tree using the
- // presence our route data keys (like action), and whether or not they match any of
- // the known values for that route data key, to find the set of actions that match
- // the route data.
- //
- // Actions:
- //
- // { controller = "Home", action = "Index" }
- // { controller = "Products", action = "Index" }
- // { controller = "Products", action = "Buy" }
- // { area = "Admin", controller = "Users", action = "AddUser" }
- //
- // The generated tree looks like this (json-like-notation):
- //
- // {
- // action : {
- // "AddUser" : {
- // controller : {
- // "Users" : {
- // area : {
- // "Admin" : match { area = "Admin", controller = "Users", action = "AddUser" }
- // }
- // }
- // }
- // },
- // "Buy" : {
- // controller : {
- // "Products" : {
- // area : {
- // null : match { controller = "Products", action = "Buy" }
- // }
- // }
- // }
- // },
- // "Index" : {
- // controller : {
- // "Home" : {
- // area : {
- // null : match { controller = "Home", action = "Index" }
- // }
- // }
- // "Products" : {
- // area : {
- // "null" : match { controller = "Products", action = "Index" }
- // }
- // }
- // }
- // }
- // }
- // }
- internal static class DecisionTreeBuilder<TItem>
+ public static DecisionTreeNode<TItem> GenerateTree(IReadOnlyList<TItem> items, IClassifier<TItem> classifier)
{
- public static DecisionTreeNode<TItem> GenerateTree(IReadOnlyList<TItem> items, IClassifier<TItem> classifier)
+ var itemCount = items.Count;
+ var itemDescriptors = new List<ItemDescriptor<TItem>>(itemCount);
+ for (var i = 0; i < itemCount; i++)
{
- var itemCount = items.Count;
- var itemDescriptors = new List<ItemDescriptor<TItem>>(itemCount);
- for (var i = 0; i < itemCount; i++)
+ var item = items[i];
+ itemDescriptors.Add(new ItemDescriptor<TItem>()
{
- var item = items[i];
- itemDescriptors.Add(new ItemDescriptor<TItem>()
- {
- Criteria = classifier.GetCriteria(item),
- Index = i,
- Item = item,
- });
- }
-
- var comparer = new DecisionCriterionValueEqualityComparer(classifier.ValueComparer);
- return GenerateNode(
- new TreeBuilderContext(),
- comparer,
- itemDescriptors);
+ Criteria = classifier.GetCriteria(item),
+ Index = i,
+ Item = item,
+ });
}
- private static DecisionTreeNode<TItem> GenerateNode(
- TreeBuilderContext context,
- DecisionCriterionValueEqualityComparer comparer,
- List<ItemDescriptor<TItem>> items)
+ var comparer = new DecisionCriterionValueEqualityComparer(classifier.ValueComparer);
+ return GenerateNode(
+ new TreeBuilderContext(),
+ comparer,
+ itemDescriptors);
+ }
+
+ private static DecisionTreeNode<TItem> GenerateNode(
+ TreeBuilderContext context,
+ DecisionCriterionValueEqualityComparer comparer,
+ List<ItemDescriptor<TItem>> items)
+ {
+ // The extreme use of generics here is intended to reduce the number of intermediate
+ // allocations of wrapper classes. Performance testing found that building these trees allocates
+ // significant memory that we can avoid and that it has a real impact on startup.
+ var criteria = new Dictionary<string, Criterion>(StringComparer.OrdinalIgnoreCase);
+
+ // Matches are items that have no remaining criteria - at this point in the tree
+ // they are considered accepted.
+ var matches = new List<TItem>();
+
+ // For each item in the working set, we want to map it to it's possible criteria-branch
+ // pairings, then reduce that tree to the minimal set.
+ foreach (var item in items)
{
- // The extreme use of generics here is intended to reduce the number of intermediate
- // allocations of wrapper classes. Performance testing found that building these trees allocates
- // significant memory that we can avoid and that it has a real impact on startup.
- var criteria = new Dictionary<string, Criterion>(StringComparer.OrdinalIgnoreCase);
-
- // Matches are items that have no remaining criteria - at this point in the tree
- // they are considered accepted.
- var matches = new List<TItem>();
-
- // For each item in the working set, we want to map it to it's possible criteria-branch
- // pairings, then reduce that tree to the minimal set.
- foreach (var item in items)
- {
- var unsatisfiedCriteria = 0;
+ var unsatisfiedCriteria = 0;
- foreach (var kvp in item.Criteria)
+ foreach (var kvp in item.Criteria)
+ {
+ // context.CurrentCriteria is the logical 'stack' of criteria that we've already processed
+ // on this branch of the tree.
+ if (context.CurrentCriteria.Contains(kvp.Key))
{
- // context.CurrentCriteria is the logical 'stack' of criteria that we've already processed
- // on this branch of the tree.
- if (context.CurrentCriteria.Contains(kvp.Key))
- {
- continue;
- }
-
- unsatisfiedCriteria++;
-
- if (!criteria.TryGetValue(kvp.Key, out var criterion))
- {
- criterion = new Criterion(comparer);
- criteria.Add(kvp.Key, criterion);
- }
+ continue;
+ }
- if (!criterion.TryGetValue(kvp.Value, out var branch))
- {
- branch = new List<ItemDescriptor<TItem>>();
- criterion.Add(kvp.Value, branch);
- }
+ unsatisfiedCriteria++;
- branch.Add(item);
+ if (!criteria.TryGetValue(kvp.Key, out var criterion))
+ {
+ criterion = new Criterion(comparer);
+ criteria.Add(kvp.Key, criterion);
}
- // If all of the criteria on item are satisfied by the 'stack' then this item is a match.
- if (unsatisfiedCriteria == 0)
+ if (!criterion.TryGetValue(kvp.Value, out var branch))
{
- matches.Add(item.Item);
+ branch = new List<ItemDescriptor<TItem>>();
+ criterion.Add(kvp.Value, branch);
}
+
+ branch.Add(item);
}
- // Iterate criteria in order of branchiness to determine which one to explore next. If a criterion
- // has no 'new' matches under it then we can just eliminate that part of the tree.
- var reducedCriteria = new List<DecisionCriterion<TItem>>();
- foreach (var criterion in criteria.OrderByDescending(c => c.Value.Count))
+ // If all of the criteria on item are satisfied by the 'stack' then this item is a match.
+ if (unsatisfiedCriteria == 0)
{
- var reducedBranches = new Dictionary<object, DecisionTreeNode<TItem>>(comparer.InnerComparer);
+ matches.Add(item.Item);
+ }
+ }
- foreach (var branch in criterion.Value)
- {
- bool hasReducedItems = false;
+ // Iterate criteria in order of branchiness to determine which one to explore next. If a criterion
+ // has no 'new' matches under it then we can just eliminate that part of the tree.
+ var reducedCriteria = new List<DecisionCriterion<TItem>>();
+ foreach (var criterion in criteria.OrderByDescending(c => c.Value.Count))
+ {
+ var reducedBranches = new Dictionary<object, DecisionTreeNode<TItem>>(comparer.InnerComparer);
- foreach (var item in branch.Value)
- {
- if (context.MatchedItems.Add(item))
- {
- hasReducedItems = true;
- }
- }
+ foreach (var branch in criterion.Value)
+ {
+ bool hasReducedItems = false;
- if (hasReducedItems)
+ foreach (var item in branch.Value)
+ {
+ if (context.MatchedItems.Add(item))
{
- var childContext = new TreeBuilderContext(context);
- childContext.CurrentCriteria.Add(criterion.Key);
-
- var newBranch = GenerateNode(childContext, comparer, branch.Value);
- reducedBranches.Add(branch.Key.Value, newBranch);
+ hasReducedItems = true;
}
}
- if (reducedBranches.Count > 0)
+ if (hasReducedItems)
{
- var newCriterion = new DecisionCriterion<TItem>()
- {
- Key = criterion.Key,
- Branches = reducedBranches,
- };
+ var childContext = new TreeBuilderContext(context);
+ childContext.CurrentCriteria.Add(criterion.Key);
- reducedCriteria.Add(newCriterion);
+ var newBranch = GenerateNode(childContext, comparer, branch.Value);
+ reducedBranches.Add(branch.Key.Value, newBranch);
}
}
- return new DecisionTreeNode<TItem>()
+ if (reducedBranches.Count > 0)
{
- Criteria = reducedCriteria,
- Matches = matches,
- };
- }
+ var newCriterion = new DecisionCriterion<TItem>()
+ {
+ Key = criterion.Key,
+ Branches = reducedBranches,
+ };
- private class TreeBuilderContext
- {
- public TreeBuilderContext()
- {
- CurrentCriteria = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- MatchedItems = new HashSet<ItemDescriptor<TItem>>();
+ reducedCriteria.Add(newCriterion);
}
+ }
- public TreeBuilderContext(TreeBuilderContext other)
- {
- CurrentCriteria = new HashSet<string>(other.CurrentCriteria, StringComparer.OrdinalIgnoreCase);
- MatchedItems = new HashSet<ItemDescriptor<TItem>>();
- }
+ return new DecisionTreeNode<TItem>()
+ {
+ Criteria = reducedCriteria,
+ Matches = matches,
+ };
+ }
- public HashSet<string> CurrentCriteria { get; private set; }
+ private class TreeBuilderContext
+ {
+ public TreeBuilderContext()
+ {
+ CurrentCriteria = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ MatchedItems = new HashSet<ItemDescriptor<TItem>>();
+ }
- public HashSet<ItemDescriptor<TItem>> MatchedItems { get; private set; }
+ public TreeBuilderContext(TreeBuilderContext other)
+ {
+ CurrentCriteria = new HashSet<string>(other.CurrentCriteria, StringComparer.OrdinalIgnoreCase);
+ MatchedItems = new HashSet<ItemDescriptor<TItem>>();
}
- // Subclass just to give a logical name to a mess of generics
- private class Criterion : Dictionary<DecisionCriterionValue, List<ItemDescriptor<TItem>>>
+ public HashSet<string> CurrentCriteria { get; private set; }
+
+ public HashSet<ItemDescriptor<TItem>> MatchedItems { get; private set; }
+ }
+
+ // Subclass just to give a logical name to a mess of generics
+ private class Criterion : Dictionary<DecisionCriterionValue, List<ItemDescriptor<TItem>>>
+ {
+ public Criterion(DecisionCriterionValueEqualityComparer comparer)
+ : base(comparer)
{
- public Criterion(DecisionCriterionValueEqualityComparer comparer)
- : base(comparer)
- {
- }
}
}
}
diff --git a/src/Http/Routing/src/DecisionTree/DecisionTreeNode.cs b/src/Http/Routing/src/DecisionTree/DecisionTreeNode.cs
index eca4006483..b4776a0b55 100644
--- a/src/Http/Routing/src/DecisionTree/DecisionTreeNode.cs
+++ b/src/Http/Routing/src/DecisionTree/DecisionTreeNode.cs
@@ -5,18 +5,17 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+// Data structure representing a node in a decision tree. These are created in DecisionTreeBuilder
+// and walked to find a set of items matching some input criteria.
+internal class DecisionTreeNode<TItem>
{
- // Data structure representing a node in a decision tree. These are created in DecisionTreeBuilder
- // and walked to find a set of items matching some input criteria.
- internal class DecisionTreeNode<TItem>
- {
- // The list of matches for the current node. This represents a set of items that have had all
- // of their criteria matched if control gets to this point in the tree.
- public IList<TItem> Matches { get; set; }
+ // The list of matches for the current node. This represents a set of items that have had all
+ // of their criteria matched if control gets to this point in the tree.
+ public IList<TItem> Matches { get; set; }
- // Additional criteria that further branch out from this node. Walk these to fine more items
- // matching the input data.
- public IList<DecisionCriterion<TItem>> Criteria { get; set; }
- }
+ // Additional criteria that further branch out from this node. Walk these to fine more items
+ // matching the input data.
+ public IList<DecisionCriterion<TItem>> Criteria { get; set; }
}
diff --git a/src/Http/Routing/src/DecisionTree/IClassifier.cs b/src/Http/Routing/src/DecisionTree/IClassifier.cs
index 6ce92a8673..1571bee942 100644
--- a/src/Http/Routing/src/DecisionTree/IClassifier.cs
+++ b/src/Http/Routing/src/DecisionTree/IClassifier.cs
@@ -3,12 +3,11 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+internal interface IClassifier<TItem>
{
- internal interface IClassifier<TItem>
- {
- IDictionary<string, DecisionCriterionValue> GetCriteria(TItem item);
+ IDictionary<string, DecisionCriterionValue> GetCriteria(TItem item);
- IEqualityComparer<object> ValueComparer { get; }
- }
-} \ No newline at end of file
+ IEqualityComparer<object> ValueComparer { get; }
+}
diff --git a/src/Http/Routing/src/DecisionTree/ItemDescriptor.cs b/src/Http/Routing/src/DecisionTree/ItemDescriptor.cs
index 4b49217883..ce35ddc5d1 100644
--- a/src/Http/Routing/src/DecisionTree/ItemDescriptor.cs
+++ b/src/Http/Routing/src/DecisionTree/ItemDescriptor.cs
@@ -5,14 +5,13 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+internal class ItemDescriptor<TItem>
{
- internal class ItemDescriptor<TItem>
- {
- public IDictionary<string, DecisionCriterionValue> Criteria { get; set; }
+ public IDictionary<string, DecisionCriterionValue> Criteria { get; set; }
- public int Index { get; set; }
+ public int Index { get; set; }
- public TItem Item { get; set; }
- }
+ public TItem Item { get; set; }
}
diff --git a/src/Http/Routing/src/DefaultEndpointConventionBuilder.cs b/src/Http/Routing/src/DefaultEndpointConventionBuilder.cs
index 992e23a551..c80b20856e 100644
--- a/src/Http/Routing/src/DefaultEndpointConventionBuilder.cs
+++ b/src/Http/Routing/src/DefaultEndpointConventionBuilder.cs
@@ -6,46 +6,45 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class DefaultEndpointConventionBuilder : IEndpointConventionBuilder
{
- internal class DefaultEndpointConventionBuilder : IEndpointConventionBuilder
+ internal EndpointBuilder EndpointBuilder { get; }
+
+ private List<Action<EndpointBuilder>>? _conventions;
+
+ public DefaultEndpointConventionBuilder(EndpointBuilder endpointBuilder)
{
- internal EndpointBuilder EndpointBuilder { get; }
+ EndpointBuilder = endpointBuilder;
+ _conventions = new();
+ }
- private List<Action<EndpointBuilder>>? _conventions;
+ public void Add(Action<EndpointBuilder> convention)
+ {
+ var conventions = _conventions;
- public DefaultEndpointConventionBuilder(EndpointBuilder endpointBuilder)
+ if (conventions is null)
{
- EndpointBuilder = endpointBuilder;
- _conventions = new();
+ throw new InvalidOperationException("Conventions cannot be added after building the endpoint");
}
- public void Add(Action<EndpointBuilder> convention)
- {
- var conventions = _conventions;
-
- if (conventions is null)
- {
- throw new InvalidOperationException("Conventions cannot be added after building the endpoint");
- }
+ conventions.Add(convention);
+ }
- conventions.Add(convention);
- }
+ public Endpoint Build()
+ {
+ // Only apply the conventions once
+ var conventions = Interlocked.Exchange(ref _conventions, null);
- public Endpoint Build()
+ if (conventions is not null)
{
- // Only apply the conventions once
- var conventions = Interlocked.Exchange(ref _conventions, null);
-
- if (conventions is not null)
+ foreach (var convention in conventions)
{
- foreach (var convention in conventions)
- {
- convention(EndpointBuilder);
- }
+ convention(EndpointBuilder);
}
-
- return EndpointBuilder.Build();
}
+
+ return EndpointBuilder.Build();
}
}
diff --git a/src/Http/Routing/src/DefaultEndpointDataSource.cs b/src/Http/Routing/src/DefaultEndpointDataSource.cs
index 1793c32b20..2728f1647f 100644
--- a/src/Http/Routing/src/DefaultEndpointDataSource.cs
+++ b/src/Http/Routing/src/DefaultEndpointDataSource.cs
@@ -7,53 +7,52 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Provides a collection of <see cref="Endpoint"/> instances.
+/// </summary>
+public sealed class DefaultEndpointDataSource : EndpointDataSource
{
+ private readonly IReadOnlyList<Endpoint> _endpoints;
+
/// <summary>
- /// Provides a collection of <see cref="Endpoint"/> instances.
+ /// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
/// </summary>
- public sealed class DefaultEndpointDataSource : EndpointDataSource
+ /// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
+ public DefaultEndpointDataSource(params Endpoint[] endpoints)
{
- private readonly IReadOnlyList<Endpoint> _endpoints;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
- /// </summary>
- /// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
- public DefaultEndpointDataSource(params Endpoint[] endpoints)
+ if (endpoints == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- _endpoints = (Endpoint[])endpoints.Clone();
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
- /// </summary>
- /// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
- public DefaultEndpointDataSource(IEnumerable<Endpoint> endpoints)
- {
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
+ _endpoints = (Endpoint[])endpoints.Clone();
+ }
- _endpoints = new List<Endpoint>(endpoints);
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DefaultEndpointDataSource" /> class.
+ /// </summary>
+ /// <param name="endpoints">The <see cref="Endpoint"/> instances that the data source will return.</param>
+ public DefaultEndpointDataSource(IEnumerable<Endpoint> endpoints)
+ {
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <summary>
- /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
- /// instances.
- /// </summary>
- /// <returns>The <see cref="IChangeToken"/>.</returns>
- public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
-
- /// <summary>
- /// Returns a read-only collection of <see cref="Endpoint"/> instances.
- /// </summary>
- public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
+ _endpoints = new List<Endpoint>(endpoints);
}
+
+ /// <summary>
+ /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
+ /// instances.
+ /// </summary>
+ /// <returns>The <see cref="IChangeToken"/>.</returns>
+ public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
+
+ /// <summary>
+ /// Returns a read-only collection of <see cref="Endpoint"/> instances.
+ /// </summary>
+ public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
}
diff --git a/src/Http/Routing/src/DefaultEndpointRouteBuilder.cs b/src/Http/Routing/src/DefaultEndpointRouteBuilder.cs
index e87e44d7ea..1d747f14a6 100644
--- a/src/Http/Routing/src/DefaultEndpointRouteBuilder.cs
+++ b/src/Http/Routing/src/DefaultEndpointRouteBuilder.cs
@@ -5,22 +5,21 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
{
- internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
+ public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
{
- public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
- {
- ApplicationBuilder = applicationBuilder ?? throw new ArgumentNullException(nameof(applicationBuilder));
- DataSources = new List<EndpointDataSource>();
- }
+ ApplicationBuilder = applicationBuilder ?? throw new ArgumentNullException(nameof(applicationBuilder));
+ DataSources = new List<EndpointDataSource>();
+ }
- public IApplicationBuilder ApplicationBuilder { get; }
+ public IApplicationBuilder ApplicationBuilder { get; }
- public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
+ public IApplicationBuilder CreateApplicationBuilder() => ApplicationBuilder.New();
- public ICollection<EndpointDataSource> DataSources { get; }
+ public ICollection<EndpointDataSource> DataSources { get; }
- public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
- }
+ public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices;
}
diff --git a/src/Http/Routing/src/DefaultInlineConstraintResolver.cs b/src/Http/Routing/src/DefaultInlineConstraintResolver.cs
index 4f58fceb3d..ad13f5ba1a 100644
--- a/src/Http/Routing/src/DefaultInlineConstraintResolver.cs
+++ b/src/Http/Routing/src/DefaultInlineConstraintResolver.cs
@@ -7,60 +7,59 @@ using System;
using System.Collections.Generic;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// The default implementation of <see cref="IInlineConstraintResolver"/>. Resolves constraints by parsing
+/// a constraint key and constraint arguments, using a map to resolve the constraint type, and calling an
+/// appropriate constructor for the constraint type.
+/// </summary>
+public class DefaultInlineConstraintResolver : IInlineConstraintResolver
{
+ private readonly IDictionary<string, Type> _inlineConstraintMap;
+ private readonly IServiceProvider _serviceProvider;
+
/// <summary>
- /// The default implementation of <see cref="IInlineConstraintResolver"/>. Resolves constraints by parsing
- /// a constraint key and constraint arguments, using a map to resolve the constraint type, and calling an
- /// appropriate constructor for the constraint type.
+ /// Initializes a new instance of the <see cref="DefaultInlineConstraintResolver"/> class.
/// </summary>
- public class DefaultInlineConstraintResolver : IInlineConstraintResolver
+ /// <param name="routeOptions">Accessor for <see cref="RouteOptions"/> containing the constraints of interest.</param>
+ /// <param name="serviceProvider">The <see cref="IServiceProvider"/> to get service arguments from.</param>
+ public DefaultInlineConstraintResolver(IOptions<RouteOptions> routeOptions, IServiceProvider serviceProvider)
{
- private readonly IDictionary<string, Type> _inlineConstraintMap;
- private readonly IServiceProvider _serviceProvider;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="DefaultInlineConstraintResolver"/> class.
- /// </summary>
- /// <param name="routeOptions">Accessor for <see cref="RouteOptions"/> containing the constraints of interest.</param>
- /// <param name="serviceProvider">The <see cref="IServiceProvider"/> to get service arguments from.</param>
- public DefaultInlineConstraintResolver(IOptions<RouteOptions> routeOptions, IServiceProvider serviceProvider)
+ if (routeOptions == null)
{
- if (routeOptions == null)
- {
- throw new ArgumentNullException(nameof(routeOptions));
- }
-
- if (serviceProvider == null)
- {
- throw new ArgumentNullException(nameof(serviceProvider));
- }
-
- _inlineConstraintMap = routeOptions.Value.ConstraintMap;
- _serviceProvider = serviceProvider;
+ throw new ArgumentNullException(nameof(routeOptions));
}
- /// <inheritdoc />
- /// <example>
- /// A typical constraint looks like the following
- /// "exampleConstraint(arg1, arg2, 12)".
- /// Here if the type registered for exampleConstraint has a single constructor with one argument,
- /// The entire string "arg1, arg2, 12" will be treated as a single argument.
- /// In all other cases arguments are split at comma.
- /// </example>
- public virtual IRouteConstraint? ResolveConstraint(string inlineConstraint)
+ if (serviceProvider == null)
{
- if (inlineConstraint == null)
- {
- throw new ArgumentNullException(nameof(inlineConstraint));
- }
+ throw new ArgumentNullException(nameof(serviceProvider));
+ }
- // This will return null if the text resolves to a non-IRouteConstraint
- return ParameterPolicyActivator.ResolveParameterPolicy<IRouteConstraint>(
- _inlineConstraintMap,
- _serviceProvider,
- inlineConstraint,
- out _);
+ _inlineConstraintMap = routeOptions.Value.ConstraintMap;
+ _serviceProvider = serviceProvider;
+ }
+
+ /// <inheritdoc />
+ /// <example>
+ /// A typical constraint looks like the following
+ /// "exampleConstraint(arg1, arg2, 12)".
+ /// Here if the type registered for exampleConstraint has a single constructor with one argument,
+ /// The entire string "arg1, arg2, 12" will be treated as a single argument.
+ /// In all other cases arguments are split at comma.
+ /// </example>
+ public virtual IRouteConstraint? ResolveConstraint(string inlineConstraint)
+ {
+ if (inlineConstraint == null)
+ {
+ throw new ArgumentNullException(nameof(inlineConstraint));
}
+
+ // This will return null if the text resolves to a non-IRouteConstraint
+ return ParameterPolicyActivator.ResolveParameterPolicy<IRouteConstraint>(
+ _inlineConstraintMap,
+ _serviceProvider,
+ inlineConstraint,
+ out _);
}
}
diff --git a/src/Http/Routing/src/DefaultLinkGenerator.cs b/src/Http/Routing/src/DefaultLinkGenerator.cs
index 72f3a68cbc..7b39a9e140 100644
--- a/src/Http/Routing/src/DefaultLinkGenerator.cs
+++ b/src/Http/Routing/src/DefaultLinkGenerator.cs
@@ -16,456 +16,455 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed partial class DefaultLinkGenerator : LinkGenerator, IDisposable
{
- internal sealed partial class DefaultLinkGenerator : LinkGenerator, IDisposable
+ private readonly TemplateBinderFactory _binderFactory;
+ private readonly ILogger<DefaultLinkGenerator> _logger;
+ private readonly IServiceProvider _serviceProvider;
+
+ // A LinkOptions object initialized with the values from RouteOptions
+ // Used when the user didn't specify something more global.
+ private readonly LinkOptions _globalLinkOptions;
+
+ // Caches TemplateBinder instances
+ private readonly DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, TemplateBinder>> _cache;
+
+ // Used to initialize TemplateBinder instances
+ private readonly Func<RouteEndpoint, TemplateBinder> _createTemplateBinder;
+
+ public DefaultLinkGenerator(
+ ParameterPolicyFactory parameterPolicyFactory,
+ TemplateBinderFactory binderFactory,
+ EndpointDataSource dataSource,
+ IOptions<RouteOptions> routeOptions,
+ ILogger<DefaultLinkGenerator> logger,
+ IServiceProvider serviceProvider)
{
- private readonly TemplateBinderFactory _binderFactory;
- private readonly ILogger<DefaultLinkGenerator> _logger;
- private readonly IServiceProvider _serviceProvider;
-
- // A LinkOptions object initialized with the values from RouteOptions
- // Used when the user didn't specify something more global.
- private readonly LinkOptions _globalLinkOptions;
-
- // Caches TemplateBinder instances
- private readonly DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, TemplateBinder>> _cache;
-
- // Used to initialize TemplateBinder instances
- private readonly Func<RouteEndpoint, TemplateBinder> _createTemplateBinder;
-
- public DefaultLinkGenerator(
- ParameterPolicyFactory parameterPolicyFactory,
- TemplateBinderFactory binderFactory,
- EndpointDataSource dataSource,
- IOptions<RouteOptions> routeOptions,
- ILogger<DefaultLinkGenerator> logger,
- IServiceProvider serviceProvider)
- {
- _binderFactory = binderFactory;
- _logger = logger;
- _serviceProvider = serviceProvider;
+ _binderFactory = binderFactory;
+ _logger = logger;
+ _serviceProvider = serviceProvider;
- // We cache TemplateBinder instances per-Endpoint for performance, but we want to wipe out
- // that cache is the endpoints change so that we don't allow unbounded memory growth.
- _cache = new DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, TemplateBinder>>(dataSource, (_) =>
- {
+ // We cache TemplateBinder instances per-Endpoint for performance, but we want to wipe out
+ // that cache is the endpoints change so that we don't allow unbounded memory growth.
+ _cache = new DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, TemplateBinder>>(dataSource, (_) =>
+ {
// We don't eagerly fill this cache because there's no real reason to. Unlike URL matching, we don't
// need to build a big data structure up front to be correct.
return new ConcurrentDictionary<RouteEndpoint, TemplateBinder>();
- });
+ });
- // Cached to avoid per-call allocation of a delegate on lookup.
- _createTemplateBinder = CreateTemplateBinder;
-
- _globalLinkOptions = new LinkOptions()
- {
- AppendTrailingSlash = routeOptions.Value.AppendTrailingSlash,
- LowercaseQueryStrings = routeOptions.Value.LowercaseQueryStrings,
- LowercaseUrls = routeOptions.Value.LowercaseUrls,
- };
- }
+ // Cached to avoid per-call allocation of a delegate on lookup.
+ _createTemplateBinder = CreateTemplateBinder;
- public override string? GetPathByAddress<TAddress>(
- HttpContext httpContext,
- TAddress address,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues = default,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = null)
+ _globalLinkOptions = new LinkOptions()
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var endpoints = GetEndpoints(address);
- if (endpoints.Count == 0)
- {
- return null;
- }
+ AppendTrailingSlash = routeOptions.Value.AppendTrailingSlash,
+ LowercaseQueryStrings = routeOptions.Value.LowercaseQueryStrings,
+ LowercaseUrls = routeOptions.Value.LowercaseUrls,
+ };
+ }
- return GetPathByEndpoints(
- httpContext,
- endpoints,
- values,
- ambientValues,
- pathBase ?? httpContext.Request.PathBase,
- fragment,
- options);
+ public override string? GetPathByAddress<TAddress>(
+ HttpContext httpContext,
+ TAddress address,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues = default,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = null)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
}
- public override string? GetPathByAddress<TAddress>(
- TAddress address,
- RouteValueDictionary values,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = null)
+ var endpoints = GetEndpoints(address);
+ if (endpoints.Count == 0)
{
- var endpoints = GetEndpoints(address);
- if (endpoints.Count == 0)
- {
- return null;
- }
-
- return GetPathByEndpoints(
- httpContext: null,
- endpoints,
- values,
- ambientValues: null,
- pathBase: pathBase,
- fragment: fragment,
- options: options);
+ return null;
}
- public override string? GetUriByAddress<TAddress>(
- HttpContext httpContext,
- TAddress address,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues = default,
- string? scheme = default,
- HostString? host = default,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = null)
+ return GetPathByEndpoints(
+ httpContext,
+ endpoints,
+ values,
+ ambientValues,
+ pathBase ?? httpContext.Request.PathBase,
+ fragment,
+ options);
+ }
+
+ public override string? GetPathByAddress<TAddress>(
+ TAddress address,
+ RouteValueDictionary values,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = null)
+ {
+ var endpoints = GetEndpoints(address);
+ if (endpoints.Count == 0)
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
+ return null;
+ }
- var endpoints = GetEndpoints(address);
- if (endpoints.Count == 0)
- {
- return null;
- }
+ return GetPathByEndpoints(
+ httpContext: null,
+ endpoints,
+ values,
+ ambientValues: null,
+ pathBase: pathBase,
+ fragment: fragment,
+ options: options);
+ }
- return GetUriByEndpoints(
- endpoints,
- values,
- ambientValues,
- scheme ?? httpContext.Request.Scheme,
- host ?? httpContext.Request.Host,
- pathBase ?? httpContext.Request.PathBase,
- fragment,
- options);
+ public override string? GetUriByAddress<TAddress>(
+ HttpContext httpContext,
+ TAddress address,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues = default,
+ string? scheme = default,
+ HostString? host = default,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = null)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
}
- public override string? GetUriByAddress<TAddress>(
- TAddress address,
- RouteValueDictionary values,
- string? scheme,
- HostString host,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = null)
+ var endpoints = GetEndpoints(address);
+ if (endpoints.Count == 0)
{
- if (string.IsNullOrEmpty(scheme))
- {
- throw new ArgumentException("A scheme must be provided.", nameof(scheme));
- }
-
- if (!host.HasValue)
- {
- throw new ArgumentException("A host must be provided.", nameof(host));
- }
+ return null;
+ }
- var endpoints = GetEndpoints(address);
- if (endpoints.Count == 0)
- {
- return null;
- }
+ return GetUriByEndpoints(
+ endpoints,
+ values,
+ ambientValues,
+ scheme ?? httpContext.Request.Scheme,
+ host ?? httpContext.Request.Host,
+ pathBase ?? httpContext.Request.PathBase,
+ fragment,
+ options);
+ }
- return GetUriByEndpoints(
- endpoints,
- values,
- ambientValues: null,
- scheme: scheme,
- host: host,
- pathBase: pathBase,
- fragment: fragment,
- options: options);
+ public override string? GetUriByAddress<TAddress>(
+ TAddress address,
+ RouteValueDictionary values,
+ string? scheme,
+ HostString host,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = null)
+ {
+ if (string.IsNullOrEmpty(scheme))
+ {
+ throw new ArgumentException("A scheme must be provided.", nameof(scheme));
}
- private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
+ if (!host.HasValue)
{
- var addressingScheme = _serviceProvider.GetRequiredService<IEndpointAddressScheme<TAddress>>();
- var endpoints = addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
-
- if (endpoints.Count == 0)
- {
- Log.EndpointsNotFound(_logger, address);
- }
- else
- {
- Log.EndpointsFound(_logger, address, endpoints);
- }
-
- return endpoints;
+ throw new ArgumentException("A host must be provided.", nameof(host));
}
- private string? GetPathByEndpoints(
- HttpContext? httpContext,
- List<RouteEndpoint> endpoints,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues,
- PathString pathBase,
- FragmentString fragment,
- LinkOptions? options)
+ var endpoints = GetEndpoints(address);
+ if (endpoints.Count == 0)
{
- for (var i = 0; i < endpoints.Count; i++)
- {
- var endpoint = endpoints[i];
- if (TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: values,
- ambientValues: ambientValues,
- options: options,
- result: out var result))
- {
- var uri = UriHelper.BuildRelative(
- pathBase,
- result.path,
- result.query,
- fragment);
- Log.LinkGenerationSucceeded(_logger, endpoints, uri);
- return uri;
- }
- }
-
- Log.LinkGenerationFailed(_logger, endpoints);
return null;
}
- // Also called from DefaultLinkGenerationTemplate
- public string? GetUriByEndpoints(
- List<RouteEndpoint> endpoints,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues,
- string scheme,
- HostString host,
- PathString pathBase,
- FragmentString fragment,
- LinkOptions? options)
- {
- for (var i = 0; i < endpoints.Count; i++)
- {
- var endpoint = endpoints[i];
- if (TryProcessTemplate(
- httpContext: null,
- endpoint: endpoint,
- values: values,
- ambientValues: ambientValues,
- options: options,
- result: out var result))
- {
- var uri = UriHelper.BuildAbsolute(
- scheme,
- host,
- pathBase,
- result.path,
- result.query,
- fragment);
- Log.LinkGenerationSucceeded(_logger, endpoints, uri);
- return uri;
- }
- }
+ return GetUriByEndpoints(
+ endpoints,
+ values,
+ ambientValues: null,
+ scheme: scheme,
+ host: host,
+ pathBase: pathBase,
+ fragment: fragment,
+ options: options);
+ }
- Log.LinkGenerationFailed(_logger, endpoints);
- return null;
- }
+ private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
+ {
+ var addressingScheme = _serviceProvider.GetRequiredService<IEndpointAddressScheme<TAddress>>();
+ var endpoints = addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
- private TemplateBinder CreateTemplateBinder(RouteEndpoint endpoint)
+ if (endpoints.Count == 0)
{
- return _binderFactory.Create(endpoint.RoutePattern);
+ Log.EndpointsNotFound(_logger, address);
}
-
- // Internal for testing
- internal TemplateBinder GetTemplateBinder(RouteEndpoint endpoint) => _cache.EnsureInitialized().GetOrAdd(endpoint, _createTemplateBinder);
-
- // Internal for testing
- internal bool TryProcessTemplate(
- HttpContext? httpContext,
- RouteEndpoint endpoint,
- RouteValueDictionary values,
- RouteValueDictionary? ambientValues,
- LinkOptions? options,
- out (PathString path, QueryString query) result)
+ else
{
- var templateBinder = GetTemplateBinder(endpoint);
+ Log.EndpointsFound(_logger, address, endpoints);
+ }
- var templateValuesResult = templateBinder.GetValues(ambientValues, values);
- if (templateValuesResult == null)
- {
- // We're missing one of the required values for this route.
- result = default;
- Log.TemplateFailedRequiredValues(_logger, endpoint, ambientValues, values);
- return false;
- }
+ return endpoints;
+ }
- if (!templateBinder.TryProcessConstraints(httpContext, templateValuesResult.CombinedValues, out var parameterName, out var constraint))
+ private string? GetPathByEndpoints(
+ HttpContext? httpContext,
+ List<RouteEndpoint> endpoints,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues,
+ PathString pathBase,
+ FragmentString fragment,
+ LinkOptions? options)
+ {
+ for (var i = 0; i < endpoints.Count; i++)
+ {
+ var endpoint = endpoints[i];
+ if (TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: values,
+ ambientValues: ambientValues,
+ options: options,
+ result: out var result))
{
- result = default;
- Log.TemplateFailedConstraint(_logger, endpoint, parameterName, constraint, templateValuesResult.CombinedValues);
- return false;
+ var uri = UriHelper.BuildRelative(
+ pathBase,
+ result.path,
+ result.query,
+ fragment);
+ Log.LinkGenerationSucceeded(_logger, endpoints, uri);
+ return uri;
}
+ }
+
+ Log.LinkGenerationFailed(_logger, endpoints);
+ return null;
+ }
- if (!templateBinder.TryBindValues(templateValuesResult.AcceptedValues, options, _globalLinkOptions, out result))
+ // Also called from DefaultLinkGenerationTemplate
+ public string? GetUriByEndpoints(
+ List<RouteEndpoint> endpoints,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues,
+ string scheme,
+ HostString host,
+ PathString pathBase,
+ FragmentString fragment,
+ LinkOptions? options)
+ {
+ for (var i = 0; i < endpoints.Count; i++)
+ {
+ var endpoint = endpoints[i];
+ if (TryProcessTemplate(
+ httpContext: null,
+ endpoint: endpoint,
+ values: values,
+ ambientValues: ambientValues,
+ options: options,
+ result: out var result))
{
- Log.TemplateFailedExpansion(_logger, endpoint, templateValuesResult.AcceptedValues);
- return false;
+ var uri = UriHelper.BuildAbsolute(
+ scheme,
+ host,
+ pathBase,
+ result.path,
+ result.query,
+ fragment);
+ Log.LinkGenerationSucceeded(_logger, endpoints, uri);
+ return uri;
}
+ }
+
+ Log.LinkGenerationFailed(_logger, endpoints);
+ return null;
+ }
- Log.TemplateSucceeded(_logger, endpoint, result.path, result.query);
- return true;
+ private TemplateBinder CreateTemplateBinder(RouteEndpoint endpoint)
+ {
+ return _binderFactory.Create(endpoint.RoutePattern);
+ }
+
+ // Internal for testing
+ internal TemplateBinder GetTemplateBinder(RouteEndpoint endpoint) => _cache.EnsureInitialized().GetOrAdd(endpoint, _createTemplateBinder);
+
+ // Internal for testing
+ internal bool TryProcessTemplate(
+ HttpContext? httpContext,
+ RouteEndpoint endpoint,
+ RouteValueDictionary values,
+ RouteValueDictionary? ambientValues,
+ LinkOptions? options,
+ out (PathString path, QueryString query) result)
+ {
+ var templateBinder = GetTemplateBinder(endpoint);
+
+ var templateValuesResult = templateBinder.GetValues(ambientValues, values);
+ if (templateValuesResult == null)
+ {
+ // We're missing one of the required values for this route.
+ result = default;
+ Log.TemplateFailedRequiredValues(_logger, endpoint, ambientValues, values);
+ return false;
}
- // Also called from DefaultLinkGenerationTemplate
- public static RouteValueDictionary? GetAmbientValues(HttpContext? httpContext)
+ if (!templateBinder.TryProcessConstraints(httpContext, templateValuesResult.CombinedValues, out var parameterName, out var constraint))
{
- return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
+ result = default;
+ Log.TemplateFailedConstraint(_logger, endpoint, parameterName, constraint, templateValuesResult.CombinedValues);
+ return false;
}
- public void Dispose()
+ if (!templateBinder.TryBindValues(templateValuesResult.AcceptedValues, options, _globalLinkOptions, out result))
{
- _cache.Dispose();
+ Log.TemplateFailedExpansion(_logger, endpoint, templateValuesResult.AcceptedValues);
+ return false;
}
- private static partial class Log
+ Log.TemplateSucceeded(_logger, endpoint, result.path, result.query);
+ return true;
+ }
+
+ // Also called from DefaultLinkGenerationTemplate
+ public static RouteValueDictionary? GetAmbientValues(HttpContext? httpContext)
+ {
+ return httpContext?.Features.Get<IRouteValuesFeature>()?.RouteValues;
+ }
+
+ public void Dispose()
+ {
+ _cache.Dispose();
+ }
+
+ private static partial class Log
+ {
+ public static void EndpointsFound(ILogger logger, object? address, IEnumerable<Endpoint> endpoints)
{
- public static void EndpointsFound(ILogger logger, object? address, IEnumerable<Endpoint> endpoints)
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- EndpointsFound(logger, endpoints.Select(e => e.DisplayName), address);
- }
+ EndpointsFound(logger, endpoints.Select(e => e.DisplayName), address);
}
+ }
- [LoggerMessage(100, LogLevel.Debug, "Found the endpoints {Endpoints} for address {Address}", EventName = "EndpointsFound", SkipEnabledCheck = true)]
- private static partial void EndpointsFound(ILogger logger, IEnumerable<string?> endpoints, object? address);
+ [LoggerMessage(100, LogLevel.Debug, "Found the endpoints {Endpoints} for address {Address}", EventName = "EndpointsFound", SkipEnabledCheck = true)]
+ private static partial void EndpointsFound(ILogger logger, IEnumerable<string?> endpoints, object? address);
- [LoggerMessage(101, LogLevel.Debug, "No endpoints found for address {Address}", EventName = "EndpointsNotFound")]
- public static partial void EndpointsNotFound(ILogger logger, object? address);
+ [LoggerMessage(101, LogLevel.Debug, "No endpoints found for address {Address}", EventName = "EndpointsNotFound")]
+ public static partial void EndpointsNotFound(ILogger logger, object? address);
- public static void TemplateSucceeded(ILogger logger, RouteEndpoint endpoint, PathString path, QueryString query)
- => TemplateSucceeded(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, path.Value, query.Value);
+ public static void TemplateSucceeded(ILogger logger, RouteEndpoint endpoint, PathString path, QueryString query)
+ => TemplateSucceeded(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, path.Value, query.Value);
- [LoggerMessage(102, LogLevel.Debug,
- "Successfully processed template {Template} for {Endpoint} resulting in {Path} and {Query}",
- EventName = "TemplateSucceeded")]
- private static partial void TemplateSucceeded(ILogger logger, string? template, string? endpoint, string? path, string? query);
+ [LoggerMessage(102, LogLevel.Debug,
+ "Successfully processed template {Template} for {Endpoint} resulting in {Path} and {Query}",
+ EventName = "TemplateSucceeded")]
+ private static partial void TemplateSucceeded(ILogger logger, string? template, string? endpoint, string? path, string? query);
- public static void TemplateFailedRequiredValues(ILogger logger, RouteEndpoint endpoint, RouteValueDictionary? ambientValues, RouteValueDictionary values)
+ public static void TemplateFailedRequiredValues(ILogger logger, RouteEndpoint endpoint, RouteValueDictionary? ambientValues, RouteValueDictionary values)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- TemplateFailedRequiredValues(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, FormatRouteValues(ambientValues), FormatRouteValues(values), FormatRouteValues(endpoint.RoutePattern.Defaults));
- }
+ TemplateFailedRequiredValues(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, FormatRouteValues(ambientValues), FormatRouteValues(values), FormatRouteValues(endpoint.RoutePattern.Defaults));
}
+ }
- [LoggerMessage(103, LogLevel.Debug,
- "Failed to process the template {Template} for {Endpoint}. " +
- "A required route value is missing, or has a different value from the required default values. " +
- "Supplied ambient values {AmbientValues} and {Values} with default values {Defaults}",
- EventName = "TemplateFailedRequiredValues",
- SkipEnabledCheck = true)]
- private static partial void TemplateFailedRequiredValues(ILogger logger, string? template, string? endpoint, string ambientValues, string values, string defaults);
+ [LoggerMessage(103, LogLevel.Debug,
+ "Failed to process the template {Template} for {Endpoint}. " +
+ "A required route value is missing, or has a different value from the required default values. " +
+ "Supplied ambient values {AmbientValues} and {Values} with default values {Defaults}",
+ EventName = "TemplateFailedRequiredValues",
+ SkipEnabledCheck = true)]
+ private static partial void TemplateFailedRequiredValues(ILogger logger, string? template, string? endpoint, string ambientValues, string values, string defaults);
- public static void TemplateFailedConstraint(ILogger logger, RouteEndpoint endpoint, string? parameterName, IRouteConstraint? constraint, RouteValueDictionary values)
+ public static void TemplateFailedConstraint(ILogger logger, RouteEndpoint endpoint, string? parameterName, IRouteConstraint? constraint, RouteValueDictionary values)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- TemplateFailedConstraint(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, constraint, parameterName, FormatRouteValues(values));
- }
+ TemplateFailedConstraint(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, constraint, parameterName, FormatRouteValues(values));
}
+ }
- [LoggerMessage(107, LogLevel.Debug,
- "Failed to process the template {Template} for {Endpoint}. " +
- "The constraint {Constraint} for parameter {ParameterName} failed with values {Values}",
- EventName = "TemplateFailedConstraint",
- SkipEnabledCheck = true)]
- private static partial void TemplateFailedConstraint(ILogger logger, string? template, string? endpoint, IRouteConstraint? constraint, string? parameterName, string values);
+ [LoggerMessage(107, LogLevel.Debug,
+ "Failed to process the template {Template} for {Endpoint}. " +
+ "The constraint {Constraint} for parameter {ParameterName} failed with values {Values}",
+ EventName = "TemplateFailedConstraint",
+ SkipEnabledCheck = true)]
+ private static partial void TemplateFailedConstraint(ILogger logger, string? template, string? endpoint, IRouteConstraint? constraint, string? parameterName, string values);
- public static void TemplateFailedExpansion(ILogger logger, RouteEndpoint endpoint, RouteValueDictionary values)
+ public static void TemplateFailedExpansion(ILogger logger, RouteEndpoint endpoint, RouteValueDictionary values)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- TemplateFailedExpansion(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, FormatRouteValues(values));
- }
+ TemplateFailedExpansion(logger, endpoint.RoutePattern.RawText, endpoint.DisplayName, FormatRouteValues(values));
}
+ }
- [LoggerMessage(104, LogLevel.Debug,
- "Failed to process the template {Template} for {Endpoint}. " +
- "The failure occurred while expanding the template with values {Values} " +
- "This is usually due to a missing or empty value in a complex segment",
- EventName = "TemplateFailedExpansion",
- SkipEnabledCheck = true)]
- private static partial void TemplateFailedExpansion(ILogger logger, string? template, string? endpoint, string values);
+ [LoggerMessage(104, LogLevel.Debug,
+ "Failed to process the template {Template} for {Endpoint}. " +
+ "The failure occurred while expanding the template with values {Values} " +
+ "This is usually due to a missing or empty value in a complex segment",
+ EventName = "TemplateFailedExpansion",
+ SkipEnabledCheck = true)]
+ private static partial void TemplateFailedExpansion(ILogger logger, string? template, string? endpoint, string values);
- public static void LinkGenerationSucceeded(ILogger logger, IEnumerable<Endpoint> endpoints, string uri)
+ public static void LinkGenerationSucceeded(ILogger logger, IEnumerable<Endpoint> endpoints, string uri)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- LinkGenerationSucceeded(logger, endpoints.Select(e => e.DisplayName), uri);
- }
+ LinkGenerationSucceeded(logger, endpoints.Select(e => e.DisplayName), uri);
}
+ }
- [LoggerMessage(105, LogLevel.Debug,
- "Link generation succeeded for endpoints {Endpoints} with result {URI}",
- EventName = "LinkGenerationSucceeded",
- SkipEnabledCheck = true)]
- private static partial void LinkGenerationSucceeded(ILogger logger, IEnumerable<string?> endpoints, string uri);
+ [LoggerMessage(105, LogLevel.Debug,
+ "Link generation succeeded for endpoints {Endpoints} with result {URI}",
+ EventName = "LinkGenerationSucceeded",
+ SkipEnabledCheck = true)]
+ private static partial void LinkGenerationSucceeded(ILogger logger, IEnumerable<string?> endpoints, string uri);
- public static void LinkGenerationFailed(ILogger logger, IEnumerable<Endpoint> endpoints)
+ public static void LinkGenerationFailed(ILogger logger, IEnumerable<Endpoint> endpoints)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- LinkGenerationFailed(logger, endpoints.Select(e => e.DisplayName));
- }
+ LinkGenerationFailed(logger, endpoints.Select(e => e.DisplayName));
}
+ }
- [LoggerMessage(106, LogLevel.Debug, "Link generation failed for endpoints {Endpoints}", EventName = "LinkGenerationFailed", SkipEnabledCheck = true)]
- private static partial void LinkGenerationFailed(ILogger logger, IEnumerable<string?> endpoints);
+ [LoggerMessage(106, LogLevel.Debug, "Link generation failed for endpoints {Endpoints}", EventName = "LinkGenerationFailed", SkipEnabledCheck = true)]
+ private static partial void LinkGenerationFailed(ILogger logger, IEnumerable<string?> endpoints);
- // EXPENSIVE: should only be used at Debug and higher levels of logging.
- private static string FormatRouteValues(IReadOnlyDictionary<string, object?>? values)
+ // EXPENSIVE: should only be used at Debug and higher levels of logging.
+ private static string FormatRouteValues(IReadOnlyDictionary<string, object?>? values)
+ {
+ if (values == null || values.Count == 0)
{
- if (values == null || values.Count == 0)
- {
- return "{ }";
- }
-
- var builder = new StringBuilder();
- builder.Append("{ ");
-
- foreach (var kvp in values.OrderBy(kvp => kvp.Key))
- {
- builder.Append('"');
- builder.Append(kvp.Key);
- builder.Append('"');
- builder.Append(':');
- builder.Append(' ');
- builder.Append('"');
- builder.Append(kvp.Value);
- builder.Append('"');
- builder.Append(", ");
- }
-
- // Trim trailing ", "
- builder.Remove(builder.Length - 2, 2);
-
- builder.Append(" }");
-
- return builder.ToString();
+ return "{ }";
}
+
+ var builder = new StringBuilder();
+ builder.Append("{ ");
+
+ foreach (var kvp in values.OrderBy(kvp => kvp.Key))
+ {
+ builder.Append('"');
+ builder.Append(kvp.Key);
+ builder.Append('"');
+ builder.Append(':');
+ builder.Append(' ');
+ builder.Append('"');
+ builder.Append(kvp.Value);
+ builder.Append('"');
+ builder.Append(", ");
+ }
+
+ // Trim trailing ", "
+ builder.Remove(builder.Length - 2, 2);
+
+ builder.Append(" }");
+
+ return builder.ToString();
}
}
}
diff --git a/src/Http/Routing/src/DefaultLinkParser.cs b/src/Http/Routing/src/DefaultLinkParser.cs
index d7243a081b..64a8531c3a 100644
--- a/src/Http/Routing/src/DefaultLinkParser.cs
+++ b/src/Http/Routing/src/DefaultLinkParser.cs
@@ -10,205 +10,204 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed partial class DefaultLinkParser : LinkParser, IDisposable
{
- internal sealed partial class DefaultLinkParser : LinkParser, IDisposable
- {
- private readonly ParameterPolicyFactory _parameterPolicyFactory;
- private readonly ILogger<DefaultLinkParser> _logger;
- private readonly IServiceProvider _serviceProvider;
+ private readonly ParameterPolicyFactory _parameterPolicyFactory;
+ private readonly ILogger<DefaultLinkParser> _logger;
+ private readonly IServiceProvider _serviceProvider;
- // Caches RoutePatternMatcher instances
- private readonly DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, MatcherState>> _matcherCache;
+ // Caches RoutePatternMatcher instances
+ private readonly DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, MatcherState>> _matcherCache;
- // Used to initialize RoutePatternMatcher and constraint instances
- private readonly Func<RouteEndpoint, MatcherState> _createMatcher;
+ // Used to initialize RoutePatternMatcher and constraint instances
+ private readonly Func<RouteEndpoint, MatcherState> _createMatcher;
- public DefaultLinkParser(
- ParameterPolicyFactory parameterPolicyFactory,
- EndpointDataSource dataSource,
- ILogger<DefaultLinkParser> logger,
- IServiceProvider serviceProvider)
- {
- _parameterPolicyFactory = parameterPolicyFactory;
- _logger = logger;
- _serviceProvider = serviceProvider;
+ public DefaultLinkParser(
+ ParameterPolicyFactory parameterPolicyFactory,
+ EndpointDataSource dataSource,
+ ILogger<DefaultLinkParser> logger,
+ IServiceProvider serviceProvider)
+ {
+ _parameterPolicyFactory = parameterPolicyFactory;
+ _logger = logger;
+ _serviceProvider = serviceProvider;
- // We cache RoutePatternMatcher instances per-Endpoint for performance, but we want to wipe out
- // that cache is the endpoints change so that we don't allow unbounded memory growth.
- _matcherCache = new DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, MatcherState>>(dataSource, (_) =>
- {
+ // We cache RoutePatternMatcher instances per-Endpoint for performance, but we want to wipe out
+ // that cache is the endpoints change so that we don't allow unbounded memory growth.
+ _matcherCache = new DataSourceDependentCache<ConcurrentDictionary<RouteEndpoint, MatcherState>>(dataSource, (_) =>
+ {
// We don't eagerly fill this cache because there's no real reason to. Unlike URL matching, we don't
// need to build a big data structure up front to be correct.
return new ConcurrentDictionary<RouteEndpoint, MatcherState>();
- });
+ });
+
+ // Cached to avoid per-call allocation of a delegate on lookup.
+ _createMatcher = CreateRoutePatternMatcher;
+ }
- // Cached to avoid per-call allocation of a delegate on lookup.
- _createMatcher = CreateRoutePatternMatcher;
+ public override RouteValueDictionary? ParsePathByAddress<TAddress>(TAddress address, PathString path)
+ {
+ var endpoints = GetEndpoints(address);
+ if (endpoints.Count == 0)
+ {
+ return null;
}
- public override RouteValueDictionary? ParsePathByAddress<TAddress>(TAddress address, PathString path)
+ for (var i = 0; i < endpoints.Count; i++)
{
- var endpoints = GetEndpoints(address);
- if (endpoints.Count == 0)
+ var endpoint = endpoints[i];
+ if (TryParse(endpoint, path, out var values))
{
- return null;
+ Log.PathParsingSucceeded(_logger, path, endpoint);
+ return values;
}
+ }
- for (var i = 0; i < endpoints.Count; i++)
- {
- var endpoint = endpoints[i];
- if (TryParse(endpoint, path, out var values))
- {
- Log.PathParsingSucceeded(_logger, path, endpoint);
- return values;
- }
- }
+ Log.PathParsingFailed(_logger, path, endpoints);
+ return null;
+ }
- Log.PathParsingFailed(_logger, path, endpoints);
- return null;
- }
+ private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
+ {
+ var addressingScheme = _serviceProvider.GetRequiredService<IEndpointAddressScheme<TAddress>>();
+ var endpoints = addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
- private List<RouteEndpoint> GetEndpoints<TAddress>(TAddress address)
+ if (endpoints.Count == 0)
+ {
+ Log.EndpointsNotFound(_logger, address);
+ }
+ else
{
- var addressingScheme = _serviceProvider.GetRequiredService<IEndpointAddressScheme<TAddress>>();
- var endpoints = addressingScheme.FindEndpoints(address).OfType<RouteEndpoint>().ToList();
+ Log.EndpointsFound(_logger, address, endpoints);
+ }
- if (endpoints.Count == 0)
- {
- Log.EndpointsNotFound(_logger, address);
- }
- else
- {
- Log.EndpointsFound(_logger, address, endpoints);
- }
+ return endpoints;
+ }
- return endpoints;
- }
+ private MatcherState CreateRoutePatternMatcher(RouteEndpoint endpoint)
+ {
+ var constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
- private MatcherState CreateRoutePatternMatcher(RouteEndpoint endpoint)
+ var policies = endpoint.RoutePattern.ParameterPolicies;
+ foreach (var kvp in policies)
{
- var constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
-
- var policies = endpoint.RoutePattern.ParameterPolicies;
- foreach (var kvp in policies)
+ var constraintsForParameter = new List<IRouteConstraint>();
+ var parameter = endpoint.RoutePattern.GetParameter(kvp.Key);
+ for (var i = 0; i < kvp.Value.Count; i++)
{
- var constraintsForParameter = new List<IRouteConstraint>();
- var parameter = endpoint.RoutePattern.GetParameter(kvp.Key);
- for (var i = 0; i < kvp.Value.Count; i++)
+ var policy = _parameterPolicyFactory.Create(parameter, kvp.Value[i]);
+ if (policy is IRouteConstraint constraint)
{
- var policy = _parameterPolicyFactory.Create(parameter, kvp.Value[i]);
- if (policy is IRouteConstraint constraint)
- {
- constraintsForParameter.Add(constraint);
- }
- }
-
- if (constraintsForParameter.Count > 0)
- {
- constraints.Add(kvp.Key, constraintsForParameter);
+ constraintsForParameter.Add(constraint);
}
}
- var matcher = new RoutePatternMatcher(endpoint.RoutePattern, new RouteValueDictionary(endpoint.RoutePattern.Defaults));
- return new MatcherState(matcher, constraints);
+ if (constraintsForParameter.Count > 0)
+ {
+ constraints.Add(kvp.Key, constraintsForParameter);
+ }
}
- // Internal for testing
- internal MatcherState GetMatcherState(RouteEndpoint endpoint) => _matcherCache.EnsureInitialized().GetOrAdd(endpoint, _createMatcher);
+ var matcher = new RoutePatternMatcher(endpoint.RoutePattern, new RouteValueDictionary(endpoint.RoutePattern.Defaults));
+ return new MatcherState(matcher, constraints);
+ }
- // Internal for testing
- internal bool TryParse(RouteEndpoint endpoint, PathString path, [NotNullWhen(true)] out RouteValueDictionary? values)
- {
- var (matcher, constraints) = GetMatcherState(endpoint);
+ // Internal for testing
+ internal MatcherState GetMatcherState(RouteEndpoint endpoint) => _matcherCache.EnsureInitialized().GetOrAdd(endpoint, _createMatcher);
- values = new RouteValueDictionary();
- if (!matcher.TryMatch(path, values))
- {
- values = null;
- return false;
- }
+ // Internal for testing
+ internal bool TryParse(RouteEndpoint endpoint, PathString path, [NotNullWhen(true)] out RouteValueDictionary? values)
+ {
+ var (matcher, constraints) = GetMatcherState(endpoint);
- foreach (var kvp in constraints)
+ values = new RouteValueDictionary();
+ if (!matcher.TryMatch(path, values))
+ {
+ values = null;
+ return false;
+ }
+
+ foreach (var kvp in constraints)
+ {
+ for (var i = 0; i < kvp.Value.Count; i++)
{
- for (var i = 0; i < kvp.Value.Count; i++)
+ var constraint = kvp.Value[i];
+ if (!constraint.Match(httpContext: null, NullRouter.Instance, kvp.Key, values, RouteDirection.IncomingRequest))
{
- var constraint = kvp.Value[i];
- if (!constraint.Match(httpContext: null, NullRouter.Instance, kvp.Key, values, RouteDirection.IncomingRequest))
- {
- values = null;
- return false;
- }
+ values = null;
+ return false;
}
}
-
- return true;
}
- public void Dispose()
+ return true;
+ }
+
+ public void Dispose()
+ {
+ _matcherCache.Dispose();
+ }
+
+ // internal for testing
+ internal readonly struct MatcherState
+ {
+ public readonly RoutePatternMatcher Matcher;
+ public readonly Dictionary<string, List<IRouteConstraint>> Constraints;
+
+ public MatcherState(RoutePatternMatcher matcher, Dictionary<string, List<IRouteConstraint>> constraints)
{
- _matcherCache.Dispose();
+ Matcher = matcher;
+ Constraints = constraints;
}
- // internal for testing
- internal readonly struct MatcherState
+ public void Deconstruct(out RoutePatternMatcher matcher, out Dictionary<string, List<IRouteConstraint>> constraints)
{
- public readonly RoutePatternMatcher Matcher;
- public readonly Dictionary<string, List<IRouteConstraint>> Constraints;
-
- public MatcherState(RoutePatternMatcher matcher, Dictionary<string, List<IRouteConstraint>> constraints)
- {
- Matcher = matcher;
- Constraints = constraints;
- }
-
- public void Deconstruct(out RoutePatternMatcher matcher, out Dictionary<string, List<IRouteConstraint>> constraints)
- {
- matcher = Matcher;
- constraints = Constraints;
- }
+ matcher = Matcher;
+ constraints = Constraints;
}
+ }
- private static partial class Log
+ private static partial class Log
+ {
+ public static void EndpointsFound(ILogger logger, object? address, IEnumerable<Endpoint> endpoints)
{
- public static void EndpointsFound(ILogger logger, object? address, IEnumerable<Endpoint> endpoints)
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- EndpointsFound(logger, endpoints.Select(e => e.DisplayName), address);
- }
+ EndpointsFound(logger, endpoints.Select(e => e.DisplayName), address);
}
+ }
- [LoggerMessage(100, LogLevel.Debug, "Found the endpoints {Endpoints} for address {Address}", EventName = "EndpointsFound", SkipEnabledCheck = true)]
- private static partial void EndpointsFound(ILogger logger, IEnumerable<string?> endpoints, object? address);
+ [LoggerMessage(100, LogLevel.Debug, "Found the endpoints {Endpoints} for address {Address}", EventName = "EndpointsFound", SkipEnabledCheck = true)]
+ private static partial void EndpointsFound(ILogger logger, IEnumerable<string?> endpoints, object? address);
- [LoggerMessage(101, LogLevel.Debug, "No endpoints found for address {Address}", EventName = "EndpointsNotFound")]
- public static partial void EndpointsNotFound(ILogger logger, object? address);
+ [LoggerMessage(101, LogLevel.Debug, "No endpoints found for address {Address}", EventName = "EndpointsNotFound")]
+ public static partial void EndpointsNotFound(ILogger logger, object? address);
- public static void PathParsingSucceeded(ILogger logger, PathString path, Endpoint endpoint)
+ public static void PathParsingSucceeded(ILogger logger, PathString path, Endpoint endpoint)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- PathParsingSucceeded(logger, endpoint.DisplayName, path.Value);
- }
+ PathParsingSucceeded(logger, endpoint.DisplayName, path.Value);
}
+ }
- [LoggerMessage(102, LogLevel.Debug, "Path parsing succeeded for endpoint {Endpoint} and URI path {URI}", EventName = "PathParsingSucceeded", SkipEnabledCheck = true)]
- private static partial void PathParsingSucceeded(ILogger logger, string? endpoint, string? uri);
+ [LoggerMessage(102, LogLevel.Debug, "Path parsing succeeded for endpoint {Endpoint} and URI path {URI}", EventName = "PathParsingSucceeded", SkipEnabledCheck = true)]
+ private static partial void PathParsingSucceeded(ILogger logger, string? endpoint, string? uri);
- public static void PathParsingFailed(ILogger logger, PathString path, IEnumerable<Endpoint> endpoints)
+ public static void PathParsingFailed(ILogger logger, PathString path, IEnumerable<Endpoint> endpoints)
+ {
+ // Checking level again to avoid allocation on the common path
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // Checking level again to avoid allocation on the common path
- if (logger.IsEnabled(LogLevel.Debug))
- {
- PathParsingFailed(logger, endpoints.Select(e => e.DisplayName), path.Value);
- }
+ PathParsingFailed(logger, endpoints.Select(e => e.DisplayName), path.Value);
}
-
- [LoggerMessage(103, LogLevel.Debug, "Path parsing failed for endpoints {Endpoints} and URI path {URI}", EventName = "PathParsingFailed", SkipEnabledCheck = true)]
- private static partial void PathParsingFailed(ILogger logger, IEnumerable<string?> endpoints, string? uri);
}
+
+ [LoggerMessage(103, LogLevel.Debug, "Path parsing failed for endpoints {Endpoints} and URI path {URI}", EventName = "PathParsingFailed", SkipEnabledCheck = true)]
+ private static partial void PathParsingFailed(ILogger logger, IEnumerable<string?> endpoints, string? uri);
}
}
diff --git a/src/Http/Routing/src/DefaultParameterPolicyFactory.cs b/src/Http/Routing/src/DefaultParameterPolicyFactory.cs
index 6ad731970b..ce90169896 100644
--- a/src/Http/Routing/src/DefaultParameterPolicyFactory.cs
+++ b/src/Http/Routing/src/DefaultParameterPolicyFactory.cs
@@ -6,75 +6,74 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class DefaultParameterPolicyFactory : ParameterPolicyFactory
{
- internal class DefaultParameterPolicyFactory : ParameterPolicyFactory
+ private readonly RouteOptions _options;
+ private readonly IServiceProvider _serviceProvider;
+
+ public DefaultParameterPolicyFactory(
+ IOptions<RouteOptions> options,
+ IServiceProvider serviceProvider)
{
- private readonly RouteOptions _options;
- private readonly IServiceProvider _serviceProvider;
+ _options = options.Value;
+ _serviceProvider = serviceProvider;
+ }
- public DefaultParameterPolicyFactory(
- IOptions<RouteOptions> options,
- IServiceProvider serviceProvider)
+ public override IParameterPolicy Create(RoutePatternParameterPart? parameter, IParameterPolicy parameterPolicy)
+ {
+ if (parameterPolicy == null)
{
- _options = options.Value;
- _serviceProvider = serviceProvider;
+ throw new ArgumentNullException(nameof(parameterPolicy));
}
- public override IParameterPolicy Create(RoutePatternParameterPart? parameter, IParameterPolicy parameterPolicy)
+ if (parameterPolicy is IRouteConstraint routeConstraint)
{
- if (parameterPolicy == null)
- {
- throw new ArgumentNullException(nameof(parameterPolicy));
- }
-
- if (parameterPolicy is IRouteConstraint routeConstraint)
- {
- return InitializeRouteConstraint(parameter?.IsOptional ?? false, routeConstraint);
- }
-
- return parameterPolicy;
+ return InitializeRouteConstraint(parameter?.IsOptional ?? false, routeConstraint);
}
- public override IParameterPolicy Create(RoutePatternParameterPart? parameter, string inlineText)
- {
- if (inlineText == null)
- {
- throw new ArgumentNullException(nameof(inlineText));
- }
-
- var parameterPolicy = ParameterPolicyActivator.ResolveParameterPolicy<IParameterPolicy>(
- _options.ConstraintMap,
- _serviceProvider,
- inlineText,
- out var parameterPolicyKey);
+ return parameterPolicy;
+ }
- if (parameterPolicy == null)
- {
- throw new InvalidOperationException(Resources.FormatRoutePattern_ConstraintReferenceNotFound(
- parameterPolicyKey,
- typeof(RouteOptions),
- nameof(RouteOptions.ConstraintMap)));
- }
+ public override IParameterPolicy Create(RoutePatternParameterPart? parameter, string inlineText)
+ {
+ if (inlineText == null)
+ {
+ throw new ArgumentNullException(nameof(inlineText));
+ }
- if (parameterPolicy is IRouteConstraint constraint)
- {
- return InitializeRouteConstraint(parameter?.IsOptional ?? false, constraint);
- }
+ var parameterPolicy = ParameterPolicyActivator.ResolveParameterPolicy<IParameterPolicy>(
+ _options.ConstraintMap,
+ _serviceProvider,
+ inlineText,
+ out var parameterPolicyKey);
- return parameterPolicy;
+ if (parameterPolicy == null)
+ {
+ throw new InvalidOperationException(Resources.FormatRoutePattern_ConstraintReferenceNotFound(
+ parameterPolicyKey,
+ typeof(RouteOptions),
+ nameof(RouteOptions.ConstraintMap)));
}
- private static IParameterPolicy InitializeRouteConstraint(
- bool optional,
- IRouteConstraint routeConstraint)
+ if (parameterPolicy is IRouteConstraint constraint)
{
- if (optional)
- {
- routeConstraint = new OptionalRouteConstraint(routeConstraint);
- }
+ return InitializeRouteConstraint(parameter?.IsOptional ?? false, constraint);
+ }
- return routeConstraint;
+ return parameterPolicy;
+ }
+
+ private static IParameterPolicy InitializeRouteConstraint(
+ bool optional,
+ IRouteConstraint routeConstraint)
+ {
+ if (optional)
+ {
+ routeConstraint = new OptionalRouteConstraint(routeConstraint);
}
+
+ return routeConstraint;
}
}
diff --git a/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs
index f11f13628b..2c225672e5 100644
--- a/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs
+++ b/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs
@@ -14,121 +14,120 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
-namespace Microsoft.Extensions.DependencyInjection
+namespace Microsoft.Extensions.DependencyInjection;
+
+/// <summary>
+/// Contains extension methods to <see cref="IServiceCollection"/>.
+/// </summary>
+public static class RoutingServiceCollectionExtensions
{
/// <summary>
- /// Contains extension methods to <see cref="IServiceCollection"/>.
+ /// Adds services required for routing requests.
/// </summary>
- public static class RoutingServiceCollectionExtensions
+ /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
+ /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
+ public static IServiceCollection AddRouting(this IServiceCollection services)
{
- /// <summary>
- /// Adds services required for routing requests.
- /// </summary>
- /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
- /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
- public static IServiceCollection AddRouting(this IServiceCollection services)
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
+ services.TryAddTransient<ObjectPoolProvider, DefaultObjectPoolProvider>();
+ services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
+ {
+ var provider = s.GetRequiredService<ObjectPoolProvider>();
+ return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy());
+ });
+
+ // The TreeRouteBuilder is a builder for creating routes, it should stay transient because it's
+ // stateful.
+ services.TryAdd(ServiceDescriptor.Transient<TreeRouteBuilder>(s =>
+ {
+ var loggerFactory = s.GetRequiredService<ILoggerFactory>();
+ var objectPool = s.GetRequiredService<ObjectPool<UriBuildingContext>>();
+ var constraintResolver = s.GetRequiredService<IInlineConstraintResolver>();
+ return new TreeRouteBuilder(loggerFactory, objectPool, constraintResolver);
+ }));
+
+ services.TryAddSingleton(typeof(RoutingMarkerService));
+
+ // Setup global collection of endpoint data sources
+ var dataSources = new ObservableCollection<EndpointDataSource>();
+ services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>(
+ serviceProvider => new ConfigureRouteOptions(dataSources)));
+
+ // Allow global access to the list of endpoints.
+ services.TryAddSingleton<EndpointDataSource>(s =>
{
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
-
- services.TryAddTransient<IInlineConstraintResolver, DefaultInlineConstraintResolver>();
- services.TryAddTransient<ObjectPoolProvider, DefaultObjectPoolProvider>();
- services.TryAddSingleton<ObjectPool<UriBuildingContext>>(s =>
- {
- var provider = s.GetRequiredService<ObjectPoolProvider>();
- return provider.Create<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy());
- });
-
- // The TreeRouteBuilder is a builder for creating routes, it should stay transient because it's
- // stateful.
- services.TryAdd(ServiceDescriptor.Transient<TreeRouteBuilder>(s =>
- {
- var loggerFactory = s.GetRequiredService<ILoggerFactory>();
- var objectPool = s.GetRequiredService<ObjectPool<UriBuildingContext>>();
- var constraintResolver = s.GetRequiredService<IInlineConstraintResolver>();
- return new TreeRouteBuilder(loggerFactory, objectPool, constraintResolver);
- }));
-
- services.TryAddSingleton(typeof(RoutingMarkerService));
-
- // Setup global collection of endpoint data sources
- var dataSources = new ObservableCollection<EndpointDataSource>();
- services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, ConfigureRouteOptions>(
- serviceProvider => new ConfigureRouteOptions(dataSources)));
-
- // Allow global access to the list of endpoints.
- services.TryAddSingleton<EndpointDataSource>(s =>
- {
// Call internal ctor and pass global collection
return new CompositeEndpointDataSource(dataSources);
- });
-
- //
- // Default matcher implementation
- //
- services.TryAddSingleton<ParameterPolicyFactory, DefaultParameterPolicyFactory>();
- services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
- services.TryAddTransient<DfaMatcherBuilder>();
- services.TryAddSingleton<DfaGraphWriter>();
- services.TryAddTransient<DataSourceDependentMatcher.Lifetime>();
- services.TryAddSingleton<EndpointMetadataComparer>(services =>
- {
+ });
+
+ //
+ // Default matcher implementation
+ //
+ services.TryAddSingleton<ParameterPolicyFactory, DefaultParameterPolicyFactory>();
+ services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
+ services.TryAddTransient<DfaMatcherBuilder>();
+ services.TryAddSingleton<DfaGraphWriter>();
+ services.TryAddTransient<DataSourceDependentMatcher.Lifetime>();
+ services.TryAddSingleton<EndpointMetadataComparer>(services =>
+ {
// This has no public constructor.
return new EndpointMetadataComparer(services);
- });
-
- // Link generation related services
- services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
- services.TryAddSingleton<IEndpointAddressScheme<string>, EndpointNameAddressScheme>();
- services.TryAddSingleton<IEndpointAddressScheme<RouteValuesAddress>, RouteValuesAddressScheme>();
- services.TryAddSingleton<LinkParser, DefaultLinkParser>();
-
- //
- // Endpoint Selection
- //
- services.TryAddSingleton<EndpointSelector, DefaultEndpointSelector>();
- services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, HttpMethodMatcherPolicy>());
- services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, HostMatcherPolicy>());
- services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, AcceptsMatcherPolicy>());
-
- //
- // Misc infrastructure
- //
- services.TryAddSingleton<TemplateBinderFactory, DefaultTemplateBinderFactory>();
- services.TryAddSingleton<RoutePatternTransformer, DefaultRoutePatternTransformer>();
-
- // Set RouteHandlerOptions.ThrowOnBadRequest in development
- services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteHandlerOptions>, ConfigureRouteHandlerOptions>());
-
- return services;
- }
+ });
+
+ // Link generation related services
+ services.TryAddSingleton<LinkGenerator, DefaultLinkGenerator>();
+ services.TryAddSingleton<IEndpointAddressScheme<string>, EndpointNameAddressScheme>();
+ services.TryAddSingleton<IEndpointAddressScheme<RouteValuesAddress>, RouteValuesAddressScheme>();
+ services.TryAddSingleton<LinkParser, DefaultLinkParser>();
+
+ //
+ // Endpoint Selection
+ //
+ services.TryAddSingleton<EndpointSelector, DefaultEndpointSelector>();
+ services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, HttpMethodMatcherPolicy>());
+ services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, HostMatcherPolicy>());
+ services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, AcceptsMatcherPolicy>());
+
+ //
+ // Misc infrastructure
+ //
+ services.TryAddSingleton<TemplateBinderFactory, DefaultTemplateBinderFactory>();
+ services.TryAddSingleton<RoutePatternTransformer, DefaultRoutePatternTransformer>();
+
+ // Set RouteHandlerOptions.ThrowOnBadRequest in development
+ services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteHandlerOptions>, ConfigureRouteHandlerOptions>());
+
+ return services;
+ }
- /// <summary>
- /// Adds services required for routing requests.
- /// </summary>
- /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
- /// <param name="configureOptions">The routing options to configure the middleware with.</param>
- /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
- public static IServiceCollection AddRouting(
- this IServiceCollection services,
- Action<RouteOptions> configureOptions)
+ /// <summary>
+ /// Adds services required for routing requests.
+ /// </summary>
+ /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
+ /// <param name="configureOptions">The routing options to configure the middleware with.</param>
+ /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
+ public static IServiceCollection AddRouting(
+ this IServiceCollection services,
+ Action<RouteOptions> configureOptions)
+ {
+ if (services == null)
{
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
+ throw new ArgumentNullException(nameof(services));
+ }
- if (configureOptions == null)
- {
- throw new ArgumentNullException(nameof(configureOptions));
- }
+ if (configureOptions == null)
+ {
+ throw new ArgumentNullException(nameof(configureOptions));
+ }
- services.Configure(configureOptions);
- services.AddRouting();
+ services.Configure(configureOptions);
+ services.AddRouting();
- return services;
- }
+ return services;
}
}
diff --git a/src/Http/Routing/src/EndpointDataSource.cs b/src/Http/Routing/src/EndpointDataSource.cs
index 9f714fd6ea..4e83b57314 100644
--- a/src/Http/Routing/src/EndpointDataSource.cs
+++ b/src/Http/Routing/src/EndpointDataSource.cs
@@ -5,23 +5,22 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Provides a collection of <see cref="Endpoint"/> instances.
+/// </summary>
+public abstract class EndpointDataSource
{
/// <summary>
- /// Provides a collection of <see cref="Endpoint"/> instances.
+ /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
+ /// instances.
/// </summary>
- public abstract class EndpointDataSource
- {
- /// <summary>
- /// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
- /// instances.
- /// </summary>
- /// <returns>The <see cref="IChangeToken"/>.</returns>
- public abstract IChangeToken GetChangeToken();
+ /// <returns>The <see cref="IChangeToken"/>.</returns>
+ public abstract IChangeToken GetChangeToken();
- /// <summary>
- /// Returns a read-only collection of <see cref="Endpoint"/> instances.
- /// </summary>
- public abstract IReadOnlyList<Endpoint> Endpoints { get; }
- }
+ /// <summary>
+ /// Returns a read-only collection of <see cref="Endpoint"/> instances.
+ /// </summary>
+ public abstract IReadOnlyList<Endpoint> Endpoints { get; }
}
diff --git a/src/Http/Routing/src/EndpointGroupNameAttribute.cs b/src/Http/Routing/src/EndpointGroupNameAttribute.cs
index 68511b6ca9..3709fd7839 100644
--- a/src/Http/Routing/src/EndpointGroupNameAttribute.cs
+++ b/src/Http/Routing/src/EndpointGroupNameAttribute.cs
@@ -4,29 +4,28 @@
using System;
using Microsoft.AspNetCore.Http;
- namespace Microsoft.AspNetCore.Routing
- {
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Specifies the endpoint group name in <see cref="Microsoft.AspNetCore.Http.Endpoint.Metadata"/>.
+/// </summary>
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+public sealed class EndpointGroupNameAttribute : Attribute, IEndpointGroupNameMetadata
+{
/// <summary>
- /// Specifies the endpoint group name in <see cref="Microsoft.AspNetCore.Http.Endpoint.Metadata"/>.
+ /// Initializes an instance of the <see cref="EndpointGroupNameAttribute"/>.
/// </summary>
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate | AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
- public sealed class EndpointGroupNameAttribute : Attribute, IEndpointGroupNameMetadata
+ /// <param name="endpointGroupName">The endpoint group name.</param>
+ public EndpointGroupNameAttribute(string endpointGroupName)
{
- /// <summary>
- /// Initializes an instance of the <see cref="EndpointGroupNameAttribute"/>.
- /// </summary>
- /// <param name="endpointGroupName">The endpoint group name.</param>
- public EndpointGroupNameAttribute(string endpointGroupName)
+ if (endpointGroupName == null)
{
- if (endpointGroupName == null)
- {
- throw new ArgumentNullException(nameof(endpointGroupName));
- }
-
- EndpointGroupName = endpointGroupName;
+ throw new ArgumentNullException(nameof(endpointGroupName));
}
- /// <inheritdoc />
- public string EndpointGroupName { get; }
+ EndpointGroupName = endpointGroupName;
}
- } \ No newline at end of file
+
+ /// <inheritdoc />
+ public string EndpointGroupName { get; }
+}
diff --git a/src/Http/Routing/src/EndpointMiddleware.cs b/src/Http/Routing/src/EndpointMiddleware.cs
index 41919ff00c..d04af317c0 100644
--- a/src/Http/Routing/src/EndpointMiddleware.cs
+++ b/src/Http/Routing/src/EndpointMiddleware.cs
@@ -7,105 +7,104 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed partial class EndpointMiddleware
{
- internal sealed partial class EndpointMiddleware
- {
- internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareWithEndpointInvoked";
- internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareWithEndpointInvoked";
+ internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareWithEndpointInvoked";
+ internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareWithEndpointInvoked";
- private readonly ILogger _logger;
- private readonly RequestDelegate _next;
- private readonly RouteOptions _routeOptions;
+ private readonly ILogger _logger;
+ private readonly RequestDelegate _next;
+ private readonly RouteOptions _routeOptions;
- public EndpointMiddleware(
- ILogger<EndpointMiddleware> logger,
- RequestDelegate next,
- IOptions<RouteOptions> routeOptions)
- {
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _next = next ?? throw new ArgumentNullException(nameof(next));
- _routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
- }
+ public EndpointMiddleware(
+ ILogger<EndpointMiddleware> logger,
+ RequestDelegate next,
+ IOptions<RouteOptions> routeOptions)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _next = next ?? throw new ArgumentNullException(nameof(next));
+ _routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
+ }
- public Task Invoke(HttpContext httpContext)
+ public Task Invoke(HttpContext httpContext)
+ {
+ var endpoint = httpContext.GetEndpoint();
+ if (endpoint?.RequestDelegate != null)
{
- var endpoint = httpContext.GetEndpoint();
- if (endpoint?.RequestDelegate != null)
+ if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
{
- if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
+ if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
+ !httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
- if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
- !httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
- {
- ThrowMissingAuthMiddlewareException(endpoint);
- }
-
- if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
- !httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
- {
- ThrowMissingCorsMiddlewareException(endpoint);
- }
+ ThrowMissingAuthMiddlewareException(endpoint);
}
- Log.ExecutingEndpoint(_logger, endpoint);
-
- try
+ if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
+ !httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
{
- var requestTask = endpoint.RequestDelegate(httpContext);
- if (!requestTask.IsCompletedSuccessfully)
- {
- return AwaitRequestTask(endpoint, requestTask, _logger);
- }
+ ThrowMissingCorsMiddlewareException(endpoint);
}
- catch (Exception exception)
- {
- Log.ExecutedEndpoint(_logger, endpoint);
- return Task.FromException(exception);
- }
-
- Log.ExecutedEndpoint(_logger, endpoint);
- return Task.CompletedTask;
}
- return _next(httpContext);
+ Log.ExecutingEndpoint(_logger, endpoint);
- static async Task AwaitRequestTask(Endpoint endpoint, Task requestTask, ILogger logger)
+ try
{
- try
- {
- await requestTask;
- }
- finally
+ var requestTask = endpoint.RequestDelegate(httpContext);
+ if (!requestTask.IsCompletedSuccessfully)
{
- Log.ExecutedEndpoint(logger, endpoint);
+ return AwaitRequestTask(endpoint, requestTask, _logger);
}
}
- }
+ catch (Exception exception)
+ {
+ Log.ExecutedEndpoint(_logger, endpoint);
+ return Task.FromException(exception);
+ }
- private static void ThrowMissingAuthMiddlewareException(Endpoint endpoint)
- {
- throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains authorization metadata, " +
- "but a middleware was not found that supports authorization." +
- Environment.NewLine +
- "Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.");
+ Log.ExecutedEndpoint(_logger, endpoint);
+ return Task.CompletedTask;
}
- private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
+ return _next(httpContext);
+
+ static async Task AwaitRequestTask(Endpoint endpoint, Task requestTask, ILogger logger)
{
- throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains CORS metadata, " +
- "but a middleware was not found that supports CORS." +
- Environment.NewLine +
- "Configure your application startup by adding app.UseCors() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.");
+ try
+ {
+ await requestTask;
+ }
+ finally
+ {
+ Log.ExecutedEndpoint(logger, endpoint);
+ }
}
+ }
- private static partial class Log
- {
- [LoggerMessage(0, LogLevel.Information, "Executing endpoint '{EndpointName}'", EventName = "ExecutingEndpoint")]
- public static partial void ExecutingEndpoint(ILogger logger, Endpoint endpointName);
+ private static void ThrowMissingAuthMiddlewareException(Endpoint endpoint)
+ {
+ throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains authorization metadata, " +
+ "but a middleware was not found that supports authorization." +
+ Environment.NewLine +
+ "Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.");
+ }
- [LoggerMessage(1, LogLevel.Information, "Executed endpoint '{EndpointName}'", EventName = "ExecutedEndpoint")]
- public static partial void ExecutedEndpoint(ILogger logger, Endpoint endpointName);
- }
+ private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
+ {
+ throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains CORS metadata, " +
+ "but a middleware was not found that supports CORS." +
+ Environment.NewLine +
+ "Configure your application startup by adding app.UseCors() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.");
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(0, LogLevel.Information, "Executing endpoint '{EndpointName}'", EventName = "ExecutingEndpoint")]
+ public static partial void ExecutingEndpoint(ILogger logger, Endpoint endpointName);
+
+ [LoggerMessage(1, LogLevel.Information, "Executed endpoint '{EndpointName}'", EventName = "ExecutedEndpoint")]
+ public static partial void ExecutedEndpoint(ILogger logger, Endpoint endpointName);
}
}
diff --git a/src/Http/Routing/src/EndpointNameAddressScheme.cs b/src/Http/Routing/src/EndpointNameAddressScheme.cs
index ef057acac1..c1fe9cdade 100644
--- a/src/Http/Routing/src/EndpointNameAddressScheme.cs
+++ b/src/Http/Routing/src/EndpointNameAddressScheme.cs
@@ -7,105 +7,104 @@ using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed class EndpointNameAddressScheme : IEndpointAddressScheme<string>, IDisposable
{
- internal sealed class EndpointNameAddressScheme : IEndpointAddressScheme<string>, IDisposable
+ private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
+
+ public EndpointNameAddressScheme(EndpointDataSource dataSource)
{
- private readonly DataSourceDependentCache<Dictionary<string, Endpoint[]>> _cache;
+ _cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize);
+ }
+
+ // Internal for tests
+ internal Dictionary<string, Endpoint[]> Entries => _cache.EnsureInitialized();
- public EndpointNameAddressScheme(EndpointDataSource dataSource)
+ public IEnumerable<Endpoint> FindEndpoints(string address)
+ {
+ if (address == null)
{
- _cache = new DataSourceDependentCache<Dictionary<string, Endpoint[]>>(dataSource, Initialize);
+ throw new ArgumentNullException(nameof(address));
}
- // Internal for tests
- internal Dictionary<string, Endpoint[]> Entries => _cache.EnsureInitialized();
+ // Capture the current value of the cache
+ var entries = Entries;
- public IEnumerable<Endpoint> FindEndpoints(string address)
- {
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
-
- // Capture the current value of the cache
- var entries = Entries;
+ entries.TryGetValue(address, out var result);
+ return result ?? Array.Empty<Endpoint>();
+ }
- entries.TryGetValue(address, out var result);
- return result ?? Array.Empty<Endpoint>();
- }
+ private static Dictionary<string, Endpoint[]> Initialize(IReadOnlyList<Endpoint> endpoints)
+ {
+ // Collect duplicates as we go, blow up on startup if we find any.
+ var hasDuplicates = false;
- private static Dictionary<string, Endpoint[]> Initialize(IReadOnlyList<Endpoint> endpoints)
+ var entries = new Dictionary<string, Endpoint[]>(StringComparer.Ordinal);
+ for (var i = 0; i < endpoints.Count; i++)
{
- // Collect duplicates as we go, blow up on startup if we find any.
- var hasDuplicates = false;
+ var endpoint = endpoints[i];
- var entries = new Dictionary<string, Endpoint[]>(StringComparer.Ordinal);
- for (var i = 0; i < endpoints.Count; i++)
+ var endpointName = GetEndpointName(endpoint);
+ if (endpointName == null)
{
- var endpoint = endpoints[i];
-
- var endpointName = GetEndpointName(endpoint);
- if (endpointName == null)
- {
- continue;
- }
-
- if (!entries.TryGetValue(endpointName, out var existing))
- {
- // This isn't a duplicate (so far)
- entries[endpointName] = new[] { endpoint };
- continue;
- }
-
- // Ok this is a duplicate, because we have two endpoints with the same name. Bail out, because we
- // are just going to throw, we don't need to finish collecting data.
- hasDuplicates = true;
- break;
+ continue;
}
- if (!hasDuplicates)
+ if (!entries.TryGetValue(endpointName, out var existing))
{
- // No duplicates, success!
- return entries;
+ // This isn't a duplicate (so far)
+ entries[endpointName] = new[] { endpoint };
+ continue;
}
- // OK we need to report some duplicates.
- var duplicates = endpoints
- .GroupBy(e => GetEndpointName(e))
- .Where(g => g.Key != null && g.Count() > 1);
+ // Ok this is a duplicate, because we have two endpoints with the same name. Bail out, because we
+ // are just going to throw, we don't need to finish collecting data.
+ hasDuplicates = true;
+ break;
+ }
- var builder = new StringBuilder();
- builder.AppendLine(Resources.DuplicateEndpointNameHeader);
+ if (!hasDuplicates)
+ {
+ // No duplicates, success!
+ return entries;
+ }
- foreach (var group in duplicates)
- {
- builder.AppendLine();
- builder.AppendLine(Resources.FormatDuplicateEndpointNameEntry(group.Key));
+ // OK we need to report some duplicates.
+ var duplicates = endpoints
+ .GroupBy(e => GetEndpointName(e))
+ .Where(g => g.Key != null && g.Count() > 1);
- foreach (var endpoint in group)
- {
- builder.AppendLine(endpoint.DisplayName);
- }
- }
+ var builder = new StringBuilder();
+ builder.AppendLine(Resources.DuplicateEndpointNameHeader);
- throw new InvalidOperationException(builder.ToString());
+ foreach (var group in duplicates)
+ {
+ builder.AppendLine();
+ builder.AppendLine(Resources.FormatDuplicateEndpointNameEntry(group.Key));
- string? GetEndpointName(Endpoint endpoint)
+ foreach (var endpoint in group)
{
- if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true)
- {
- // Skip anything that's suppressed for linking.
- return null;
- }
-
- return endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName;
+ builder.AppendLine(endpoint.DisplayName);
}
}
- public void Dispose()
+ throw new InvalidOperationException(builder.ToString());
+
+ string? GetEndpointName(Endpoint endpoint)
{
- _cache.Dispose();
+ if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true)
+ {
+ // Skip anything that's suppressed for linking.
+ return null;
+ }
+
+ return endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName;
}
}
+
+ public void Dispose()
+ {
+ _cache.Dispose();
+ }
}
diff --git a/src/Http/Routing/src/EndpointNameAttribute.cs b/src/Http/Routing/src/EndpointNameAttribute.cs
index 9692dc8321..fe1ac9a7cc 100644
--- a/src/Http/Routing/src/EndpointNameAttribute.cs
+++ b/src/Http/Routing/src/EndpointNameAttribute.cs
@@ -3,34 +3,33 @@
using System;
using Microsoft.AspNetCore.Http;
-
- namespace Microsoft.AspNetCore.Routing
- {
+
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Specifies the endpoint name in <see cref="Endpoint.Metadata"/>.
+/// </summary>
+/// <remarks>
+/// Endpoint names must be unique within an application, and can be used to unambiguously
+/// identify a desired endpoint for URI generation using <see cref="Microsoft.AspNetCore.Routing.LinkGenerator"/>
+/// </remarks>
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate, Inherited = false, AllowMultiple = false)]
+public sealed class EndpointNameAttribute : Attribute, IEndpointNameMetadata
+{
/// <summary>
- /// Specifies the endpoint name in <see cref="Endpoint.Metadata"/>.
+ /// Initializes an instance of the EndpointNameAttribute.
/// </summary>
- /// <remarks>
- /// Endpoint names must be unique within an application, and can be used to unambiguously
- /// identify a desired endpoint for URI generation using <see cref="Microsoft.AspNetCore.Routing.LinkGenerator"/>
- /// </remarks>
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Delegate, Inherited = false, AllowMultiple = false)]
- public sealed class EndpointNameAttribute : Attribute, IEndpointNameMetadata
+ /// <param name="endpointName">The endpoint name.</param>
+ public EndpointNameAttribute(string endpointName)
{
- /// <summary>
- /// Initializes an instance of the EndpointNameAttribute.
- /// </summary>
- /// <param name="endpointName">The endpoint name.</param>
- public EndpointNameAttribute(string endpointName)
+ if (endpointName == null)
{
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
-
- EndpointName = endpointName;
+ throw new ArgumentNullException(nameof(endpointName));
}
- /// <inheritdoc />
- public string EndpointName { get; }
+ EndpointName = endpointName;
}
- } \ No newline at end of file
+
+ /// <inheritdoc />
+ public string EndpointName { get; }
+}
diff --git a/src/Http/Routing/src/EndpointNameMetadata.cs b/src/Http/Routing/src/EndpointNameMetadata.cs
index 7584675ad2..a33949e060 100644
--- a/src/Http/Routing/src/EndpointNameMetadata.cs
+++ b/src/Http/Routing/src/EndpointNameMetadata.cs
@@ -4,34 +4,33 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Specifies an endpoint name in <see cref="Endpoint.Metadata"/>.
+/// </summary>
+/// <remarks>
+/// Endpoint names must be unique within an application, and can be used to unambiguously
+/// identify a desired endpoint for URI generation using <see cref="LinkGenerator"/>.
+/// </remarks>
+public class EndpointNameMetadata : IEndpointNameMetadata
{
/// <summary>
- /// Specifies an endpoint name in <see cref="Endpoint.Metadata"/>.
+ /// Creates a new instance of <see cref="EndpointNameMetadata"/> with the provided endpoint name.
/// </summary>
- /// <remarks>
- /// Endpoint names must be unique within an application, and can be used to unambiguously
- /// identify a desired endpoint for URI generation using <see cref="LinkGenerator"/>.
- /// </remarks>
- public class EndpointNameMetadata : IEndpointNameMetadata
+ /// <param name="endpointName">The endpoint name.</param>
+ public EndpointNameMetadata(string endpointName)
{
- /// <summary>
- /// Creates a new instance of <see cref="EndpointNameMetadata"/> with the provided endpoint name.
- /// </summary>
- /// <param name="endpointName">The endpoint name.</param>
- public EndpointNameMetadata(string endpointName)
+ if (endpointName == null)
{
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
-
- EndpointName = endpointName;
+ throw new ArgumentNullException(nameof(endpointName));
}
- /// <summary>
- /// Gets the endpoint name.
- /// </summary>
- public string EndpointName { get; }
+ EndpointName = endpointName;
}
+
+ /// <summary>
+ /// Gets the endpoint name.
+ /// </summary>
+ public string EndpointName { get; }
}
diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs
index 5bf49b7f54..772556e04f 100644
--- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs
+++ b/src/Http/Routing/src/EndpointRoutingMiddleware.cs
@@ -10,172 +10,171 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed partial class EndpointRoutingMiddleware
{
- internal sealed partial class EndpointRoutingMiddleware
+ private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
+
+ private readonly MatcherFactory _matcherFactory;
+ private readonly ILogger _logger;
+ private readonly EndpointDataSource _endpointDataSource;
+ private readonly DiagnosticListener _diagnosticListener;
+ private readonly RequestDelegate _next;
+
+ private Task<Matcher>? _initializationTask;
+
+ public EndpointRoutingMiddleware(
+ MatcherFactory matcherFactory,
+ ILogger<EndpointRoutingMiddleware> logger,
+ IEndpointRouteBuilder endpointRouteBuilder,
+ DiagnosticListener diagnosticListener,
+ RequestDelegate next)
{
- private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
-
- private readonly MatcherFactory _matcherFactory;
- private readonly ILogger _logger;
- private readonly EndpointDataSource _endpointDataSource;
- private readonly DiagnosticListener _diagnosticListener;
- private readonly RequestDelegate _next;
-
- private Task<Matcher>? _initializationTask;
-
- public EndpointRoutingMiddleware(
- MatcherFactory matcherFactory,
- ILogger<EndpointRoutingMiddleware> logger,
- IEndpointRouteBuilder endpointRouteBuilder,
- DiagnosticListener diagnosticListener,
- RequestDelegate next)
+ if (endpointRouteBuilder == null)
{
- if (endpointRouteBuilder == null)
- {
- throw new ArgumentNullException(nameof(endpointRouteBuilder));
- }
+ throw new ArgumentNullException(nameof(endpointRouteBuilder));
+ }
- _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
- _next = next ?? throw new ArgumentNullException(nameof(next));
+ _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
+ _next = next ?? throw new ArgumentNullException(nameof(next));
- _endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
+ _endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
+ }
+
+ public Task Invoke(HttpContext httpContext)
+ {
+ // There's already an endpoint, skip matching completely
+ var endpoint = httpContext.GetEndpoint();
+ if (endpoint != null)
+ {
+ Log.MatchSkipped(_logger, endpoint);
+ return _next(httpContext);
}
- public Task Invoke(HttpContext httpContext)
+ // There's an inherent race condition between waiting for init and accessing the matcher
+ // this is OK because once `_matcher` is initialized, it will not be set to null again.
+ var matcherTask = InitializeAsync();
+ if (!matcherTask.IsCompletedSuccessfully)
{
- // There's already an endpoint, skip matching completely
- var endpoint = httpContext.GetEndpoint();
- if (endpoint != null)
- {
- Log.MatchSkipped(_logger, endpoint);
- return _next(httpContext);
- }
+ return AwaitMatcher(this, httpContext, matcherTask);
+ }
- // There's an inherent race condition between waiting for init and accessing the matcher
- // this is OK because once `_matcher` is initialized, it will not be set to null again.
- var matcherTask = InitializeAsync();
- if (!matcherTask.IsCompletedSuccessfully)
- {
- return AwaitMatcher(this, httpContext, matcherTask);
- }
+ var matchTask = matcherTask.Result.MatchAsync(httpContext);
+ if (!matchTask.IsCompletedSuccessfully)
+ {
+ return AwaitMatch(this, httpContext, matchTask);
+ }
- var matchTask = matcherTask.Result.MatchAsync(httpContext);
- if (!matchTask.IsCompletedSuccessfully)
- {
- return AwaitMatch(this, httpContext, matchTask);
- }
+ return SetRoutingAndContinue(httpContext);
- return SetRoutingAndContinue(httpContext);
+ // Awaited fallbacks for when the Tasks do not synchronously complete
+ static async Task AwaitMatcher(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task<Matcher> matcherTask)
+ {
+ var matcher = await matcherTask;
+ await matcher.MatchAsync(httpContext);
+ await middleware.SetRoutingAndContinue(httpContext);
+ }
- // Awaited fallbacks for when the Tasks do not synchronously complete
- static async Task AwaitMatcher(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task<Matcher> matcherTask)
- {
- var matcher = await matcherTask;
- await matcher.MatchAsync(httpContext);
- await middleware.SetRoutingAndContinue(httpContext);
- }
+ static async Task AwaitMatch(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
+ {
+ await matchTask;
+ await middleware.SetRoutingAndContinue(httpContext);
+ }
- static async Task AwaitMatch(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
- {
- await matchTask;
- await middleware.SetRoutingAndContinue(httpContext);
- }
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Task SetRoutingAndContinue(HttpContext httpContext)
+ {
+ // If there was no mutation of the endpoint then log failure
+ var endpoint = httpContext.GetEndpoint();
+ if (endpoint == null)
+ {
+ Log.MatchFailure(_logger);
}
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private Task SetRoutingAndContinue(HttpContext httpContext)
+ else
{
- // If there was no mutation of the endpoint then log failure
- var endpoint = httpContext.GetEndpoint();
- if (endpoint == null)
- {
- Log.MatchFailure(_logger);
- }
- else
+ // Raise an event if the route matched
+ if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
{
- // Raise an event if the route matched
- if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
- {
- // We're just going to send the HttpContext since it has all of the relevant information
- _diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
- }
-
- Log.MatchSuccess(_logger, endpoint);
+ // We're just going to send the HttpContext since it has all of the relevant information
+ _diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
}
- return _next(httpContext);
+ Log.MatchSuccess(_logger, endpoint);
}
- // Initialization is async to avoid blocking threads while reflection and things
- // of that nature take place.
- //
- // We've seen cases where startup is very slow if we allow multiple threads to race
- // while initializing the set of endpoints/routes. Doing CPU intensive work is a
- // blocking operation if you have a low core count and enough work to do.
- private Task<Matcher> InitializeAsync()
- {
- var initializationTask = _initializationTask;
- if (initializationTask != null)
- {
- return initializationTask;
- }
+ return _next(httpContext);
+ }
- return InitializeCoreAsync();
+ // Initialization is async to avoid blocking threads while reflection and things
+ // of that nature take place.
+ //
+ // We've seen cases where startup is very slow if we allow multiple threads to race
+ // while initializing the set of endpoints/routes. Doing CPU intensive work is a
+ // blocking operation if you have a low core count and enough work to do.
+ private Task<Matcher> InitializeAsync()
+ {
+ var initializationTask = _initializationTask;
+ if (initializationTask != null)
+ {
+ return initializationTask;
}
- private Task<Matcher> InitializeCoreAsync()
+ return InitializeCoreAsync();
+ }
+
+ private Task<Matcher> InitializeCoreAsync()
+ {
+ var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
+ var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
+ if (initializationTask != null)
{
- var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
- var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
- if (initializationTask != null)
- {
- // This thread lost the race, join the existing task.
- return initializationTask;
- }
+ // This thread lost the race, join the existing task.
+ return initializationTask;
+ }
- // This thread won the race, do the initialization.
- try
- {
- var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
+ // This thread won the race, do the initialization.
+ try
+ {
+ var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
- _initializationTask = Task.FromResult(matcher);
+ _initializationTask = Task.FromResult(matcher);
- // Complete the task, this will unblock any requests that came in while initializing.
- initialization.SetResult(matcher);
- return initialization.Task;
- }
- catch (Exception ex)
- {
- // Allow initialization to occur again. Since DataSources can change, it's possible
- // for the developer to correct the data causing the failure.
- _initializationTask = null;
+ // Complete the task, this will unblock any requests that came in while initializing.
+ initialization.SetResult(matcher);
+ return initialization.Task;
+ }
+ catch (Exception ex)
+ {
+ // Allow initialization to occur again. Since DataSources can change, it's possible
+ // for the developer to correct the data causing the failure.
+ _initializationTask = null;
- // Complete the task, this will throw for any requests that came in while initializing.
- initialization.SetException(ex);
- return initialization.Task;
- }
+ // Complete the task, this will throw for any requests that came in while initializing.
+ initialization.SetException(ex);
+ return initialization.Task;
}
+ }
- private static partial class Log
- {
- public static void MatchSuccess(ILogger logger, Endpoint endpoint)
- => MatchSuccess(logger, endpoint.DisplayName);
+ private static partial class Log
+ {
+ public static void MatchSuccess(ILogger logger, Endpoint endpoint)
+ => MatchSuccess(logger, endpoint.DisplayName);
- [LoggerMessage(1, LogLevel.Debug, "Request matched endpoint '{EndpointName}'", EventName = "MatchSuccess")]
- private static partial void MatchSuccess(ILogger logger, string? endpointName);
+ [LoggerMessage(1, LogLevel.Debug, "Request matched endpoint '{EndpointName}'", EventName = "MatchSuccess")]
+ private static partial void MatchSuccess(ILogger logger, string? endpointName);
- [LoggerMessage(2, LogLevel.Debug, "Request did not match any endpoints", EventName = "MatchFailure")]
- public static partial void MatchFailure(ILogger logger);
+ [LoggerMessage(2, LogLevel.Debug, "Request did not match any endpoints", EventName = "MatchFailure")]
+ public static partial void MatchFailure(ILogger logger);
- public static void MatchSkipped(ILogger logger, Endpoint endpoint)
- => MatchingSkipped(logger, endpoint.DisplayName);
+ public static void MatchSkipped(ILogger logger, Endpoint endpoint)
+ => MatchingSkipped(logger, endpoint.DisplayName);
- [LoggerMessage(3, LogLevel.Debug, "Endpoint '{EndpointName}' already set, skipping route matching.", EventName = "MatchingSkipped")]
- private static partial void MatchingSkipped(ILogger logger, string? endpointName);
- }
+ [LoggerMessage(3, LogLevel.Debug, "Endpoint '{EndpointName}' already set, skipping route matching.", EventName = "MatchingSkipped")]
+ private static partial void MatchingSkipped(ILogger logger, string? endpointName);
}
}
diff --git a/src/Http/Routing/src/ExcludeFromDescriptionAttribute.cs b/src/Http/Routing/src/ExcludeFromDescriptionAttribute.cs
index 6aeb8426e5..0a4c87c441 100644
--- a/src/Http/Routing/src/ExcludeFromDescriptionAttribute.cs
+++ b/src/Http/Routing/src/ExcludeFromDescriptionAttribute.cs
@@ -4,15 +4,14 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Indicates that this <see cref="Endpoint"/> should not be included in the generated API metadata.
+/// </summary>
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Delegate, AllowMultiple = false, Inherited = true)]
+public sealed class ExcludeFromDescriptionAttribute : Attribute, IExcludeFromDescriptionMetadata
{
- /// <summary>
- /// Indicates that this <see cref="Endpoint"/> should not be included in the generated API metadata.
- /// </summary>
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Delegate, AllowMultiple = false, Inherited = true)]
- public sealed class ExcludeFromDescriptionAttribute : Attribute, IExcludeFromDescriptionMetadata
- {
- /// <inheritdoc />
- public bool ExcludeFromDescription => true;
- }
+ /// <inheritdoc />
+ public bool ExcludeFromDescription => true;
}
diff --git a/src/Http/Routing/src/HostAttribute.cs b/src/Http/Routing/src/HostAttribute.cs
index be4403eaa0..c9b2c8569c 100644
--- a/src/Http/Routing/src/HostAttribute.cs
+++ b/src/Http/Routing/src/HostAttribute.cs
@@ -6,62 +6,61 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Attribute for providing host metdata that is used during routing.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+public sealed class HostAttribute : Attribute, IHostMetadata
{
/// <summary>
- /// Attribute for providing host metdata that is used during routing.
+ /// Initializes a new instance of the <see cref="HostAttribute" /> class.
/// </summary>
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
- public sealed class HostAttribute : Attribute, IHostMetadata
+ /// <param name="host">
+ /// The host used during routing.
+ /// Host should be Unicode rather than punycode, and may have a port.
+ /// </param>
+ public HostAttribute(string host) : this(new[] { host })
{
- /// <summary>
- /// Initializes a new instance of the <see cref="HostAttribute" /> class.
- /// </summary>
- /// <param name="host">
- /// The host used during routing.
- /// Host should be Unicode rather than punycode, and may have a port.
- /// </param>
- public HostAttribute(string host) : this(new[] { host })
+ if (host == null)
{
- if (host == null)
- {
- throw new ArgumentNullException(nameof(host));
- }
+ throw new ArgumentNullException(nameof(host));
}
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="HostAttribute" /> class.
- /// </summary>
- /// <param name="hosts">
- /// The hosts used during routing.
- /// Hosts should be Unicode rather than punycode, and may have a port.
- /// An empty collection means any host will be accepted.
- /// </param>
- public HostAttribute(params string[] hosts)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HostAttribute" /> class.
+ /// </summary>
+ /// <param name="hosts">
+ /// The hosts used during routing.
+ /// Hosts should be Unicode rather than punycode, and may have a port.
+ /// An empty collection means any host will be accepted.
+ /// </param>
+ public HostAttribute(params string[] hosts)
+ {
+ if (hosts == null)
{
- if (hosts == null)
- {
- throw new ArgumentNullException(nameof(hosts));
- }
-
- Hosts = hosts.ToArray();
+ throw new ArgumentNullException(nameof(hosts));
}
- /// <summary>
- /// Returns a read-only collection of hosts used during routing.
- /// Hosts will be Unicode rather than punycode, and may have a port.
- /// An empty collection means any host will be accepted.
- /// </summary>
- public IReadOnlyList<string> Hosts { get; }
+ Hosts = hosts.ToArray();
+ }
- private string DebuggerToString()
- {
- var hostsDisplay = (Hosts.Count == 0)
- ? "*:*"
- : string.Join(",", Hosts.Select(h => h.Contains(':') ? h : h + ":*"));
+ /// <summary>
+ /// Returns a read-only collection of hosts used during routing.
+ /// Hosts will be Unicode rather than punycode, and may have a port.
+ /// An empty collection means any host will be accepted.
+ /// </summary>
+ public IReadOnlyList<string> Hosts { get; }
- return $"Hosts: {hostsDisplay}";
- }
+ private string DebuggerToString()
+ {
+ var hostsDisplay = (Hosts.Count == 0)
+ ? "*:*"
+ : string.Join(",", Hosts.Select(h => h.Contains(':') ? h : h + ":*"));
+
+ return $"Hosts: {hostsDisplay}";
}
}
diff --git a/src/Http/Routing/src/HttpMethodMetadata.cs b/src/Http/Routing/src/HttpMethodMetadata.cs
index a37701efa7..8586c5b744 100644
--- a/src/Http/Routing/src/HttpMethodMetadata.cs
+++ b/src/Http/Routing/src/HttpMethodMetadata.cs
@@ -7,59 +7,58 @@ using System.Diagnostics;
using System.Linq;
using static Microsoft.AspNetCore.Http.HttpMethods;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents HTTP method metadata used during routing.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+public sealed class HttpMethodMetadata : IHttpMethodMetadata
{
/// <summary>
- /// Represents HTTP method metadata used during routing.
+ /// Initializes a new instance of the <see cref="HttpMethodMetadata" /> class.
/// </summary>
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- public sealed class HttpMethodMetadata : IHttpMethodMetadata
+ /// <param name="httpMethods">
+ /// The HTTP methods used during routing.
+ /// An empty collection means any HTTP method will be accepted.
+ /// </param>
+ public HttpMethodMetadata(IEnumerable<string> httpMethods)
+ : this(httpMethods, acceptCorsPreflight: false)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="HttpMethodMetadata" /> class.
- /// </summary>
- /// <param name="httpMethods">
- /// The HTTP methods used during routing.
- /// An empty collection means any HTTP method will be accepted.
- /// </param>
- public HttpMethodMetadata(IEnumerable<string> httpMethods)
- : this(httpMethods, acceptCorsPreflight: false)
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="HttpMethodMetadata" /> class.
- /// </summary>
- /// <param name="httpMethods">
- /// The HTTP methods used during routing.
- /// An empty collection means any HTTP method will be accepted.
- /// </param>
- /// <param name="acceptCorsPreflight">A value indicating whether routing accepts CORS preflight requests.</param>
- public HttpMethodMetadata(IEnumerable<string> httpMethods, bool acceptCorsPreflight)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpMethodMetadata" /> class.
+ /// </summary>
+ /// <param name="httpMethods">
+ /// The HTTP methods used during routing.
+ /// An empty collection means any HTTP method will be accepted.
+ /// </param>
+ /// <param name="acceptCorsPreflight">A value indicating whether routing accepts CORS preflight requests.</param>
+ public HttpMethodMetadata(IEnumerable<string> httpMethods, bool acceptCorsPreflight)
+ {
+ if (httpMethods == null)
{
- if (httpMethods == null)
- {
- throw new ArgumentNullException(nameof(httpMethods));
- }
-
- HttpMethods = httpMethods.Select(GetCanonicalizedValue).ToArray();
- AcceptCorsPreflight = acceptCorsPreflight;
+ throw new ArgumentNullException(nameof(httpMethods));
}
- /// <summary>
- /// Returns a value indicating whether the associated endpoint should accept CORS preflight requests.
- /// </summary>
- public bool AcceptCorsPreflight { get; }
+ HttpMethods = httpMethods.Select(GetCanonicalizedValue).ToArray();
+ AcceptCorsPreflight = acceptCorsPreflight;
+ }
- /// <summary>
- /// Returns a read-only collection of HTTP methods used during routing.
- /// An empty collection means any HTTP method will be accepted.
- /// </summary>
- public IReadOnlyList<string> HttpMethods { get; }
+ /// <summary>
+ /// Returns a value indicating whether the associated endpoint should accept CORS preflight requests.
+ /// </summary>
+ public bool AcceptCorsPreflight { get; }
- private string DebuggerToString()
- {
- return $"HttpMethods: {string.Join(",", HttpMethods)} - Cors: {AcceptCorsPreflight}";
- }
+ /// <summary>
+ /// Returns a read-only collection of HTTP methods used during routing.
+ /// An empty collection means any HTTP method will be accepted.
+ /// </summary>
+ public IReadOnlyList<string> HttpMethods { get; }
+
+ private string DebuggerToString()
+ {
+ return $"HttpMethods: {string.Join(",", HttpMethods)} - Cors: {AcceptCorsPreflight}";
}
}
diff --git a/src/Http/Routing/src/IDataTokenMetadata.cs b/src/Http/Routing/src/IDataTokenMetadata.cs
index 02a292d02b..6dbf0a2cb0 100644
--- a/src/Http/Routing/src/IDataTokenMetadata.cs
+++ b/src/Http/Routing/src/IDataTokenMetadata.cs
@@ -4,18 +4,17 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Metadata that defines data tokens for an <see cref="Endpoint"/>. This metadata
+/// type provides data tokens value for <see cref="RouteData.DataTokens"/> associated
+/// with an endpoint.
+/// </summary>
+public interface IDataTokensMetadata
{
/// <summary>
- /// Metadata that defines data tokens for an <see cref="Endpoint"/>. This metadata
- /// type provides data tokens value for <see cref="RouteData.DataTokens"/> associated
- /// with an endpoint.
+ /// Get the data tokens.
/// </summary>
- public interface IDataTokensMetadata
- {
- /// <summary>
- /// Get the data tokens.
- /// </summary>
- IReadOnlyDictionary<string, object?> DataTokens { get; }
- }
+ IReadOnlyDictionary<string, object?> DataTokens { get; }
}
diff --git a/src/Http/Routing/src/IDynamicEndpointMetadata.cs b/src/Http/Routing/src/IDynamicEndpointMetadata.cs
index 76d0698b60..501fe5b2ef 100644
--- a/src/Http/Routing/src/IDynamicEndpointMetadata.cs
+++ b/src/Http/Routing/src/IDynamicEndpointMetadata.cs
@@ -4,31 +4,30 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A metadata interface that can be used to specify that the associated <see cref="Endpoint" />
+/// will be dynamically replaced during matching.
+/// </summary>
+/// <remarks>
+/// <para>
+/// <see cref="IDynamicEndpointMetadata"/> and related derived interfaces signal to
+/// <see cref="MatcherPolicy"/> implementations that an <see cref="Endpoint"/> has dynamic behavior
+/// and thus cannot have its characteristics cached.
+/// </para>
+/// <para>
+/// Using dynamic endpoints can be useful because the default matcher implementation does not
+/// supply extensibility for how URLs are processed. Routing implementations that have dynamic
+/// behavior can apply their dynamic logic after URL processing, by replacing a endpoints as
+/// part of a <see cref="CandidateSet"/>.
+/// </para>
+/// </remarks>
+public interface IDynamicEndpointMetadata
{
/// <summary>
- /// A metadata interface that can be used to specify that the associated <see cref="Endpoint" />
- /// will be dynamically replaced during matching.
+ /// Returns a value that indicates whether the associated endpoint has dynamic matching
+ /// behavior.
/// </summary>
- /// <remarks>
- /// <para>
- /// <see cref="IDynamicEndpointMetadata"/> and related derived interfaces signal to
- /// <see cref="MatcherPolicy"/> implementations that an <see cref="Endpoint"/> has dynamic behavior
- /// and thus cannot have its characteristics cached.
- /// </para>
- /// <para>
- /// Using dynamic endpoints can be useful because the default matcher implementation does not
- /// supply extensibility for how URLs are processed. Routing implementations that have dynamic
- /// behavior can apply their dynamic logic after URL processing, by replacing a endpoints as
- /// part of a <see cref="CandidateSet"/>.
- /// </para>
- /// </remarks>
- public interface IDynamicEndpointMetadata
- {
- /// <summary>
- /// Returns a value that indicates whether the associated endpoint has dynamic matching
- /// behavior.
- /// </summary>
- bool IsDynamic { get; }
- }
+ bool IsDynamic { get; }
}
diff --git a/src/Http/Routing/src/IEndpointAddressScheme.cs b/src/Http/Routing/src/IEndpointAddressScheme.cs
index 09f503e84d..513ceda497 100644
--- a/src/Http/Routing/src/IEndpointAddressScheme.cs
+++ b/src/Http/Routing/src/IEndpointAddressScheme.cs
@@ -4,19 +4,18 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract to find endpoints based on the provided address.
+/// </summary>
+/// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
+public interface IEndpointAddressScheme<TAddress>
{
/// <summary>
- /// Defines a contract to find endpoints based on the provided address.
+ /// Finds endpoints based on the provided <paramref name="address"/>.
/// </summary>
- /// <typeparam name="TAddress">The address type to look up endpoints.</typeparam>
- public interface IEndpointAddressScheme<TAddress>
- {
- /// <summary>
- /// Finds endpoints based on the provided <paramref name="address"/>.
- /// </summary>
- /// <param name="address">The information used to look up endpoints.</param>
- /// <returns>A collection of <see cref="Endpoint"/>.</returns>
- IEnumerable<Endpoint> FindEndpoints(TAddress address);
- }
+ /// <param name="address">The information used to look up endpoints.</param>
+ /// <returns>A collection of <see cref="Endpoint"/>.</returns>
+ IEnumerable<Endpoint> FindEndpoints(TAddress address);
}
diff --git a/src/Http/Routing/src/IEndpointGroupNameMetadata.cs b/src/Http/Routing/src/IEndpointGroupNameMetadata.cs
index 08d7fefc63..108d0f2a95 100644
--- a/src/Http/Routing/src/IEndpointGroupNameMetadata.cs
+++ b/src/Http/Routing/src/IEndpointGroupNameMetadata.cs
@@ -3,16 +3,15 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract used to specify an endpoint group name in <see cref="Endpoint.Metadata"/>.
+/// </summary>
+public interface IEndpointGroupNameMetadata
{
/// <summary>
- /// Defines a contract used to specify an endpoint group name in <see cref="Endpoint.Metadata"/>.
+ /// Gets the endpoint group name.
/// </summary>
- public interface IEndpointGroupNameMetadata
- {
- /// <summary>
- /// Gets the endpoint group name.
- /// </summary>
- string EndpointGroupName { get; }
- }
+ string EndpointGroupName { get; }
}
diff --git a/src/Http/Routing/src/IEndpointNameMetadata.cs b/src/Http/Routing/src/IEndpointNameMetadata.cs
index 4b5f3a5236..79ddfa7a66 100644
--- a/src/Http/Routing/src/IEndpointNameMetadata.cs
+++ b/src/Http/Routing/src/IEndpointNameMetadata.cs
@@ -3,20 +3,19 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract use to specify an endpoint name in <see cref="Endpoint.Metadata"/>.
+/// </summary>
+/// <remarks>
+/// Endpoint names must be unique within an application, and can be used to unambiguously
+/// identify a desired endpoint for URI generation using <see cref="LinkGenerator"/>.
+/// </remarks>
+public interface IEndpointNameMetadata
{
/// <summary>
- /// Defines a contract use to specify an endpoint name in <see cref="Endpoint.Metadata"/>.
+ /// Gets the endpoint name.
/// </summary>
- /// <remarks>
- /// Endpoint names must be unique within an application, and can be used to unambiguously
- /// identify a desired endpoint for URI generation using <see cref="LinkGenerator"/>.
- /// </remarks>
- public interface IEndpointNameMetadata
- {
- /// <summary>
- /// Gets the endpoint name.
- /// </summary>
- string EndpointName { get; }
- }
+ string EndpointName { get; }
}
diff --git a/src/Http/Routing/src/IEndpointRouteBuilder.cs b/src/Http/Routing/src/IEndpointRouteBuilder.cs
index 8810235be1..3840cbb3d1 100644
--- a/src/Http/Routing/src/IEndpointRouteBuilder.cs
+++ b/src/Http/Routing/src/IEndpointRouteBuilder.cs
@@ -5,28 +5,27 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract for a route builder in an application. A route builder specifies the routes for
+/// an application.
+/// </summary>
+public interface IEndpointRouteBuilder
{
/// <summary>
- /// Defines a contract for a route builder in an application. A route builder specifies the routes for
- /// an application.
+ /// Creates a new <see cref="IApplicationBuilder"/>.
/// </summary>
- public interface IEndpointRouteBuilder
- {
- /// <summary>
- /// Creates a new <see cref="IApplicationBuilder"/>.
- /// </summary>
- /// <returns>The new <see cref="IApplicationBuilder"/>.</returns>
- IApplicationBuilder CreateApplicationBuilder();
+ /// <returns>The new <see cref="IApplicationBuilder"/>.</returns>
+ IApplicationBuilder CreateApplicationBuilder();
- /// <summary>
- /// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
- /// </summary>
- IServiceProvider ServiceProvider { get; }
+ /// <summary>
+ /// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
+ /// </summary>
+ IServiceProvider ServiceProvider { get; }
- /// <summary>
- /// Gets the endpoint data sources configured in the builder.
- /// </summary>
- ICollection<EndpointDataSource> DataSources { get; }
- }
+ /// <summary>
+ /// Gets the endpoint data sources configured in the builder.
+ /// </summary>
+ ICollection<EndpointDataSource> DataSources { get; }
}
diff --git a/src/Http/Routing/src/IExcludeFromDescriptionMetadata.cs b/src/Http/Routing/src/IExcludeFromDescriptionMetadata.cs
index 4e3c1eb997..ae308e14ac 100644
--- a/src/Http/Routing/src/IExcludeFromDescriptionMetadata.cs
+++ b/src/Http/Routing/src/IExcludeFromDescriptionMetadata.cs
@@ -4,18 +4,17 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Indicates whether or not that API explorer data should be emitted for this endpoint.
+/// </summary>
+public interface IExcludeFromDescriptionMetadata
{
/// <summary>
- /// Indicates whether or not that API explorer data should be emitted for this endpoint.
+ /// Gets a value indicating whether OpenAPI
+ /// data should be excluded for this endpoint. If <see langword="true"/>,
+ /// API metadata is not emitted.
/// </summary>
- public interface IExcludeFromDescriptionMetadata
- {
- /// <summary>
- /// Gets a value indicating whether OpenAPI
- /// data should be excluded for this endpoint. If <see langword="true"/>,
- /// API metadata is not emitted.
- /// </summary>
- bool ExcludeFromDescription { get; }
- }
+ bool ExcludeFromDescription { get; }
}
diff --git a/src/Http/Routing/src/IHostMetadata.cs b/src/Http/Routing/src/IHostMetadata.cs
index 8876ac770b..5975671e5f 100644
--- a/src/Http/Routing/src/IHostMetadata.cs
+++ b/src/Http/Routing/src/IHostMetadata.cs
@@ -3,18 +3,17 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents host metadata used during routing.
+/// </summary>
+public interface IHostMetadata
{
/// <summary>
- /// Represents host metadata used during routing.
+ /// Returns a read-only collection of hosts used during routing.
+ /// Hosts will be Unicode rather than punycode, and may have a port.
+ /// An empty collection means any host will be accepted.
/// </summary>
- public interface IHostMetadata
- {
- /// <summary>
- /// Returns a read-only collection of hosts used during routing.
- /// Hosts will be Unicode rather than punycode, and may have a port.
- /// An empty collection means any host will be accepted.
- /// </summary>
- IReadOnlyList<string> Hosts { get; }
- }
+ IReadOnlyList<string> Hosts { get; }
}
diff --git a/src/Http/Routing/src/IHttpMethodMetadata.cs b/src/Http/Routing/src/IHttpMethodMetadata.cs
index ca7e87d506..c32391b2d5 100644
--- a/src/Http/Routing/src/IHttpMethodMetadata.cs
+++ b/src/Http/Routing/src/IHttpMethodMetadata.cs
@@ -3,22 +3,21 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents HTTP method metadata used during routing.
+/// </summary>
+public interface IHttpMethodMetadata
{
/// <summary>
- /// Represents HTTP method metadata used during routing.
+ /// Returns a value indicating whether the associated endpoint should accept CORS preflight requests.
/// </summary>
- public interface IHttpMethodMetadata
- {
- /// <summary>
- /// Returns a value indicating whether the associated endpoint should accept CORS preflight requests.
- /// </summary>
- bool AcceptCorsPreflight { get; }
+ bool AcceptCorsPreflight { get; }
- /// <summary>
- /// Returns a read-only collection of HTTP methods used during routing.
- /// An empty collection means any HTTP method will be accepted.
- /// </summary>
- IReadOnlyList<string> HttpMethods { get; }
- }
+ /// <summary>
+ /// Returns a read-only collection of HTTP methods used during routing.
+ /// An empty collection means any HTTP method will be accepted.
+ /// </summary>
+ IReadOnlyList<string> HttpMethods { get; }
}
diff --git a/src/Http/Routing/src/IInlineConstraintResolver.cs b/src/Http/Routing/src/IInlineConstraintResolver.cs
index 16e469cc4c..20537eefca 100644
--- a/src/Http/Routing/src/IInlineConstraintResolver.cs
+++ b/src/Http/Routing/src/IInlineConstraintResolver.cs
@@ -1,18 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines an abstraction for resolving inline constraints as instances of <see cref="IRouteConstraint"/>.
+/// </summary>
+public interface IInlineConstraintResolver
{
/// <summary>
- /// Defines an abstraction for resolving inline constraints as instances of <see cref="IRouteConstraint"/>.
+ /// Resolves the inline constraint.
/// </summary>
- public interface IInlineConstraintResolver
- {
- /// <summary>
- /// Resolves the inline constraint.
- /// </summary>
- /// <param name="inlineConstraint">The inline constraint to resolve.</param>
- /// <returns>The <see cref="IRouteConstraint"/> the inline constraint was resolved to.</returns>
- IRouteConstraint? ResolveConstraint(string inlineConstraint);
- }
+ /// <param name="inlineConstraint">The inline constraint to resolve.</param>
+ /// <returns>The <see cref="IRouteConstraint"/> the inline constraint was resolved to.</returns>
+ IRouteConstraint? ResolveConstraint(string inlineConstraint);
}
diff --git a/src/Http/Routing/src/INamedRouter.cs b/src/Http/Routing/src/INamedRouter.cs
index 71ed22bc41..be07a56222 100644
--- a/src/Http/Routing/src/INamedRouter.cs
+++ b/src/Http/Routing/src/INamedRouter.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// An interface for an <see cref="IRouter"/> with a name.
+/// </summary>
+public interface INamedRouter : IRouter
{
/// <summary>
- /// An interface for an <see cref="IRouter"/> with a name.
+ /// The name of the router. Can be null.
/// </summary>
- public interface INamedRouter : IRouter
- {
- /// <summary>
- /// The name of the router. Can be null.
- /// </summary>
- string? Name { get; }
- }
+ string? Name { get; }
}
diff --git a/src/Http/Routing/src/IRouteBuilder.cs b/src/Http/Routing/src/IRouteBuilder.cs
index 4e460c0be2..7229d675f2 100644
--- a/src/Http/Routing/src/IRouteBuilder.cs
+++ b/src/Http/Routing/src/IRouteBuilder.cs
@@ -5,38 +5,37 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract for a route builder in an application. A route builder specifies the routes for
+/// an application.
+/// </summary>
+public interface IRouteBuilder
{
/// <summary>
- /// Defines a contract for a route builder in an application. A route builder specifies the routes for
- /// an application.
+ /// Gets the <see cref="IApplicationBuilder"/>.
/// </summary>
- public interface IRouteBuilder
- {
- /// <summary>
- /// Gets the <see cref="IApplicationBuilder"/>.
- /// </summary>
- IApplicationBuilder ApplicationBuilder { get; }
+ IApplicationBuilder ApplicationBuilder { get; }
- /// <summary>
- /// Gets or sets the default <see cref="IRouter"/> that is used as a handler if an <see cref="IRouter"/>
- /// is added to the list of routes but does not specify its own.
- /// </summary>
- IRouter? DefaultHandler { get; set; }
+ /// <summary>
+ /// Gets or sets the default <see cref="IRouter"/> that is used as a handler if an <see cref="IRouter"/>
+ /// is added to the list of routes but does not specify its own.
+ /// </summary>
+ IRouter? DefaultHandler { get; set; }
- /// <summary>
- /// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
- /// </summary>
- IServiceProvider ServiceProvider { get; }
+ /// <summary>
+ /// Gets the sets the <see cref="IServiceProvider"/> used to resolve services for routes.
+ /// </summary>
+ IServiceProvider ServiceProvider { get; }
- /// <summary>
- /// Gets the routes configured in the builder.
- /// </summary>
- IList<IRouter> Routes { get; }
+ /// <summary>
+ /// Gets the routes configured in the builder.
+ /// </summary>
+ IList<IRouter> Routes { get; }
- /// <summary>
- /// Builds an <see cref="IRouter"/> that routes the routes specified in the <see cref="Routes"/> property.
- /// </summary>
- IRouter Build();
- }
+ /// <summary>
+ /// Builds an <see cref="IRouter"/> that routes the routes specified in the <see cref="Routes"/> property.
+ /// </summary>
+ IRouter Build();
}
diff --git a/src/Http/Routing/src/IRouteCollection.cs b/src/Http/Routing/src/IRouteCollection.cs
index 06cfd4b4c8..8259b4bab5 100644
--- a/src/Http/Routing/src/IRouteCollection.cs
+++ b/src/Http/Routing/src/IRouteCollection.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Interface for a router that supports appending new routes.
+/// </summary>
+public interface IRouteCollection : IRouter
{
/// <summary>
- /// Interface for a router that supports appending new routes.
+ /// Appends the collection of routes defined in <paramref name="router"/>.
/// </summary>
- public interface IRouteCollection : IRouter
- {
- /// <summary>
- /// Appends the collection of routes defined in <paramref name="router"/>.
- /// </summary>
- /// <param name="router">A <see cref="IRouter"/> instance.</param>
- void Add(IRouter router);
- }
+ /// <param name="router">A <see cref="IRouter"/> instance.</param>
+ void Add(IRouter router);
}
diff --git a/src/Http/Routing/src/IRouteNameMetadata.cs b/src/Http/Routing/src/IRouteNameMetadata.cs
index e7198844c4..9a617cc3da 100644
--- a/src/Http/Routing/src/IRouteNameMetadata.cs
+++ b/src/Http/Routing/src/IRouteNameMetadata.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents metadata used during link generation to find
+/// the associated endpoint using route name.
+/// </summary>
+public interface IRouteNameMetadata
{
/// <summary>
- /// Represents metadata used during link generation to find
- /// the associated endpoint using route name.
+ /// Gets the route name. Can be <see langword="null"/>.
/// </summary>
- public interface IRouteNameMetadata
- {
- /// <summary>
- /// Gets the route name. Can be <see langword="null"/>.
- /// </summary>
- string? RouteName { get; }
- }
+ string? RouteName { get; }
}
diff --git a/src/Http/Routing/src/ISuppressLinkGenerationMetadata.cs b/src/Http/Routing/src/ISuppressLinkGenerationMetadata.cs
index 45b2504ad8..3305e5e8ab 100644
--- a/src/Http/Routing/src/ISuppressLinkGenerationMetadata.cs
+++ b/src/Http/Routing/src/ISuppressLinkGenerationMetadata.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents metadata used during link generation. If <see cref="SuppressLinkGeneration"/> is <c>true</c>
+/// the associated endpoint will not be used for link generation.
+/// </summary>
+public interface ISuppressLinkGenerationMetadata
{
/// <summary>
- /// Represents metadata used during link generation. If <see cref="SuppressLinkGeneration"/> is <c>true</c>
- /// the associated endpoint will not be used for link generation.
+ /// Gets a value indicating whether the associated endpoint should be used for link generation.
/// </summary>
- public interface ISuppressLinkGenerationMetadata
- {
- /// <summary>
- /// Gets a value indicating whether the associated endpoint should be used for link generation.
- /// </summary>
- bool SuppressLinkGeneration { get; }
- }
-} \ No newline at end of file
+ bool SuppressLinkGeneration { get; }
+}
diff --git a/src/Http/Routing/src/ISuppressMatchingMetadata.cs b/src/Http/Routing/src/ISuppressMatchingMetadata.cs
index fbf5ac0f43..ca0a79e844 100644
--- a/src/Http/Routing/src/ISuppressMatchingMetadata.cs
+++ b/src/Http/Routing/src/ISuppressMatchingMetadata.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Metadata used to prevent URL matching. If <see cref="SuppressMatching"/> is <c>true</c> the
+/// associated endpoint will not be considered for URL matching.
+/// </summary>
+public interface ISuppressMatchingMetadata
{
/// <summary>
- /// Metadata used to prevent URL matching. If <see cref="SuppressMatching"/> is <c>true</c> the
- /// associated endpoint will not be considered for URL matching.
+ /// Gets a value indicating whether the associated endpoint should be used for URL matching.
/// </summary>
- public interface ISuppressMatchingMetadata
- {
- /// <summary>
- /// Gets a value indicating whether the associated endpoint should be used for URL matching.
- /// </summary>
- bool SuppressMatching { get; }
- }
-} \ No newline at end of file
+ bool SuppressMatching { get; }
+}
diff --git a/src/Http/Routing/src/InlineRouteParameterParser.cs b/src/Http/Routing/src/InlineRouteParameterParser.cs
index 416f395bf7..a68827a259 100644
--- a/src/Http/Routing/src/InlineRouteParameterParser.cs
+++ b/src/Http/Routing/src/InlineRouteParameterParser.cs
@@ -5,249 +5,248 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Contains methods for parsing processing constraints from a route definition.
+/// </summary>
+public static class InlineRouteParameterParser
{
/// <summary>
- /// Contains methods for parsing processing constraints from a route definition.
+ /// Parses a string representing the provided <paramref name="routeParameter"/> into a <see cref="TemplatePart"/>.
/// </summary>
- public static class InlineRouteParameterParser
+ /// <param name="routeParameter">A string representation of the route parameter.</param>
+ /// <returns>A <see cref="TemplatePart"/> instance.</returns>
+ public static TemplatePart ParseRouteParameter(string routeParameter)
{
- /// <summary>
- /// Parses a string representing the provided <paramref name="routeParameter"/> into a <see cref="TemplatePart"/>.
- /// </summary>
- /// <param name="routeParameter">A string representation of the route parameter.</param>
- /// <returns>A <see cref="TemplatePart"/> instance.</returns>
- public static TemplatePart ParseRouteParameter(string routeParameter)
+ if (routeParameter == null)
{
- if (routeParameter == null)
- {
- throw new ArgumentNullException(nameof(routeParameter));
- }
-
- if (routeParameter.Length == 0)
- {
- return TemplatePart.CreateParameter(
- name: string.Empty,
- isCatchAll: false,
- isOptional: false,
- defaultValue: null,
- inlineConstraints: null);
- }
-
- var startIndex = 0;
- var endIndex = routeParameter.Length - 1;
+ throw new ArgumentNullException(nameof(routeParameter));
+ }
- var isCatchAll = false;
- var isOptional = false;
+ if (routeParameter.Length == 0)
+ {
+ return TemplatePart.CreateParameter(
+ name: string.Empty,
+ isCatchAll: false,
+ isOptional: false,
+ defaultValue: null,
+ inlineConstraints: null);
+ }
- if (routeParameter[0] == '*')
- {
- isCatchAll = true;
- startIndex++;
- }
+ var startIndex = 0;
+ var endIndex = routeParameter.Length - 1;
- if (routeParameter[endIndex] == '?')
- {
- isOptional = true;
- endIndex--;
- }
+ var isCatchAll = false;
+ var isOptional = false;
- var currentIndex = startIndex;
+ if (routeParameter[0] == '*')
+ {
+ isCatchAll = true;
+ startIndex++;
+ }
- // Parse parameter name
- var parameterName = string.Empty;
+ if (routeParameter[endIndex] == '?')
+ {
+ isOptional = true;
+ endIndex--;
+ }
- while (currentIndex <= endIndex)
- {
- var currentChar = routeParameter[currentIndex];
+ var currentIndex = startIndex;
- if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
- {
- // Parameter names are allowed to start with delimiters used to denote constraints or default values.
- // i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
- // specifications.
- parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ // Parse parameter name
+ var parameterName = string.Empty;
- // Roll the index back and move to the constraint parsing stage.
- currentIndex--;
- break;
- }
- else if (currentIndex == endIndex)
- {
- parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
- }
+ while (currentIndex <= endIndex)
+ {
+ var currentChar = routeParameter[currentIndex];
- currentIndex++;
+ if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
+ {
+ // Parameter names are allowed to start with delimiters used to denote constraints or default values.
+ // i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
+ // specifications.
+ parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex);
+
+ // Roll the index back and move to the constraint parsing stage.
+ currentIndex--;
+ break;
}
-
- var parseResults = ParseConstraints(routeParameter, currentIndex, endIndex);
- currentIndex = parseResults.CurrentIndex;
-
- string? defaultValue = null;
- if (currentIndex <= endIndex &&
- routeParameter[currentIndex] == '=')
+ else if (currentIndex == endIndex)
{
- defaultValue = routeParameter.Substring(currentIndex + 1, endIndex - currentIndex);
+ parameterName = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
}
- return TemplatePart.CreateParameter(parameterName,
- isCatchAll,
- isOptional,
- defaultValue,
- parseResults.Constraints);
+ currentIndex++;
}
- private static ConstraintParseResults ParseConstraints(
- string routeParameter,
- int currentIndex,
- int endIndex)
+ var parseResults = ParseConstraints(routeParameter, currentIndex, endIndex);
+ currentIndex = parseResults.CurrentIndex;
+
+ string? defaultValue = null;
+ if (currentIndex <= endIndex &&
+ routeParameter[currentIndex] == '=')
+ {
+ defaultValue = routeParameter.Substring(currentIndex + 1, endIndex - currentIndex);
+ }
+
+ return TemplatePart.CreateParameter(parameterName,
+ isCatchAll,
+ isOptional,
+ defaultValue,
+ parseResults.Constraints);
+ }
+
+ private static ConstraintParseResults ParseConstraints(
+ string routeParameter,
+ int currentIndex,
+ int endIndex)
+ {
+ var inlineConstraints = new List<InlineConstraint>();
+ var state = ParseState.Start;
+ var startIndex = currentIndex;
+ do
{
- var inlineConstraints = new List<InlineConstraint>();
- var state = ParseState.Start;
- var startIndex = currentIndex;
- do
+ var currentChar = currentIndex > endIndex ? null : (char?)routeParameter[currentIndex];
+ switch (state)
{
- var currentChar = currentIndex > endIndex ? null : (char?)routeParameter[currentIndex];
- switch (state)
- {
- case ParseState.Start:
- switch (currentChar)
- {
- case null:
- state = ParseState.End;
- break;
- case ':':
- state = ParseState.ParsingName;
- startIndex = currentIndex + 1;
- break;
- case '(':
- state = ParseState.InsideParenthesis;
- break;
- case '=':
- state = ParseState.End;
- currentIndex--;
- break;
- }
- break;
- case ParseState.InsideParenthesis:
- switch (currentChar)
- {
- case null:
- state = ParseState.End;
- var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- break;
- case ')':
- // Only consume a ')' token if
- // (a) it is the last token
- // (b) the next character is the start of the new constraint ':'
- // (c) the next character is the start of the default value.
-
- var nextChar = currentIndex + 1 > endIndex ? null : (char?)routeParameter[currentIndex + 1];
- switch (nextChar)
- {
- case null:
- state = ParseState.End;
- constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- break;
- case ':':
- state = ParseState.Start;
- constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- startIndex = currentIndex + 1;
- break;
- case '=':
- state = ParseState.End;
- constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- break;
- }
- break;
- case ':':
- case '=':
- // In the original implementation, the Regex would've backtracked if it encountered an
- // unbalanced opening bracket followed by (not necessarily immediately) a delimiter.
- // Simply verifying that the parantheses will eventually be closed should suffice to
- // determine if the terminator needs to be consumed as part of the current constraint
- // specification.
- var indexOfClosingParantheses = routeParameter.IndexOf(')', currentIndex + 1);
- if (indexOfClosingParantheses == -1)
- {
- constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ case ParseState.Start:
+ switch (currentChar)
+ {
+ case null:
+ state = ParseState.End;
+ break;
+ case ':':
+ state = ParseState.ParsingName;
+ startIndex = currentIndex + 1;
+ break;
+ case '(':
+ state = ParseState.InsideParenthesis;
+ break;
+ case '=':
+ state = ParseState.End;
+ currentIndex--;
+ break;
+ }
+ break;
+ case ParseState.InsideParenthesis:
+ switch (currentChar)
+ {
+ case null:
+ state = ParseState.End;
+ var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
+ break;
+ case ')':
+ // Only consume a ')' token if
+ // (a) it is the last token
+ // (b) the next character is the start of the new constraint ':'
+ // (c) the next character is the start of the default value.
+
+ var nextChar = currentIndex + 1 > endIndex ? null : (char?)routeParameter[currentIndex + 1];
+ switch (nextChar)
+ {
+ case null:
+ state = ParseState.End;
+ constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
+ break;
+ case ':':
+ state = ParseState.Start;
+ constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
inlineConstraints.Add(new InlineConstraint(constraintText));
+ startIndex = currentIndex + 1;
+ break;
+ case '=':
+ state = ParseState.End;
+ constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex + 1);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
+ break;
+ }
+ break;
+ case ':':
+ case '=':
+ // In the original implementation, the Regex would've backtracked if it encountered an
+ // unbalanced opening bracket followed by (not necessarily immediately) a delimiter.
+ // Simply verifying that the parantheses will eventually be closed should suffice to
+ // determine if the terminator needs to be consumed as part of the current constraint
+ // specification.
+ var indexOfClosingParantheses = routeParameter.IndexOf(')', currentIndex + 1);
+ if (indexOfClosingParantheses == -1)
+ {
+ constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
- if (currentChar == ':')
- {
- state = ParseState.ParsingName;
- startIndex = currentIndex + 1;
- }
- else
- {
- state = ParseState.End;
- currentIndex--;
- }
+ if (currentChar == ':')
+ {
+ state = ParseState.ParsingName;
+ startIndex = currentIndex + 1;
}
else
{
- currentIndex = indexOfClosingParantheses;
+ state = ParseState.End;
+ currentIndex--;
}
+ }
+ else
+ {
+ currentIndex = indexOfClosingParantheses;
+ }
+
+ break;
+ }
+ break;
+ case ParseState.ParsingName:
+ switch (currentChar)
+ {
+ case null:
+ state = ParseState.End;
+ var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
+ break;
+ case ':':
+ constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
+ startIndex = currentIndex + 1;
+ break;
+ case '(':
+ state = ParseState.InsideParenthesis;
+ break;
+ case '=':
+ state = ParseState.End;
+ constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
+ inlineConstraints.Add(new InlineConstraint(constraintText));
+ currentIndex--;
+ break;
+ }
+ break;
+ }
- break;
- }
- break;
- case ParseState.ParsingName:
- switch (currentChar)
- {
- case null:
- state = ParseState.End;
- var constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- break;
- case ':':
- constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- startIndex = currentIndex + 1;
- break;
- case '(':
- state = ParseState.InsideParenthesis;
- break;
- case '=':
- state = ParseState.End;
- constraintText = routeParameter.Substring(startIndex, currentIndex - startIndex);
- inlineConstraints.Add(new InlineConstraint(constraintText));
- currentIndex--;
- break;
- }
- break;
- }
-
- currentIndex++;
+ currentIndex++;
- } while (state != ParseState.End);
+ } while (state != ParseState.End);
- return new ConstraintParseResults(currentIndex, inlineConstraints);
- }
+ return new ConstraintParseResults(currentIndex, inlineConstraints);
+ }
- private enum ParseState
- {
- Start,
- ParsingName,
- InsideParenthesis,
- End
- }
+ private enum ParseState
+ {
+ Start,
+ ParsingName,
+ InsideParenthesis,
+ End
+ }
- private readonly struct ConstraintParseResults
- {
- public readonly int CurrentIndex;
+ private readonly struct ConstraintParseResults
+ {
+ public readonly int CurrentIndex;
- public readonly IEnumerable<InlineConstraint> Constraints;
+ public readonly IEnumerable<InlineConstraint> Constraints;
- public ConstraintParseResults(int currentIndex, IEnumerable<InlineConstraint> constraints)
- {
- CurrentIndex = currentIndex;
- Constraints = constraints;
- }
+ public ConstraintParseResults(int currentIndex, IEnumerable<InlineConstraint> constraints)
+ {
+ CurrentIndex = currentIndex;
+ Constraints = constraints;
}
}
}
diff --git a/src/Http/Routing/src/Internal/DfaGraphWriter.cs b/src/Http/Routing/src/Internal/DfaGraphWriter.cs
index dbe47d7c28..95f992d457 100644
--- a/src/Http/Routing/src/Internal/DfaGraphWriter.cs
+++ b/src/Http/Routing/src/Internal/DfaGraphWriter.cs
@@ -7,100 +7,99 @@ using System.IO;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.Internal
+namespace Microsoft.AspNetCore.Routing.Internal;
+
+/// <summary>
+/// <para>
+/// A singleton service that can be used to write the route table as a state machine
+/// in GraphViz DOT language https://www.graphviz.org/doc/info/lang.html
+/// </para>
+/// <para>
+/// You can use http://www.webgraphviz.com/ to visualize the results.
+/// </para>
+/// <para>
+/// This type has no support contract, and may be removed or changed at any time in
+/// a future release.
+/// </para>
+/// </summary>
+public class DfaGraphWriter
{
+ private readonly IServiceProvider _services;
+
/// <summary>
- /// <para>
- /// A singleton service that can be used to write the route table as a state machine
- /// in GraphViz DOT language https://www.graphviz.org/doc/info/lang.html
- /// </para>
- /// <para>
- /// You can use http://www.webgraphviz.com/ to visualize the results.
- /// </para>
- /// <para>
- /// This type has no support contract, and may be removed or changed at any time in
- /// a future release.
- /// </para>
+ /// Constructor for a <see cref="DfaGraphWriter"/> given <paramref name="services"/>.
/// </summary>
- public class DfaGraphWriter
+ /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
+ public DfaGraphWriter(IServiceProvider services)
{
- private readonly IServiceProvider _services;
+ _services = services;
+ }
- /// <summary>
- /// Constructor for a <see cref="DfaGraphWriter"/> given <paramref name="services"/>.
- /// </summary>
- /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
- public DfaGraphWriter(IServiceProvider services)
- {
- _services = services;
- }
+ /// <summary>
+ /// Displays a graph representation of <paramref name="dataSource"/> in DOT.
+ /// </summary>
+ /// <param name="dataSource">The <see cref="EndpointDataSource"/> to extract routes from.</param>
+ /// <param name="writer">The <see cref="TextWriter"/> to which the content is written.</param>
+ public void Write(EndpointDataSource dataSource, TextWriter writer)
+ {
+ var builder = _services.GetRequiredService<DfaMatcherBuilder>();
- /// <summary>
- /// Displays a graph representation of <paramref name="dataSource"/> in DOT.
- /// </summary>
- /// <param name="dataSource">The <see cref="EndpointDataSource"/> to extract routes from.</param>
- /// <param name="writer">The <see cref="TextWriter"/> to which the content is written.</param>
- public void Write(EndpointDataSource dataSource, TextWriter writer)
+ var endpoints = dataSource.Endpoints;
+ for (var i = 0; i < endpoints.Count; i++)
{
- var builder = _services.GetRequiredService<DfaMatcherBuilder>();
-
- var endpoints = dataSource.Endpoints;
- for (var i = 0; i < endpoints.Count; i++)
+ if (endpoints[i] is RouteEndpoint endpoint && (endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching ?? false) == false)
{
- if (endpoints[i] is RouteEndpoint endpoint && (endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching ?? false) == false)
- {
- builder.AddEndpoint(endpoint);
- }
+ builder.AddEndpoint(endpoint);
}
+ }
- // Assign each node a sequential index.
- var visited = new Dictionary<DfaNode, int>();
+ // Assign each node a sequential index.
+ var visited = new Dictionary<DfaNode, int>();
- var tree = builder.BuildDfaTree(includeLabel: true);
+ var tree = builder.BuildDfaTree(includeLabel: true);
- writer.WriteLine("digraph DFA {");
- tree.Visit(WriteNode);
- writer.WriteLine("}");
+ writer.WriteLine("digraph DFA {");
+ tree.Visit(WriteNode);
+ writer.WriteLine("}");
- void WriteNode(DfaNode node)
+ void WriteNode(DfaNode node)
+ {
+ if (!visited.TryGetValue(node, out var label))
{
- if (!visited.TryGetValue(node, out var label))
- {
- label = visited.Count;
- visited.Add(node, label);
- }
+ label = visited.Count;
+ visited.Add(node, label);
+ }
- // We can safely index into visited because this is a post-order traversal,
- // all of the children of this node are already in the dictionary.
+ // We can safely index into visited because this is a post-order traversal,
+ // all of the children of this node are already in the dictionary.
- if (node.Literals != null)
+ if (node.Literals != null)
+ {
+ foreach (var literal in node.Literals)
{
- foreach (var literal in node.Literals)
- {
- writer.WriteLine($"{label} -> {visited[literal.Value]} [label=\"/{literal.Key}\"]");
- }
+ writer.WriteLine($"{label} -> {visited[literal.Value]} [label=\"/{literal.Key}\"]");
}
+ }
- if (node.Parameters != null)
- {
- writer.WriteLine($"{label} -> {visited[node.Parameters]} [label=\"/*\"]");
- }
+ if (node.Parameters != null)
+ {
+ writer.WriteLine($"{label} -> {visited[node.Parameters]} [label=\"/*\"]");
+ }
- if (node.CatchAll != null && node.Parameters != node.CatchAll)
- {
- writer.WriteLine($"{label} -> {visited[node.CatchAll]} [label=\"/**\"]");
- }
+ if (node.CatchAll != null && node.Parameters != node.CatchAll)
+ {
+ writer.WriteLine($"{label} -> {visited[node.CatchAll]} [label=\"/**\"]");
+ }
- if (node.PolicyEdges != null)
+ if (node.PolicyEdges != null)
+ {
+ foreach (var policy in node.PolicyEdges)
{
- foreach (var policy in node.PolicyEdges)
- {
- writer.WriteLine($"{label} -> {visited[policy.Value]} [label=\"{policy.Key}\"]");
- }
+ writer.WriteLine($"{label} -> {visited[policy.Value]} [label=\"{policy.Key}\"]");
}
-
- writer.WriteLine($"{label} [label=\"{node.Label}\"]");
}
+
+ writer.WriteLine($"{label} [label=\"{node.Label}\"]");
}
}
}
diff --git a/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs b/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs
index 5a652f513f..8b556914d2 100644
--- a/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs
+++ b/src/Http/Routing/src/LinkGeneratorEndpointNameAddressExtensions.cs
@@ -5,227 +5,226 @@ using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Extension methods for using <see cref="LinkGenerator"/> with and endpoint name.
+/// </summary>
+public static class LinkGeneratorEndpointNameAddressExtensions
{
/// <summary>
- /// Extension methods for using <see cref="LinkGenerator"/> with and endpoint name.
+ /// Generates a URI with an absolute path based on the provided values.
/// </summary>
- public static class LinkGeneratorEndpointNameAddressExtensions
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
+ /// <param name="pathBase">
+ /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
+ /// </param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetPathByName(
+ this LinkGenerator generator,
+ HttpContext httpContext,
+ string endpointName,
+ object? values,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
{
- /// <summary>
- /// Generates a URI with an absolute path based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
- /// <param name="pathBase">
- /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
- /// </param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetPathByName(
- this LinkGenerator generator,
- HttpContext httpContext,
- string endpointName,
- object? values,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ if (generator == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
-
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
-
- return generator.GetPathByAddress<string>(
- httpContext,
- endpointName,
- new RouteValueDictionary(values),
- ambientValues: null,
- pathBase,
- fragment,
- options);
+ throw new ArgumentNullException(nameof(generator));
}
- /// <summary>
- /// Generates a URI with an absolute path based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
- /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetPathByName(
- this LinkGenerator generator,
- string endpointName,
- object? values,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ if (httpContext == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (endpointName == null)
+ {
+ throw new ArgumentNullException(nameof(endpointName));
+ }
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
+ return generator.GetPathByAddress<string>(
+ httpContext,
+ endpointName,
+ new RouteValueDictionary(values),
+ ambientValues: null,
+ pathBase,
+ fragment,
+ options);
+ }
+
+ /// <summary>
+ /// Generates a URI with an absolute path based on the provided values.
+ /// </summary>
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
+ /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetPathByName(
+ this LinkGenerator generator,
+ string endpointName,
+ object? values,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
+ {
+ if (generator == null)
+ {
+ throw new ArgumentNullException(nameof(generator));
+ }
- return generator.GetPathByAddress<string>(endpointName, new RouteValueDictionary(values), pathBase, fragment, options);
+ if (endpointName == null)
+ {
+ throw new ArgumentNullException(nameof(endpointName));
}
- /// <summary>
- /// Generates an absolute URI based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
- /// <param name="scheme">
- /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
- /// </param>
- /// <param name="host">
- /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
- /// See the remarks section for details about the security implications of the <paramref name="host"/>.
- /// </param>
- /// <param name="pathBase">
- /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
- /// </param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- /// <remarks>
- /// <para>
- /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
- /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
- /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
- /// your deployment environment.
- /// </para>
- /// </remarks>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetUriByName(
- this LinkGenerator generator,
- HttpContext httpContext,
- string endpointName,
- object? values,
- string? scheme = default,
- HostString? host = default,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ return generator.GetPathByAddress<string>(endpointName, new RouteValueDictionary(values), pathBase, fragment, options);
+ }
+
+ /// <summary>
+ /// Generates an absolute URI based on the provided values.
+ /// </summary>
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
+ /// <param name="scheme">
+ /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
+ /// </param>
+ /// <param name="host">
+ /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
+ /// See the remarks section for details about the security implications of the <paramref name="host"/>.
+ /// </param>
+ /// <param name="pathBase">
+ /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
+ /// </param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ /// <remarks>
+ /// <para>
+ /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
+ /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
+ /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
+ /// your deployment environment.
+ /// </para>
+ /// </remarks>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetUriByName(
+ this LinkGenerator generator,
+ HttpContext httpContext,
+ string endpointName,
+ object? values,
+ string? scheme = default,
+ HostString? host = default,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
+ {
+ if (generator == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
-
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
-
- return generator.GetUriByAddress<string>(
- httpContext,
- endpointName,
- new RouteValueDictionary(values),
- ambientValues: null,
- scheme,
- host,
- pathBase,
- fragment,
- options);
+ throw new ArgumentNullException(nameof(generator));
}
- /// <summary>
- /// Generates an absolute URI based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
- /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
- /// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
- /// <param name="host">
- /// The URI host/authority, applied to the resulting URI.
- /// See the remarks section for details about the security implications of the <paramref name="host"/>.
- /// </param>
- /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>An absolute URI, or <c>null</c>.</returns>
- /// <remarks>
- /// <para>
- /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
- /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
- /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
- /// your deployment environment.
- /// </para>
- /// </remarks>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetUriByName(
- this LinkGenerator generator,
- string endpointName,
- object? values,
- string scheme,
- HostString host,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ if (httpContext == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
-
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
-
- if (string.IsNullOrEmpty(scheme))
- {
- throw new ArgumentException("A scheme must be provided.", nameof(scheme));
- }
-
- if (!host.HasValue)
- {
- throw new ArgumentException("A host must be provided.", nameof(host));
- }
-
- return generator.GetUriByAddress<string>(endpointName, new RouteValueDictionary(values), scheme, host, pathBase, fragment, options);
+ throw new ArgumentNullException(nameof(httpContext));
}
+
+ if (endpointName == null)
+ {
+ throw new ArgumentNullException(nameof(endpointName));
+ }
+
+ return generator.GetUriByAddress<string>(
+ httpContext,
+ endpointName,
+ new RouteValueDictionary(values),
+ ambientValues: null,
+ scheme,
+ host,
+ pathBase,
+ fragment,
+ options);
+ }
+
+ /// <summary>
+ /// Generates an absolute URI based on the provided values.
+ /// </summary>
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
+ /// <param name="values">The route values. Used to expand parameters in the route template. Optional.</param>
+ /// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
+ /// <param name="host">
+ /// The URI host/authority, applied to the resulting URI.
+ /// See the remarks section for details about the security implications of the <paramref name="host"/>.
+ /// </param>
+ /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>An absolute URI, or <c>null</c>.</returns>
+ /// <remarks>
+ /// <para>
+ /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
+ /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
+ /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
+ /// your deployment environment.
+ /// </para>
+ /// </remarks>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetUriByName(
+ this LinkGenerator generator,
+ string endpointName,
+ object? values,
+ string scheme,
+ HostString host,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
+ {
+ if (generator == null)
+ {
+ throw new ArgumentNullException(nameof(generator));
+ }
+
+ if (endpointName == null)
+ {
+ throw new ArgumentNullException(nameof(endpointName));
+ }
+
+ if (string.IsNullOrEmpty(scheme))
+ {
+ throw new ArgumentException("A scheme must be provided.", nameof(scheme));
+ }
+
+ if (!host.HasValue)
+ {
+ throw new ArgumentException("A host must be provided.", nameof(host));
+ }
+
+ return generator.GetUriByAddress<string>(endpointName, new RouteValueDictionary(values), scheme, host, pathBase, fragment, options);
}
}
diff --git a/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs b/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs
index 444dc5c0d3..a1677a71ba 100644
--- a/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs
+++ b/src/Http/Routing/src/LinkGeneratorRouteValuesAddressExtensions.cs
@@ -5,211 +5,210 @@ using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Extension methods for using <see cref="LinkGenerator"/> with <see cref="RouteValuesAddress"/>.
+/// </summary>
+public static class LinkGeneratorRouteValuesAddressExtensions
{
/// <summary>
- /// Extension methods for using <see cref="LinkGenerator"/> with <see cref="RouteValuesAddress"/>.
+ /// Generates a URI with an absolute path based on the provided values.
/// </summary>
- public static class LinkGeneratorRouteValuesAddressExtensions
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
+ /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
+ /// <param name="pathBase">
+ /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
+ /// </param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetPathByRouteValues(
+ this LinkGenerator generator,
+ HttpContext httpContext,
+ string? routeName,
+ object? values,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
{
- /// <summary>
- /// Generates a URI with an absolute path based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
- /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
- /// <param name="pathBase">
- /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
- /// </param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetPathByRouteValues(
- this LinkGenerator generator,
- HttpContext httpContext,
- string? routeName,
- object? values,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ if (generator == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
-
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var address = CreateAddress(httpContext, routeName, values);
- return generator.GetPathByAddress<RouteValuesAddress>(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues,
- pathBase,
- fragment,
- options);
+ throw new ArgumentNullException(nameof(generator));
}
- /// <summary>
- /// Generates a URI with an absolute path based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
- /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
- /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetPathByRouteValues(
- this LinkGenerator generator,
- string? routeName,
- object? values,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ if (httpContext == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
-
- var address = CreateAddress(httpContext: null, routeName, values);
- return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
+ throw new ArgumentNullException(nameof(httpContext));
}
- /// <summary>
- /// Generates an absolute URI based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
- /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
- /// <param name="scheme">
- /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
- /// </param>
- /// <param name="host">
- /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
- /// See the remarks section for details about the security implications of the <paramref name="host"/>.
- /// </param>
- /// <param name="pathBase">
- /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
- /// </param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
- /// <remarks>
- /// <para>
- /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
- /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
- /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
- /// your deployment environment.
- /// </para>
- /// </remarks>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetUriByRouteValues(
- this LinkGenerator generator,
- HttpContext httpContext,
- string? routeName,
- object? values,
- string? scheme = default,
- HostString? host = default,
- PathString? pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ var address = CreateAddress(httpContext, routeName, values);
+ return generator.GetPathByAddress<RouteValuesAddress>(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues,
+ pathBase,
+ fragment,
+ options);
+ }
+
+ /// <summary>
+ /// Generates a URI with an absolute path based on the provided values.
+ /// </summary>
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
+ /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
+ /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetPathByRouteValues(
+ this LinkGenerator generator,
+ string? routeName,
+ object? values,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
+ {
+ if (generator == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
+ throw new ArgumentNullException(nameof(generator));
+ }
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
+ var address = CreateAddress(httpContext: null, routeName, values);
+ return generator.GetPathByAddress<RouteValuesAddress>(address, address.ExplicitValues, pathBase, fragment, options);
+ }
- var address = CreateAddress(httpContext, routeName, values);
- return generator.GetUriByAddress<RouteValuesAddress>(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues,
- scheme,
- host,
- pathBase,
- fragment,
- options);
+ /// <summary>
+ /// Generates an absolute URI based on the provided values.
+ /// </summary>
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
+ /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
+ /// <param name="scheme">
+ /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of <see cref="HttpRequest.Scheme"/> will be used.
+ /// </param>
+ /// <param name="host">
+ /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value <see cref="HttpRequest.Host"/> will be used.
+ /// See the remarks section for details about the security implications of the <paramref name="host"/>.
+ /// </param>
+ /// <param name="pathBase">
+ /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of <see cref="HttpRequest.PathBase"/> will be used.
+ /// </param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>A URI with an absolute path, or <c>null</c>.</returns>
+ /// <remarks>
+ /// <para>
+ /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
+ /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
+ /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
+ /// your deployment environment.
+ /// </para>
+ /// </remarks>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetUriByRouteValues(
+ this LinkGenerator generator,
+ HttpContext httpContext,
+ string? routeName,
+ object? values,
+ string? scheme = default,
+ HostString? host = default,
+ PathString? pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
+ {
+ if (generator == null)
+ {
+ throw new ArgumentNullException(nameof(generator));
}
- /// <summary>
- /// Generates an absolute URI based on the provided values.
- /// </summary>
- /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
- /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
- /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
- /// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
- /// <param name="host">
- /// The URI host/authority, applied to the resulting URI.
- /// See the remarks section for details about the security implications of the <paramref name="host"/>.
- /// </param>
- /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
- /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
- /// <param name="options">
- /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
- /// names from <c>RouteOptions</c>.
- /// </param>
- /// <returns>An absolute URI, or <c>null</c>.</returns>
- /// <remarks>
- /// <para>
- /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
- /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
- /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
- /// your deployment environment.
- /// </para>
- /// </remarks>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public static string? GetUriByRouteValues(
- this LinkGenerator generator,
- string? routeName,
- object? values,
- string scheme,
- HostString host,
- PathString pathBase = default,
- FragmentString fragment = default,
- LinkOptions? options = default)
+ if (httpContext == null)
{
- if (generator == null)
- {
- throw new ArgumentNullException(nameof(generator));
- }
-
- var address = CreateAddress(httpContext: null, routeName, values);
- return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
+ throw new ArgumentNullException(nameof(httpContext));
}
- private static RouteValuesAddress CreateAddress(HttpContext? httpContext, string? routeName, object? values)
+ var address = CreateAddress(httpContext, routeName, values);
+ return generator.GetUriByAddress<RouteValuesAddress>(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues,
+ scheme,
+ host,
+ pathBase,
+ fragment,
+ options);
+ }
+
+ /// <summary>
+ /// Generates an absolute URI based on the provided values.
+ /// </summary>
+ /// <param name="generator">The <see cref="LinkGenerator"/>.</param>
+ /// <param name="routeName">The route name. Used to resolve endpoints. Optional.</param>
+ /// <param name="values">The route values. Used to resolve endpoints and expand parameters in the route template. Optional.</param>
+ /// <param name="scheme">The URI scheme, applied to the resulting URI.</param>
+ /// <param name="host">
+ /// The URI host/authority, applied to the resulting URI.
+ /// See the remarks section for details about the security implications of the <paramref name="host"/>.
+ /// </param>
+ /// <param name="pathBase">An optional URI path base. Prepended to the path in the resulting URI.</param>
+ /// <param name="fragment">An optional URI fragment. Appended to the resulting URI.</param>
+ /// <param name="options">
+ /// An optional <see cref="LinkOptions"/>. Settings on provided object override the settings with matching
+ /// names from <c>RouteOptions</c>.
+ /// </param>
+ /// <returns>An absolute URI, or <c>null</c>.</returns>
+ /// <remarks>
+ /// <para>
+ /// The value of <paramref name="host" /> should be a trusted value. Relying on the value of the current request
+ /// can allow untrusted input to influence the resulting URI unless the <c>Host</c> header has been validated.
+ /// See the deployment documentation for instructions on how to properly validate the <c>Host</c> header in
+ /// your deployment environment.
+ /// </para>
+ /// </remarks>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public static string? GetUriByRouteValues(
+ this LinkGenerator generator,
+ string? routeName,
+ object? values,
+ string scheme,
+ HostString host,
+ PathString pathBase = default,
+ FragmentString fragment = default,
+ LinkOptions? options = default)
+ {
+ if (generator == null)
{
- return new RouteValuesAddress()
- {
- AmbientValues = DefaultLinkGenerator.GetAmbientValues(httpContext),
- ExplicitValues = new RouteValueDictionary(values),
- RouteName = routeName,
- };
+ throw new ArgumentNullException(nameof(generator));
}
+
+ var address = CreateAddress(httpContext: null, routeName, values);
+ return generator.GetUriByAddress<RouteValuesAddress>(address, address.ExplicitValues, scheme, host, pathBase, fragment, options);
+ }
+
+ private static RouteValuesAddress CreateAddress(HttpContext? httpContext, string? routeName, object? values)
+ {
+ return new RouteValuesAddress()
+ {
+ AmbientValues = DefaultLinkGenerator.GetAmbientValues(httpContext),
+ ExplicitValues = new RouteValueDictionary(values),
+ RouteName = routeName,
+ };
}
}
diff --git a/src/Http/Routing/src/LinkParser.cs b/src/Http/Routing/src/LinkParser.cs
index a28d9283ea..68edc662a4 100644
--- a/src/Http/Routing/src/LinkParser.cs
+++ b/src/Http/Routing/src/LinkParser.cs
@@ -3,35 +3,34 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a contract to parse URIs using information from routing.
+/// </summary>
+public abstract class LinkParser
{
/// <summary>
- /// Defines a contract to parse URIs using information from routing.
+ /// Attempts to parse the provided <paramref name="path"/> using the route pattern
+ /// specified by the <see cref="Endpoint"/> matching <paramref name="address"/>.
/// </summary>
- public abstract class LinkParser
- {
- /// <summary>
- /// Attempts to parse the provided <paramref name="path"/> using the route pattern
- /// specified by the <see cref="Endpoint"/> matching <paramref name="address"/>.
- /// </summary>
- /// <typeparam name="TAddress">The address type.</typeparam>
- /// <param name="address">The address value. Used to resolve endpoints.</param>
- /// <param name="path">The URI path to parse.</param>
- /// <returns>
- /// A <see cref="RouteValueDictionary"/> with the parsed values if parsing is successful;
- /// otherwise <c>null</c>.
- /// </returns>
- /// <remarks>
- /// <para>
- /// <see cref="ParsePathByAddress{TAddress}(TAddress, PathString)"/> will attempt to first resolve
- /// <see cref="Endpoint"/> instances that match <paramref name="address"/> and then use the route
- /// pattern associated with each endpoint to parse the URL path.
- /// </para>
- /// <para>
- /// The parsing operation will fail and return <c>null</c> if either no endpoints are found or none
- /// of the route patterns match the provided URI path.
- /// </para>
- /// </remarks>
- public abstract RouteValueDictionary? ParsePathByAddress<TAddress>(TAddress address, PathString path);
- }
+ /// <typeparam name="TAddress">The address type.</typeparam>
+ /// <param name="address">The address value. Used to resolve endpoints.</param>
+ /// <param name="path">The URI path to parse.</param>
+ /// <returns>
+ /// A <see cref="RouteValueDictionary"/> with the parsed values if parsing is successful;
+ /// otherwise <c>null</c>.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// <see cref="ParsePathByAddress{TAddress}(TAddress, PathString)"/> will attempt to first resolve
+ /// <see cref="Endpoint"/> instances that match <paramref name="address"/> and then use the route
+ /// pattern associated with each endpoint to parse the URL path.
+ /// </para>
+ /// <para>
+ /// The parsing operation will fail and return <c>null</c> if either no endpoints are found or none
+ /// of the route patterns match the provided URI path.
+ /// </para>
+ /// </remarks>
+ public abstract RouteValueDictionary? ParsePathByAddress<TAddress>(TAddress address, PathString path);
}
diff --git a/src/Http/Routing/src/LinkParserEndpointNameAddressExtensions.cs b/src/Http/Routing/src/LinkParserEndpointNameAddressExtensions.cs
index 700961b1e8..2c72c3c38f 100644
--- a/src/Http/Routing/src/LinkParserEndpointNameAddressExtensions.cs
+++ b/src/Http/Routing/src/LinkParserEndpointNameAddressExtensions.cs
@@ -4,51 +4,50 @@
using System;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Extension methods for using <see cref="LinkParser"/> with an endpoint name.
+/// </summary>
+public static class LinkParserEndpointNameAddressExtensions
{
/// <summary>
- /// Extension methods for using <see cref="LinkParser"/> with an endpoint name.
+ /// Attempts to parse the provided <paramref name="path"/> using the route pattern
+ /// specified by the <see cref="Endpoint"/> matching <paramref name="endpointName"/>.
/// </summary>
- public static class LinkParserEndpointNameAddressExtensions
+ /// <param name="parser">The <see cref="LinkParser"/>.</param>
+ /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
+ /// <param name="path">The URI path to parse.</param>
+ /// <returns>
+ /// A <see cref="RouteValueDictionary"/> with the parsed values if parsing is successful;
+ /// otherwise <c>null</c>.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// <see cref="ParsePathByEndpointName(LinkParser, string, PathString)"/> will attempt to first resolve
+ /// <see cref="Endpoint"/> instances that match <paramref name="endpointName"/> and then use the route
+ /// pattern associated with each endpoint to parse the URL path.
+ /// </para>
+ /// <para>
+ /// The parsing operation will fail and return <c>null</c> if either no endpoints are found or none
+ /// of the route patterns match the provided URI path.
+ /// </para>
+ /// </remarks>
+ public static RouteValueDictionary? ParsePathByEndpointName(
+ this LinkParser parser,
+ string endpointName,
+ PathString path)
{
- /// <summary>
- /// Attempts to parse the provided <paramref name="path"/> using the route pattern
- /// specified by the <see cref="Endpoint"/> matching <paramref name="endpointName"/>.
- /// </summary>
- /// <param name="parser">The <see cref="LinkParser"/>.</param>
- /// <param name="endpointName">The endpoint name. Used to resolve endpoints.</param>
- /// <param name="path">The URI path to parse.</param>
- /// <returns>
- /// A <see cref="RouteValueDictionary"/> with the parsed values if parsing is successful;
- /// otherwise <c>null</c>.
- /// </returns>
- /// <remarks>
- /// <para>
- /// <see cref="ParsePathByEndpointName(LinkParser, string, PathString)"/> will attempt to first resolve
- /// <see cref="Endpoint"/> instances that match <paramref name="endpointName"/> and then use the route
- /// pattern associated with each endpoint to parse the URL path.
- /// </para>
- /// <para>
- /// The parsing operation will fail and return <c>null</c> if either no endpoints are found or none
- /// of the route patterns match the provided URI path.
- /// </para>
- /// </remarks>
- public static RouteValueDictionary? ParsePathByEndpointName(
- this LinkParser parser,
- string endpointName,
- PathString path)
+ if (parser == null)
{
- if (parser == null)
- {
- throw new ArgumentNullException(nameof(parser));
- }
-
- if (endpointName == null)
- {
- throw new ArgumentNullException(nameof(endpointName));
- }
+ throw new ArgumentNullException(nameof(parser));
+ }
- return parser.ParsePathByAddress<string>(endpointName, path);
+ if (endpointName == null)
+ {
+ throw new ArgumentNullException(nameof(endpointName));
}
+
+ return parser.ParsePathByAddress<string>(endpointName, path);
}
}
diff --git a/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs b/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs
index 55c32b2b7b..8dd3e1de3b 100644
--- a/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs
+++ b/src/Http/Routing/src/MapRouteRouteBuilderExtensions.cs
@@ -8,161 +8,160 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Provides extension methods for <see cref="IRouteBuilder"/> to add routes.
+/// </summary>
+public static class MapRouteRouteBuilderExtensions
{
/// <summary>
- /// Provides extension methods for <see cref="IRouteBuilder"/> to add routes.
+ /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name and template.
/// </summary>
- public static class MapRouteRouteBuilderExtensions
+ /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
+ /// <param name="name">The name of the route.</param>
+ /// <param name="template">The URL pattern of the route.</param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static IRouteBuilder MapRoute(
+ this IRouteBuilder routeBuilder,
+ string? name,
+ string? template)
{
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name and template.
- /// </summary>
- /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
- /// <param name="name">The name of the route.</param>
- /// <param name="template">The URL pattern of the route.</param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static IRouteBuilder MapRoute(
- this IRouteBuilder routeBuilder,
- string? name,
- string? template)
- {
- MapRoute(routeBuilder, name, template, defaults: null);
- return routeBuilder;
- }
+ MapRoute(routeBuilder, name, template, defaults: null);
+ return routeBuilder;
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, and default values.
- /// </summary>
- /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
- /// <param name="name">The name of the route.</param>
- /// <param name="template">The URL pattern of the route.</param>
- /// <param name="defaults">
- /// An object that contains default values for route parameters. The object's properties represent the names
- /// and values of the default values.
- /// </param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static IRouteBuilder MapRoute(
- this IRouteBuilder routeBuilder,
- string? name,
- string? template,
- object? defaults)
- {
- return MapRoute(routeBuilder, name, template, defaults, constraints: null);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, and default values.
+ /// </summary>
+ /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
+ /// <param name="name">The name of the route.</param>
+ /// <param name="template">The URL pattern of the route.</param>
+ /// <param name="defaults">
+ /// An object that contains default values for route parameters. The object's properties represent the names
+ /// and values of the default values.
+ /// </param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static IRouteBuilder MapRoute(
+ this IRouteBuilder routeBuilder,
+ string? name,
+ string? template,
+ object? defaults)
+ {
+ return MapRoute(routeBuilder, name, template, defaults, constraints: null);
+ }
+
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
+ /// constraints.
+ /// </summary>
+ /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
+ /// <param name="name">The name of the route.</param>
+ /// <param name="template">The URL pattern of the route.</param>
+ /// <param name="defaults">
+ /// An object that contains default values for route parameters. The object's properties represent the names
+ /// and values of the default values.
+ /// </param>
+ /// <param name="constraints">
+ /// An object that contains constraints for the route. The object's properties represent the names and values
+ /// of the constraints.
+ /// </param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static IRouteBuilder MapRoute(
+ this IRouteBuilder routeBuilder,
+ string? name,
+ string? template,
+ object? defaults,
+ object? constraints)
+ {
+ return MapRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
- /// constraints.
- /// </summary>
- /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
- /// <param name="name">The name of the route.</param>
- /// <param name="template">The URL pattern of the route.</param>
- /// <param name="defaults">
- /// An object that contains default values for route parameters. The object's properties represent the names
- /// and values of the default values.
- /// </param>
- /// <param name="constraints">
- /// An object that contains constraints for the route. The object's properties represent the names and values
- /// of the constraints.
- /// </param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static IRouteBuilder MapRoute(
- this IRouteBuilder routeBuilder,
- string? name,
- string? template,
- object? defaults,
- object? constraints)
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
+ /// data tokens.
+ /// </summary>
+ /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
+ /// <param name="name">The name of the route.</param>
+ /// <param name="template">The URL pattern of the route.</param>
+ /// <param name="defaults">
+ /// An object that contains default values for route parameters. The object's properties represent the names
+ /// and values of the default values.
+ /// </param>
+ /// <param name="constraints">
+ /// An object that contains constraints for the route. The object's properties represent the names and values
+ /// of the constraints.
+ /// </param>
+ /// <param name="dataTokens">
+ /// An object that contains data tokens for the route. The object's properties represent the names and values
+ /// of the data tokens.
+ /// </param>
+ /// <returns>A reference to this instance after the operation has completed.</returns>
+ public static IRouteBuilder MapRoute(
+ this IRouteBuilder routeBuilder,
+ string? name,
+ string? template,
+ object? defaults,
+ object? constraints,
+ object? dataTokens)
+ {
+ if (routeBuilder.DefaultHandler == null)
{
- return MapRoute(routeBuilder, name, template, defaults, constraints, dataTokens: null);
+ throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
}
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> with the specified name, template, default values, and
- /// data tokens.
- /// </summary>
- /// <param name="routeBuilder">The <see cref="IRouteBuilder"/> to add the route to.</param>
- /// <param name="name">The name of the route.</param>
- /// <param name="template">The URL pattern of the route.</param>
- /// <param name="defaults">
- /// An object that contains default values for route parameters. The object's properties represent the names
- /// and values of the default values.
- /// </param>
- /// <param name="constraints">
- /// An object that contains constraints for the route. The object's properties represent the names and values
- /// of the constraints.
- /// </param>
- /// <param name="dataTokens">
- /// An object that contains data tokens for the route. The object's properties represent the names and values
- /// of the data tokens.
- /// </param>
- /// <returns>A reference to this instance after the operation has completed.</returns>
- public static IRouteBuilder MapRoute(
- this IRouteBuilder routeBuilder,
- string? name,
- string? template,
- object? defaults,
- object? constraints,
- object? dataTokens)
- {
- if (routeBuilder.DefaultHandler == null)
- {
- throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
- }
+ routeBuilder.Routes.Add(new Route(
+ routeBuilder.DefaultHandler,
+ name,
+ template,
+ new RouteValueDictionary(defaults),
+ new RouteValueDictionary(constraints)!,
+ new RouteValueDictionary(dataTokens),
+ CreateInlineConstraintResolver(routeBuilder.ServiceProvider)));
- routeBuilder.Routes.Add(new Route(
- routeBuilder.DefaultHandler,
- name,
- template,
- new RouteValueDictionary(defaults),
- new RouteValueDictionary(constraints)!,
- new RouteValueDictionary(dataTokens),
- CreateInlineConstraintResolver(routeBuilder.ServiceProvider)));
+ return routeBuilder;
+ }
- return routeBuilder;
- }
+ private static IInlineConstraintResolver CreateInlineConstraintResolver(IServiceProvider serviceProvider)
+ {
+ var inlineConstraintResolver = serviceProvider
+ .GetRequiredService<IInlineConstraintResolver>();
- private static IInlineConstraintResolver CreateInlineConstraintResolver(IServiceProvider serviceProvider)
- {
- var inlineConstraintResolver = serviceProvider
- .GetRequiredService<IInlineConstraintResolver>();
+ var parameterPolicyFactory = serviceProvider
+ .GetRequiredService<ParameterPolicyFactory>();
- var parameterPolicyFactory = serviceProvider
- .GetRequiredService<ParameterPolicyFactory>();
+ // This inline constraint resolver will return a null constraint for non-IRouteConstraint
+ // parameter policies so Route does not error
+ return new BackCompatInlineConstraintResolver(inlineConstraintResolver, parameterPolicyFactory);
+ }
- // This inline constraint resolver will return a null constraint for non-IRouteConstraint
- // parameter policies so Route does not error
- return new BackCompatInlineConstraintResolver(inlineConstraintResolver, parameterPolicyFactory);
- }
+ private class BackCompatInlineConstraintResolver : IInlineConstraintResolver
+ {
+ private readonly IInlineConstraintResolver _inner;
+ private readonly ParameterPolicyFactory _parameterPolicyFactory;
- private class BackCompatInlineConstraintResolver : IInlineConstraintResolver
+ public BackCompatInlineConstraintResolver(IInlineConstraintResolver inner, ParameterPolicyFactory parameterPolicyFactory)
{
- private readonly IInlineConstraintResolver _inner;
- private readonly ParameterPolicyFactory _parameterPolicyFactory;
+ _inner = inner;
+ _parameterPolicyFactory = parameterPolicyFactory;
+ }
- public BackCompatInlineConstraintResolver(IInlineConstraintResolver inner, ParameterPolicyFactory parameterPolicyFactory)
+ public IRouteConstraint? ResolveConstraint(string inlineConstraint)
+ {
+ var routeConstraint = _inner.ResolveConstraint(inlineConstraint);
+ if (routeConstraint != null)
{
- _inner = inner;
- _parameterPolicyFactory = parameterPolicyFactory;
+ return routeConstraint;
}
- public IRouteConstraint? ResolveConstraint(string inlineConstraint)
+ var parameterPolicy = _parameterPolicyFactory.Create(null!, inlineConstraint);
+ if (parameterPolicy != null)
{
- var routeConstraint = _inner.ResolveConstraint(inlineConstraint);
- if (routeConstraint != null)
- {
- return routeConstraint;
- }
-
- var parameterPolicy = _parameterPolicyFactory.Create(null!, inlineConstraint);
- if (parameterPolicy != null)
- {
- // Logic inside Route will skip adding NullRouteConstraint
- return NullRouteConstraint.Instance;
- }
-
- return null;
+ // Logic inside Route will skip adding NullRouteConstraint
+ return NullRouteConstraint.Instance;
}
+
+ return null;
}
}
}
diff --git a/src/Http/Routing/src/Matching/AmbiguousMatchException.cs b/src/Http/Routing/src/Matching/AmbiguousMatchException.cs
index 10d02711e6..8acbccc0f2 100644
--- a/src/Http/Routing/src/Matching/AmbiguousMatchException.cs
+++ b/src/Http/Routing/src/Matching/AmbiguousMatchException.cs
@@ -4,22 +4,21 @@
using System;
using System.Runtime.Serialization;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// An exception which indicates multiple matches in endpoint selection.
+/// </summary>
+[Serializable]
+internal class AmbiguousMatchException : Exception
{
- /// <summary>
- /// An exception which indicates multiple matches in endpoint selection.
- /// </summary>
- [Serializable]
- internal class AmbiguousMatchException : Exception
+ public AmbiguousMatchException(string message)
+ : base(message)
{
- public AmbiguousMatchException(string message)
- : base(message)
- {
- }
+ }
- protected AmbiguousMatchException(SerializationInfo info, StreamingContext context)
- : base(info, context)
- {
- }
+ protected AmbiguousMatchException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/src/Matching/Ascii.cs b/src/Http/Routing/src/Matching/Ascii.cs
index 1820a3ce3b..69cb1e64e1 100644
--- a/src/Http/Routing/src/Matching/Ascii.cs
+++ b/src/Http/Routing/src/Matching/Ascii.cs
@@ -5,71 +5,70 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal static class Ascii
{
- internal static class Ascii
+ // case-sensitive equality comparison when we KNOW that 'a' is in the ASCII range
+ // and we know that the spans are the same length.
+ //
+ // Similar to https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs#L549
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool AsciiIgnoreCaseEquals(ReadOnlySpan<char> a, ReadOnlySpan<char> b, int length)
{
- // case-sensitive equality comparison when we KNOW that 'a' is in the ASCII range
- // and we know that the spans are the same length.
- //
- // Similar to https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs#L549
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool AsciiIgnoreCaseEquals(ReadOnlySpan<char> a, ReadOnlySpan<char> b, int length)
+ // The caller should have checked the length. We enforce that here by THROWING if the
+ // lengths are unequal.
+ if (a.Length < length || b.Length < length)
{
- // The caller should have checked the length. We enforce that here by THROWING if the
- // lengths are unequal.
- if (a.Length < length || b.Length < length)
- {
- // This should never happen, but we don't want to have undefined
- // behavior if it does.
- ThrowArgumentExceptionForLength();
- }
+ // This should never happen, but we don't want to have undefined
+ // behavior if it does.
+ ThrowArgumentExceptionForLength();
+ }
- ref var charA = ref MemoryMarshal.GetReference(a);
- ref var charB = ref MemoryMarshal.GetReference(b);
+ ref var charA = ref MemoryMarshal.GetReference(a);
+ ref var charB = ref MemoryMarshal.GetReference(b);
- // Iterates each span for the provided length and compares each character
- // case-insensitively. This looks funky because we're using unsafe operations
- // to elide bounds-checks.
- while (length > 0 && AsciiIgnoreCaseEquals(charA, charB))
- {
- charA = ref Unsafe.Add(ref charA, 1);
- charB = ref Unsafe.Add(ref charB, 1);
- length--;
- }
-
- return length == 0;
+ // Iterates each span for the provided length and compares each character
+ // case-insensitively. This looks funky because we're using unsafe operations
+ // to elide bounds-checks.
+ while (length > 0 && AsciiIgnoreCaseEquals(charA, charB))
+ {
+ charA = ref Unsafe.Add(ref charA, 1);
+ charB = ref Unsafe.Add(ref charB, 1);
+ length--;
}
- // case-insensitive equality comparison for characters in the ASCII range
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool AsciiIgnoreCaseEquals(char charA, char charB)
- {
- const uint AsciiToLower = 0x20;
- return
- // Equal when chars are exactly equal
- charA == charB ||
+ return length == 0;
+ }
- // Equal when converted to-lower AND they are letters
- ((charA | AsciiToLower) == (charB | AsciiToLower) && (uint)((charA | AsciiToLower) - 'a') <= (uint)('z' - 'a'));
- }
+ // case-insensitive equality comparison for characters in the ASCII range
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool AsciiIgnoreCaseEquals(char charA, char charB)
+ {
+ const uint AsciiToLower = 0x20;
+ return
+ // Equal when chars are exactly equal
+ charA == charB ||
+
+ // Equal when converted to-lower AND they are letters
+ ((charA | AsciiToLower) == (charB | AsciiToLower) && (uint)((charA | AsciiToLower) - 'a') <= (uint)('z' - 'a'));
+ }
- public static bool IsAscii(string text)
+ public static bool IsAscii(string text)
+ {
+ for (var i = 0; i < text.Length; i++)
{
- for (var i = 0; i < text.Length; i++)
+ if (text[i] > (char)0x7F)
{
- if (text[i] > (char)0x7F)
- {
- return false;
- }
+ return false;
}
-
- return true;
}
- private static void ThrowArgumentExceptionForLength()
- {
- throw new ArgumentException("length");
- }
+ return true;
+ }
+
+ private static void ThrowArgumentExceptionForLength()
+ {
+ throw new ArgumentException("length");
}
}
diff --git a/src/Http/Routing/src/Matching/Candidate.cs b/src/Http/Routing/src/Matching/Candidate.cs
index ab97dc6e0a..e906118104 100644
--- a/src/Http/Routing/src/Matching/Candidate.cs
+++ b/src/Http/Routing/src/Matching/Candidate.cs
@@ -6,120 +6,119 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal readonly struct Candidate
{
- internal readonly struct Candidate
+ public readonly Endpoint Endpoint;
+
+ // Used to optimize out operations that modify route values.
+ public readonly CandidateFlags Flags;
+
+ // Data for creating the RouteValueDictionary. We assign each key its own slot
+ // and we fill the values array with all of the default values.
+ //
+ // Then when we process parameters, we don't need to operate on the RouteValueDictionary
+ // we can just operate on an array, which is much much faster.
+ public readonly KeyValuePair<string, object>[] Slots;
+
+ // List of parameters to capture. Segment is the segment index, index is the
+ // index into the values array.
+ public readonly (string parameterName, int segmentIndex, int slotIndex)[] Captures;
+
+ // Catchall parameter to capture (limit one per template).
+ public readonly (string parameterName, int segmentIndex, int slotIndex) CatchAll;
+
+ // Complex segments are processed in a separate pass because they require a
+ // RouteValueDictionary.
+ public readonly (RoutePatternPathSegment pathSegment, int segmentIndex)[] ComplexSegments;
+
+ public readonly KeyValuePair<string, IRouteConstraint>[] Constraints;
+
+ // Score is a sequential integer value that in determines the priority of an Endpoint.
+ // Scores are computed within the context of candidate set, and are meaningless when
+ // applied to endpoints not in the set.
+ //
+ // The score concept boils down the system of comparisons done when ordering Endpoints
+ // to a single value that can be compared easily. This can be defeated by having
+ // int32.MaxValue + 1 endpoints in a single set, but you would have other problems by
+ // that point.
+ //
+ // Score is not part of the Endpoint itself, because it's contextual based on where
+ // the endpoint appears. An Endpoint is often be a member of multiple candidate sets.
+ public readonly int Score;
+
+ // Used in tests.
+ public Candidate(Endpoint endpoint)
{
- public readonly Endpoint Endpoint;
-
- // Used to optimize out operations that modify route values.
- public readonly CandidateFlags Flags;
-
- // Data for creating the RouteValueDictionary. We assign each key its own slot
- // and we fill the values array with all of the default values.
- //
- // Then when we process parameters, we don't need to operate on the RouteValueDictionary
- // we can just operate on an array, which is much much faster.
- public readonly KeyValuePair<string, object>[] Slots;
-
- // List of parameters to capture. Segment is the segment index, index is the
- // index into the values array.
- public readonly (string parameterName, int segmentIndex, int slotIndex)[] Captures;
-
- // Catchall parameter to capture (limit one per template).
- public readonly (string parameterName, int segmentIndex, int slotIndex) CatchAll;
-
- // Complex segments are processed in a separate pass because they require a
- // RouteValueDictionary.
- public readonly (RoutePatternPathSegment pathSegment, int segmentIndex)[] ComplexSegments;
-
- public readonly KeyValuePair<string, IRouteConstraint>[] Constraints;
-
- // Score is a sequential integer value that in determines the priority of an Endpoint.
- // Scores are computed within the context of candidate set, and are meaningless when
- // applied to endpoints not in the set.
- //
- // The score concept boils down the system of comparisons done when ordering Endpoints
- // to a single value that can be compared easily. This can be defeated by having
- // int32.MaxValue + 1 endpoints in a single set, but you would have other problems by
- // that point.
- //
- // Score is not part of the Endpoint itself, because it's contextual based on where
- // the endpoint appears. An Endpoint is often be a member of multiple candidate sets.
- public readonly int Score;
-
- // Used in tests.
- public Candidate(Endpoint endpoint)
- {
- Endpoint = endpoint;
+ Endpoint = endpoint;
- Slots = Array.Empty<KeyValuePair<string, object>>();
- Captures = Array.Empty<(string parameterName, int segmentIndex, int slotIndex)>();
- CatchAll = default;
- ComplexSegments = Array.Empty<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
- Constraints = Array.Empty<KeyValuePair<string, IRouteConstraint>>();
- Score = 0;
+ Slots = Array.Empty<KeyValuePair<string, object>>();
+ Captures = Array.Empty<(string parameterName, int segmentIndex, int slotIndex)>();
+ CatchAll = default;
+ ComplexSegments = Array.Empty<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
+ Constraints = Array.Empty<KeyValuePair<string, IRouteConstraint>>();
+ Score = 0;
- Flags = CandidateFlags.None;
- }
+ Flags = CandidateFlags.None;
+ }
- public Candidate(
- Endpoint endpoint,
- int score,
- KeyValuePair<string, object>[] slots,
- (string parameterName, int segmentIndex, int slotIndex)[] captures,
- in (string parameterName, int segmentIndex, int slotIndex) catchAll,
- (RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
- KeyValuePair<string, IRouteConstraint>[] constraints)
+ public Candidate(
+ Endpoint endpoint,
+ int score,
+ KeyValuePair<string, object>[] slots,
+ (string parameterName, int segmentIndex, int slotIndex)[] captures,
+ in (string parameterName, int segmentIndex, int slotIndex) catchAll,
+ (RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
+ KeyValuePair<string, IRouteConstraint>[] constraints)
+ {
+ Endpoint = endpoint;
+ Score = score;
+ Slots = slots;
+ Captures = captures;
+ CatchAll = catchAll;
+ ComplexSegments = complexSegments;
+ Constraints = constraints;
+
+ Flags = CandidateFlags.None;
+ for (var i = 0; i < slots.Length; i++)
{
- Endpoint = endpoint;
- Score = score;
- Slots = slots;
- Captures = captures;
- CatchAll = catchAll;
- ComplexSegments = complexSegments;
- Constraints = constraints;
-
- Flags = CandidateFlags.None;
- for (var i = 0; i < slots.Length; i++)
- {
- if (slots[i].Key != null)
- {
- Flags |= CandidateFlags.HasDefaults;
- }
- }
-
- if (captures.Length > 0)
+ if (slots[i].Key != null)
{
- Flags |= CandidateFlags.HasCaptures;
+ Flags |= CandidateFlags.HasDefaults;
}
+ }
- if (catchAll.parameterName != null)
- {
- Flags |= CandidateFlags.HasCatchAll;
- }
+ if (captures.Length > 0)
+ {
+ Flags |= CandidateFlags.HasCaptures;
+ }
- if (complexSegments.Length > 0)
- {
- Flags |= CandidateFlags.HasComplexSegments;
- }
+ if (catchAll.parameterName != null)
+ {
+ Flags |= CandidateFlags.HasCatchAll;
+ }
- if (constraints.Length > 0)
- {
- Flags |= CandidateFlags.HasConstraints;
- }
+ if (complexSegments.Length > 0)
+ {
+ Flags |= CandidateFlags.HasComplexSegments;
}
- [Flags]
- public enum CandidateFlags
+ if (constraints.Length > 0)
{
- None = 0,
- HasDefaults = 1,
- HasCaptures = 2,
- HasCatchAll = 4,
- HasSlots = HasDefaults | HasCaptures | HasCatchAll,
- HasComplexSegments = 8,
- HasConstraints = 16,
+ Flags |= CandidateFlags.HasConstraints;
}
}
+
+ [Flags]
+ public enum CandidateFlags
+ {
+ None = 0,
+ HasDefaults = 1,
+ HasCaptures = 2,
+ HasCatchAll = 4,
+ HasSlots = HasDefaults | HasCaptures | HasCatchAll,
+ HasComplexSegments = 8,
+ HasConstraints = 16,
+ }
}
diff --git a/src/Http/Routing/src/Matching/CandidateSet.cs b/src/Http/Routing/src/Matching/CandidateSet.cs
index 9a7df16550..188e63329a 100644
--- a/src/Http/Routing/src/Matching/CandidateSet.cs
+++ b/src/Http/Routing/src/Matching/CandidateSet.cs
@@ -12,116 +12,95 @@ using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// Represents a set of <see cref="Endpoint"/> candidates that have been matched
+/// by the routing system. Used by implementations of <see cref="EndpointSelector"/>
+/// and <see cref="IEndpointSelectorPolicy"/>.
+/// </summary>
+public sealed class CandidateSet
{
+ internal CandidateState[] Candidates;
+
/// <summary>
- /// Represents a set of <see cref="Endpoint"/> candidates that have been matched
- /// by the routing system. Used by implementations of <see cref="EndpointSelector"/>
+ /// <para>
+ /// Initializes a new instances of the <see cref="CandidateSet"/> class with the provided <paramref name="endpoints"/>,
+ /// <paramref name="values"/>, and <paramref name="scores"/>.
+ /// </para>
+ /// <para>
+ /// The constructor is provided to enable unit tests of implementations of <see cref="EndpointSelector"/>
/// and <see cref="IEndpointSelectorPolicy"/>.
+ /// </para>
/// </summary>
- public sealed class CandidateSet
+ /// <param name="endpoints">The list of endpoints, sorted in descending priority order.</param>
+ /// <param name="values">The list of <see cref="RouteValueDictionary"/> instances.</param>
+ /// <param name="scores">The list of endpoint scores. <see cref="CandidateState.Score"/>.</param>
+ public CandidateSet(Endpoint[] endpoints, RouteValueDictionary[] values, int[] scores)
{
- internal CandidateState[] Candidates;
-
- /// <summary>
- /// <para>
- /// Initializes a new instances of the <see cref="CandidateSet"/> class with the provided <paramref name="endpoints"/>,
- /// <paramref name="values"/>, and <paramref name="scores"/>.
- /// </para>
- /// <para>
- /// The constructor is provided to enable unit tests of implementations of <see cref="EndpointSelector"/>
- /// and <see cref="IEndpointSelectorPolicy"/>.
- /// </para>
- /// </summary>
- /// <param name="endpoints">The list of endpoints, sorted in descending priority order.</param>
- /// <param name="values">The list of <see cref="RouteValueDictionary"/> instances.</param>
- /// <param name="scores">The list of endpoint scores. <see cref="CandidateState.Score"/>.</param>
- public CandidateSet(Endpoint[] endpoints, RouteValueDictionary[] values, int[] scores)
+ if (endpoints == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- if (values == null)
- {
- throw new ArgumentNullException(nameof(values));
- }
-
- if (scores == null)
- {
- throw new ArgumentNullException(nameof(scores));
- }
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- if (endpoints.Length != values.Length || endpoints.Length != scores.Length)
- {
- throw new ArgumentException($"The provided {nameof(endpoints)}, {nameof(values)}, and {nameof(scores)} must have the same length.");
- }
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- Candidates = new CandidateState[endpoints.Length];
- for (var i = 0; i < endpoints.Length; i++)
- {
- Candidates[i] = new CandidateState(endpoints[i], values[i], scores[i]);
- }
+ if (scores == null)
+ {
+ throw new ArgumentNullException(nameof(scores));
}
- // Used in tests.
- internal CandidateSet(Candidate[] candidates)
+ if (endpoints.Length != values.Length || endpoints.Length != scores.Length)
{
- Candidates = new CandidateState[candidates.Length];
- for (var i = 0; i < candidates.Length; i++)
- {
- Candidates[i] = new CandidateState(candidates[i].Endpoint, candidates[i].Score);
- }
+ throw new ArgumentException($"The provided {nameof(endpoints)}, {nameof(values)}, and {nameof(scores)} must have the same length.");
}
- internal CandidateSet(CandidateState[] candidates)
+ Candidates = new CandidateState[endpoints.Length];
+ for (var i = 0; i < endpoints.Length; i++)
{
- Candidates = candidates;
+ Candidates[i] = new CandidateState(endpoints[i], values[i], scores[i]);
}
+ }
- /// <summary>
- /// Gets the count of candidates in the set.
- /// </summary>
- public int Count => Candidates.Length;
-
- /// <summary>
- /// Gets the <see cref="CandidateState"/> associated with the candidate <see cref="Endpoint"/>
- /// at <paramref name="index"/>.
- /// </summary>
- /// <param name="index">The candidate index.</param>
- /// <returns>
- /// A reference to the <see cref="CandidateState"/>. The result is returned by reference.
- /// </returns>
- public ref CandidateState this[int index]
+ // Used in tests.
+ internal CandidateSet(Candidate[] candidates)
+ {
+ Candidates = new CandidateState[candidates.Length];
+ for (var i = 0; i < candidates.Length; i++)
{
- // Note that this is a ref-return because of performance.
- // We don't want to copy these fat structs if it can be avoided.
+ Candidates[i] = new CandidateState(candidates[i].Endpoint, candidates[i].Score);
+ }
+ }
- // PERF: Force inlining
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- // Friendliness for inlining
- if ((uint)index >= Count)
- {
- ThrowIndexArgumentOutOfRangeException();
- }
+ internal CandidateSet(CandidateState[] candidates)
+ {
+ Candidates = candidates;
+ }
- return ref Candidates[index];
- }
- }
+ /// <summary>
+ /// Gets the count of candidates in the set.
+ /// </summary>
+ public int Count => Candidates.Length;
+
+ /// <summary>
+ /// Gets the <see cref="CandidateState"/> associated with the candidate <see cref="Endpoint"/>
+ /// at <paramref name="index"/>.
+ /// </summary>
+ /// <param name="index">The candidate index.</param>
+ /// <returns>
+ /// A reference to the <see cref="CandidateState"/>. The result is returned by reference.
+ /// </returns>
+ public ref CandidateState this[int index]
+ {
+ // Note that this is a ref-return because of performance.
+ // We don't want to copy these fat structs if it can be avoided.
- /// <summary>
- /// Gets a value which indicates where the <see cref="Http.Endpoint"/> is considered
- /// a valid candidate for the current request.
- /// </summary>
- /// <param name="index">The candidate index.</param>
- /// <returns>
- /// <c>true</c> if the candidate at position <paramref name="index"/> is considered valid
- /// for the current request, otherwise <c>false</c>.
- /// </returns>
- public bool IsValidCandidate(int index)
+ // PERF: Force inlining
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
{
// Friendliness for inlining
if ((uint)index >= Count)
@@ -129,256 +108,276 @@ namespace Microsoft.AspNetCore.Routing.Matching
ThrowIndexArgumentOutOfRangeException();
}
- return IsValidCandidate(ref Candidates[index]);
+ return ref Candidates[index];
}
+ }
- internal static bool IsValidCandidate(ref CandidateState candidate)
+ /// <summary>
+ /// Gets a value which indicates where the <see cref="Http.Endpoint"/> is considered
+ /// a valid candidate for the current request.
+ /// </summary>
+ /// <param name="index">The candidate index.</param>
+ /// <returns>
+ /// <c>true</c> if the candidate at position <paramref name="index"/> is considered valid
+ /// for the current request, otherwise <c>false</c>.
+ /// </returns>
+ public bool IsValidCandidate(int index)
+ {
+ // Friendliness for inlining
+ if ((uint)index >= Count)
{
- return candidate.Score >= 0;
+ ThrowIndexArgumentOutOfRangeException();
}
- /// <summary>
- /// Sets the validity of the candidate at the provided index.
- /// </summary>
- /// <param name="index">The candidate index.</param>
- /// <param name="value">
- /// The value to set. If <c>true</c> the candidate is considered valid for the current request.
- /// </param>
- public void SetValidity(int index, bool value)
- {
- // Friendliness for inlining
- if ((uint)index >= Count)
- {
- ThrowIndexArgumentOutOfRangeException();
- }
+ return IsValidCandidate(ref Candidates[index]);
+ }
- ref var original = ref Candidates[index];
- SetValidity(ref original, value);
- }
+ internal static bool IsValidCandidate(ref CandidateState candidate)
+ {
+ return candidate.Score >= 0;
+ }
- internal static void SetValidity(ref CandidateState candidate, bool value)
+ /// <summary>
+ /// Sets the validity of the candidate at the provided index.
+ /// </summary>
+ /// <param name="index">The candidate index.</param>
+ /// <param name="value">
+ /// The value to set. If <c>true</c> the candidate is considered valid for the current request.
+ /// </param>
+ public void SetValidity(int index, bool value)
+ {
+ // Friendliness for inlining
+ if ((uint)index >= Count)
{
- var originalScore = candidate.Score;
- var score = originalScore >= 0 ^ value ? ~originalScore : originalScore;
- candidate = new CandidateState(candidate.Endpoint, candidate.Values, score);
+ ThrowIndexArgumentOutOfRangeException();
}
- /// <summary>
- /// Replaces the <see cref="Endpoint"/> at the provided <paramref name="index"/> with the
- /// provided <paramref name="endpoint"/>.
- /// </summary>
- /// <param name="index">The candidate index.</param>
- /// <param name="endpoint">
- /// The <see cref="Endpoint"/> to replace the original <see cref="Endpoint"/> at
- /// the <paramref name="index"/>. If <paramref name="endpoint"/> is <c>null</c>. the candidate will be marked
- /// as invalid.
- /// </param>
- /// <param name="values">
- /// The <see cref="RouteValueDictionary"/> to replace the original <see cref="RouteValueDictionary"/> at
- /// the <paramref name="index"/>.
- /// </param>
- public void ReplaceEndpoint(int index, Endpoint? endpoint, RouteValueDictionary? values)
+ ref var original = ref Candidates[index];
+ SetValidity(ref original, value);
+ }
+
+ internal static void SetValidity(ref CandidateState candidate, bool value)
+ {
+ var originalScore = candidate.Score;
+ var score = originalScore >= 0 ^ value ? ~originalScore : originalScore;
+ candidate = new CandidateState(candidate.Endpoint, candidate.Values, score);
+ }
+
+ /// <summary>
+ /// Replaces the <see cref="Endpoint"/> at the provided <paramref name="index"/> with the
+ /// provided <paramref name="endpoint"/>.
+ /// </summary>
+ /// <param name="index">The candidate index.</param>
+ /// <param name="endpoint">
+ /// The <see cref="Endpoint"/> to replace the original <see cref="Endpoint"/> at
+ /// the <paramref name="index"/>. If <paramref name="endpoint"/> is <c>null</c>. the candidate will be marked
+ /// as invalid.
+ /// </param>
+ /// <param name="values">
+ /// The <see cref="RouteValueDictionary"/> to replace the original <see cref="RouteValueDictionary"/> at
+ /// the <paramref name="index"/>.
+ /// </param>
+ public void ReplaceEndpoint(int index, Endpoint? endpoint, RouteValueDictionary? values)
+ {
+ // Friendliness for inlining
+ if ((uint)index >= Count)
{
- // Friendliness for inlining
- if ((uint)index >= Count)
- {
- ThrowIndexArgumentOutOfRangeException();
- }
+ ThrowIndexArgumentOutOfRangeException();
+ }
- // CandidateState allows a null-valued endpoint. However a validate candidate should never have a null endpoint
- // We'll make lives easier for matcher policies by declaring it as non-null.
- Candidates[index] = new CandidateState(endpoint!, values, Candidates[index].Score);
+ // CandidateState allows a null-valued endpoint. However a validate candidate should never have a null endpoint
+ // We'll make lives easier for matcher policies by declaring it as non-null.
+ Candidates[index] = new CandidateState(endpoint!, values, Candidates[index].Score);
- if (endpoint == null)
- {
- SetValidity(index, false);
- }
+ if (endpoint == null)
+ {
+ SetValidity(index, false);
}
+ }
- /// <summary>
- /// Replaces the <see cref="Endpoint"/> at the provided <paramref name="index"/> with the
- /// provided <paramref name="endpoints"/>.
- /// </summary>
- /// <param name="index">The candidate index.</param>
- /// <param name="endpoints">
- /// The list of endpoints <see cref="Endpoint"/> to replace the original <see cref="Endpoint"/> at
- /// the <paramref name="index"/>. If <paramref name="endpoints"/> is empty, the candidate will be marked
- /// as invalid.
- /// </param>
- /// <param name="comparer">
- /// The endpoint comparer used to order the endpoints. Can be retrieved from the service provider as
- /// type <see cref="EndpointMetadataComparer"/>.
- /// </param>
- /// <remarks>
- /// <para>
- /// This method supports replacing a dynamic endpoint with a collection of endpoints, and relying on
- /// <see cref="IEndpointSelectorPolicy"/> implementations to disambiguate further.
- /// </para>
- /// <para>
- /// The endpoint being replace should have a unique score value. The score is the combination of route
- /// patter precedence, order, and policy metadata evaluation. A dynamic endpoint will not function
- /// correctly if other endpoints exist with the same score.
- /// </para>
- /// </remarks>
- public void ExpandEndpoint(int index, IReadOnlyList<Endpoint> endpoints, IComparer<Endpoint> comparer)
+ /// <summary>
+ /// Replaces the <see cref="Endpoint"/> at the provided <paramref name="index"/> with the
+ /// provided <paramref name="endpoints"/>.
+ /// </summary>
+ /// <param name="index">The candidate index.</param>
+ /// <param name="endpoints">
+ /// The list of endpoints <see cref="Endpoint"/> to replace the original <see cref="Endpoint"/> at
+ /// the <paramref name="index"/>. If <paramref name="endpoints"/> is empty, the candidate will be marked
+ /// as invalid.
+ /// </param>
+ /// <param name="comparer">
+ /// The endpoint comparer used to order the endpoints. Can be retrieved from the service provider as
+ /// type <see cref="EndpointMetadataComparer"/>.
+ /// </param>
+ /// <remarks>
+ /// <para>
+ /// This method supports replacing a dynamic endpoint with a collection of endpoints, and relying on
+ /// <see cref="IEndpointSelectorPolicy"/> implementations to disambiguate further.
+ /// </para>
+ /// <para>
+ /// The endpoint being replace should have a unique score value. The score is the combination of route
+ /// patter precedence, order, and policy metadata evaluation. A dynamic endpoint will not function
+ /// correctly if other endpoints exist with the same score.
+ /// </para>
+ /// </remarks>
+ public void ExpandEndpoint(int index, IReadOnlyList<Endpoint> endpoints, IComparer<Endpoint> comparer)
+ {
+ // Friendliness for inlining
+ if ((uint)index >= Count)
{
- // Friendliness for inlining
- if ((uint)index >= Count)
- {
- ThrowIndexArgumentOutOfRangeException();
- }
+ ThrowIndexArgumentOutOfRangeException();
+ }
- if (endpoints == null)
- {
- ThrowArgumentNullException(nameof(endpoints));
- }
+ if (endpoints == null)
+ {
+ ThrowArgumentNullException(nameof(endpoints));
+ }
- if (comparer == null)
- {
- ThrowArgumentNullException(nameof(comparer));
- }
+ if (comparer == null)
+ {
+ ThrowArgumentNullException(nameof(comparer));
+ }
- // First we need to verify that the score of what we're replacing is unique.
- ValidateUniqueScore(index);
+ // First we need to verify that the score of what we're replacing is unique.
+ ValidateUniqueScore(index);
- switch (endpoints.Count)
- {
- case 0:
- ReplaceEndpoint(index, null, null);
- break;
-
- case 1:
- ReplaceEndpoint(index, endpoints[0], Candidates[index].Values);
- break;
-
- default:
-
- var score = GetOriginalScore(index);
- var values = Candidates[index].Values;
-
- // Adding candidates requires expanding the array and computing new score values for the new candidates.
- var original = Candidates;
- var candidates = new CandidateState[original.Length - 1 + endpoints.Count];
- Candidates = candidates;
-
- // Since the new endpoints have an unknown ordering relationship to each other, we need to:
- // - order them
- // - assign scores
- // - offset everything that comes after
- //
- // If the inputs look like:
- //
- // score 0: A1
- // score 0: A2
- // score 1: B
- // score 2: C <-- being expanded
- // score 3: D
- //
- // Then the result should look like:
- //
- // score 0: A1
- // score 0: A2
- // score 1: B
- // score 2: `C1
- // score 3: `C2
- // score 4: D
-
- // Candidates before index can be copied unchanged.
- for (var i = 0; i < index; i++)
- {
- candidates[i] = original[i];
- }
+ switch (endpoints.Count)
+ {
+ case 0:
+ ReplaceEndpoint(index, null, null);
+ break;
+
+ case 1:
+ ReplaceEndpoint(index, endpoints[0], Candidates[index].Values);
+ break;
+
+ default:
+
+ var score = GetOriginalScore(index);
+ var values = Candidates[index].Values;
+
+ // Adding candidates requires expanding the array and computing new score values for the new candidates.
+ var original = Candidates;
+ var candidates = new CandidateState[original.Length - 1 + endpoints.Count];
+ Candidates = candidates;
+
+ // Since the new endpoints have an unknown ordering relationship to each other, we need to:
+ // - order them
+ // - assign scores
+ // - offset everything that comes after
+ //
+ // If the inputs look like:
+ //
+ // score 0: A1
+ // score 0: A2
+ // score 1: B
+ // score 2: C <-- being expanded
+ // score 3: D
+ //
+ // Then the result should look like:
+ //
+ // score 0: A1
+ // score 0: A2
+ // score 1: B
+ // score 2: `C1
+ // score 3: `C2
+ // score 4: D
+
+ // Candidates before index can be copied unchanged.
+ for (var i = 0; i < index; i++)
+ {
+ candidates[i] = original[i];
+ }
+
+ var buffer = endpoints.ToArray();
+ Array.Sort<Endpoint>(buffer, comparer);
- var buffer = endpoints.ToArray();
- Array.Sort<Endpoint>(buffer, comparer);
+ // Add the first new endpoint with the current score
+ candidates[index] = new CandidateState(buffer[0], values, score);
- // Add the first new endpoint with the current score
- candidates[index] = new CandidateState(buffer[0], values, score);
+ var scoreOffset = 0;
+ for (var i = 1; i < buffer.Length; i++)
+ {
+ var cmp = comparer.Compare(buffer[i - 1], buffer[i]);
- var scoreOffset = 0;
- for (var i = 1; i < buffer.Length; i++)
+ // This should not be possible. This would mean that sorting is wrong.
+ Debug.Assert(cmp <= 0);
+ if (cmp == 0)
{
- var cmp = comparer.Compare(buffer[i - 1], buffer[i]);
-
- // This should not be possible. This would mean that sorting is wrong.
- Debug.Assert(cmp <= 0);
- if (cmp == 0)
- {
- // Score is unchanged.
- }
- else if (cmp < 0)
- {
- // Endpoint is lower priority, higher score.
- scoreOffset++;
- }
-
- Candidates[i + index] = new CandidateState(buffer[i], values, score + scoreOffset);
+ // Score is unchanged.
}
-
- for (var i = index + 1; i < original.Length; i++)
+ else if (cmp < 0)
{
- Candidates[i + endpoints.Count - 1] = new CandidateState(original[i].Endpoint, original[i].Values, original[i].Score + scoreOffset);
+ // Endpoint is lower priority, higher score.
+ scoreOffset++;
}
- break;
+ Candidates[i + index] = new CandidateState(buffer[i], values, score + scoreOffset);
+ }
+
+ for (var i = index + 1; i < original.Length; i++)
+ {
+ Candidates[i + endpoints.Count - 1] = new CandidateState(original[i].Endpoint, original[i].Values, original[i].Score + scoreOffset);
+ }
+
+ break;
- }
}
+ }
+
+ // Returns the *positive* score value. Score is used to track valid/invalid which can cause it to be negative.
+ //
+ // This is the original score and used to determine if there are ambiguities.
+ private int GetOriginalScore(int index)
+ {
+ var score = Candidates[index].Score;
+ return score >= 0 ? score : ~score;
+ }
- // Returns the *positive* score value. Score is used to track valid/invalid which can cause it to be negative.
- //
- // This is the original score and used to determine if there are ambiguities.
- private int GetOriginalScore(int index)
+ private void ValidateUniqueScore(int index)
+ {
+ var score = GetOriginalScore(index);
+
+ var count = 0;
+ var candidates = Candidates;
+ for (var i = 0; i < candidates.Length; i++)
{
- var score = Candidates[index].Score;
- return score >= 0 ? score : ~score;
+ if (GetOriginalScore(i) == score)
+ {
+ count++;
+ }
}
- private void ValidateUniqueScore(int index)
+ Debug.Assert(count > 0);
+ if (count > 1)
{
- var score = GetOriginalScore(index);
-
- var count = 0;
- var candidates = Candidates;
+ // Uh-oh. We don't allow duplicates with ExpandEndpoint because that will do unpredictable things.
+ var duplicates = new List<Endpoint>();
for (var i = 0; i < candidates.Length; i++)
{
if (GetOriginalScore(i) == score)
{
- count++;
+ duplicates.Add(candidates[i].Endpoint!);
}
}
- Debug.Assert(count > 0);
- if (count > 1)
- {
- // Uh-oh. We don't allow duplicates with ExpandEndpoint because that will do unpredictable things.
- var duplicates = new List<Endpoint>();
- for (var i = 0; i < candidates.Length; i++)
- {
- if (GetOriginalScore(i) == score)
- {
- duplicates.Add(candidates[i].Endpoint!);
- }
- }
-
- var message =
- $"Using {nameof(ExpandEndpoint)} requires that the replaced endpoint have a unique priority. " +
- $"The following endpoints were found with the same priority:" + Environment.NewLine +
- string.Join(Environment.NewLine, duplicates.Select(e => e.DisplayName));
- throw new InvalidOperationException(message);
- }
+ var message =
+ $"Using {nameof(ExpandEndpoint)} requires that the replaced endpoint have a unique priority. " +
+ $"The following endpoints were found with the same priority:" + Environment.NewLine +
+ string.Join(Environment.NewLine, duplicates.Select(e => e.DisplayName));
+ throw new InvalidOperationException(message);
}
+ }
- [DoesNotReturn]
- private static void ThrowIndexArgumentOutOfRangeException()
- {
- throw new ArgumentOutOfRangeException("index");
- }
+ [DoesNotReturn]
+ private static void ThrowIndexArgumentOutOfRangeException()
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
- [DoesNotReturn]
- private static void ThrowArgumentNullException(string parameter)
- {
- throw new ArgumentNullException(parameter);
- }
+ [DoesNotReturn]
+ private static void ThrowArgumentNullException(string parameter)
+ {
+ throw new ArgumentNullException(parameter);
}
}
diff --git a/src/Http/Routing/src/Matching/CandidateState.cs b/src/Http/Routing/src/Matching/CandidateState.cs
index 1c9b0f3128..8eae3a22c3 100644
--- a/src/Http/Routing/src/Matching/CandidateState.cs
+++ b/src/Http/Routing/src/Matching/CandidateState.cs
@@ -3,53 +3,52 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// The state associated with a candidate in a <see cref="CandidateSet"/>.
+/// </summary>
+public struct CandidateState
{
- /// <summary>
- /// The state associated with a candidate in a <see cref="CandidateSet"/>.
- /// </summary>
- public struct CandidateState
+ internal CandidateState(Endpoint endpoint, int score)
{
- internal CandidateState(Endpoint endpoint, int score)
- {
- Endpoint = endpoint;
- Score = score;
- Values = null;
- }
+ Endpoint = endpoint;
+ Score = score;
+ Values = null;
+ }
- internal CandidateState(Endpoint endpoint, RouteValueDictionary? values, int score)
- {
- Endpoint = endpoint;
- Values = values;
- Score = score;
- }
+ internal CandidateState(Endpoint endpoint, RouteValueDictionary? values, int score)
+ {
+ Endpoint = endpoint;
+ Values = values;
+ Score = score;
+ }
- /// <summary>
- /// Gets the <see cref="Http.Endpoint"/>.
- /// </summary>
- public Endpoint Endpoint { get; }
+ /// <summary>
+ /// Gets the <see cref="Http.Endpoint"/>.
+ /// </summary>
+ public Endpoint Endpoint { get; }
- /// <summary>
- /// Gets the score of the <see cref="Http.Endpoint"/> within the current
- /// <see cref="CandidateSet"/>.
- /// </summary>
- /// <remarks>
- /// <para>
- /// Candidates within a set are ordered in priority order and then assigned a
- /// sequential score value based on that ordering. Candiates with the same
- /// score are considered to have equal priority.
- /// </para>
- /// <para>
- /// The score values are used in the <see cref="EndpointSelector"/> to determine
- /// whether a set of matching candidates is an ambiguous match.
- /// </para>
- /// </remarks>
- public int Score { get; }
+ /// <summary>
+ /// Gets the score of the <see cref="Http.Endpoint"/> within the current
+ /// <see cref="CandidateSet"/>.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Candidates within a set are ordered in priority order and then assigned a
+ /// sequential score value based on that ordering. Candiates with the same
+ /// score are considered to have equal priority.
+ /// </para>
+ /// <para>
+ /// The score values are used in the <see cref="EndpointSelector"/> to determine
+ /// whether a set of matching candidates is an ambiguous match.
+ /// </para>
+ /// </remarks>
+ public int Score { get; }
- /// <summary>
- /// Gets <see cref="RouteValueDictionary"/> associated with the
- /// <see cref="Http.Endpoint"/> and the current request.
- /// </summary>
- public RouteValueDictionary? Values { get; internal set; }
- }
+ /// <summary>
+ /// Gets <see cref="RouteValueDictionary"/> associated with the
+ /// <see cref="Http.Endpoint"/> and the current request.
+ /// </summary>
+ public RouteValueDictionary? Values { get; internal set; }
}
diff --git a/src/Http/Routing/src/Matching/DataSourceDependentMatcher.cs b/src/Http/Routing/src/Matching/DataSourceDependentMatcher.cs
index 2906028a02..cc01324dea 100644
--- a/src/Http/Routing/src/Matching/DataSourceDependentMatcher.cs
+++ b/src/Http/Routing/src/Matching/DataSourceDependentMatcher.cs
@@ -8,105 +8,104 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal sealed class DataSourceDependentMatcher : Matcher
{
- internal sealed class DataSourceDependentMatcher : Matcher
- {
- private readonly Func<MatcherBuilder> _matcherBuilderFactory;
- private readonly DataSourceDependentCache<Matcher> _cache;
+ private readonly Func<MatcherBuilder> _matcherBuilderFactory;
+ private readonly DataSourceDependentCache<Matcher> _cache;
- public DataSourceDependentMatcher(
- EndpointDataSource dataSource,
- Lifetime lifetime,
- Func<MatcherBuilder> matcherBuilderFactory)
- {
- _matcherBuilderFactory = matcherBuilderFactory;
+ public DataSourceDependentMatcher(
+ EndpointDataSource dataSource,
+ Lifetime lifetime,
+ Func<MatcherBuilder> matcherBuilderFactory)
+ {
+ _matcherBuilderFactory = matcherBuilderFactory;
- _cache = new DataSourceDependentCache<Matcher>(dataSource, CreateMatcher);
- _cache.EnsureInitialized();
+ _cache = new DataSourceDependentCache<Matcher>(dataSource, CreateMatcher);
+ _cache.EnsureInitialized();
- // This will Dispose the cache when the lifetime is disposed, this allows
- // the service provider to manage the lifetime of the cache.
- lifetime.Cache = _cache;
- }
+ // This will Dispose the cache when the lifetime is disposed, this allows
+ // the service provider to manage the lifetime of the cache.
+ lifetime.Cache = _cache;
+ }
- // Used in tests
- internal Matcher CurrentMatcher => _cache.Value!;
+ // Used in tests
+ internal Matcher CurrentMatcher => _cache.Value!;
- public override Task MatchAsync(HttpContext httpContext)
- {
- return CurrentMatcher.MatchAsync(httpContext);
- }
+ public override Task MatchAsync(HttpContext httpContext)
+ {
+ return CurrentMatcher.MatchAsync(httpContext);
+ }
- private Matcher CreateMatcher(IReadOnlyList<Endpoint> endpoints)
+ private Matcher CreateMatcher(IReadOnlyList<Endpoint> endpoints)
+ {
+ var builder = _matcherBuilderFactory();
+ var seenEndpointNames = new Dictionary<string, string?>();
+ for (var i = 0; i < endpoints.Count; i++)
{
- var builder = _matcherBuilderFactory();
- var seenEndpointNames = new Dictionary<string, string?>();
- for (var i = 0; i < endpoints.Count; i++)
+ // By design we only look at RouteEndpoint here. It's possible to
+ // register other endpoint types, which are non-routable, and it's
+ // ok that we won't route to them.
+ if (endpoints[i] is RouteEndpoint endpoint)
{
- // By design we only look at RouteEndpoint here. It's possible to
- // register other endpoint types, which are non-routable, and it's
- // ok that we won't route to them.
- if (endpoints[i] is RouteEndpoint endpoint)
+ // Validate that endpoint names are unique.
+ var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName;
+ if (endpointName is not null)
{
- // Validate that endpoint names are unique.
- var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName;
- if (endpointName is not null)
+ if (seenEndpointNames.TryGetValue(endpointName, out var existingEndpoint))
{
- if (seenEndpointNames.TryGetValue(endpointName, out var existingEndpoint))
- {
- throw new InvalidOperationException($"Duplicate endpoint name '{endpointName}' found on '{endpoint.DisplayName}' and '{existingEndpoint}'. Endpoint names must be globally unique.");
- }
-
- seenEndpointNames.Add(endpointName, endpoint.DisplayName ?? endpoint.RoutePattern.RawText);
+ throw new InvalidOperationException($"Duplicate endpoint name '{endpointName}' found on '{endpoint.DisplayName}' and '{existingEndpoint}'. Endpoint names must be globally unique.");
}
- // We check for duplicate endpoint names on all endpoints regardless
- // of whether they suppress matching because endpoint names can be
- // used in OpenAPI specifications as well.
- if (endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching != true)
- {
- builder.AddEndpoint(endpoint);
- }
+ seenEndpointNames.Add(endpointName, endpoint.DisplayName ?? endpoint.RoutePattern.RawText);
}
- }
- return builder.Build();
+ // We check for duplicate endpoint names on all endpoints regardless
+ // of whether they suppress matching because endpoint names can be
+ // used in OpenAPI specifications as well.
+ if (endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching != true)
+ {
+ builder.AddEndpoint(endpoint);
+ }
+ }
}
- // Used to tie the lifetime of a DataSourceDependentCache to the service provider
- public sealed class Lifetime : IDisposable
- {
- private readonly object _lock = new object();
- private DataSourceDependentCache<Matcher>? _cache;
- private bool _disposed;
+ return builder.Build();
+ }
- public DataSourceDependentCache<Matcher>? Cache
+ // Used to tie the lifetime of a DataSourceDependentCache to the service provider
+ public sealed class Lifetime : IDisposable
+ {
+ private readonly object _lock = new object();
+ private DataSourceDependentCache<Matcher>? _cache;
+ private bool _disposed;
+
+ public DataSourceDependentCache<Matcher>? Cache
+ {
+ get => _cache;
+ set
{
- get => _cache;
- set
+ lock (_lock)
{
- lock (_lock)
+ if (_disposed)
{
- if (_disposed)
- {
- value?.Dispose();
- }
-
- _cache = value;
+ value?.Dispose();
}
+
+ _cache = value;
}
}
+ }
- public void Dispose()
+ public void Dispose()
+ {
+ lock (_lock)
{
- lock (_lock)
- {
- _cache?.Dispose();
- _cache = null;
+ _cache?.Dispose();
+ _cache = null;
- _disposed = true;
- }
+ _disposed = true;
}
}
}
diff --git a/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs b/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs
index 9cb7684579..eddde5aec7 100644
--- a/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs
+++ b/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs
@@ -7,131 +7,130 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal sealed class DefaultEndpointSelector : EndpointSelector
{
- internal sealed class DefaultEndpointSelector : EndpointSelector
+ public override Task SelectAsync(
+ HttpContext httpContext,
+ CandidateSet candidateSet)
{
- public override Task SelectAsync(
- HttpContext httpContext,
- CandidateSet candidateSet)
+ if (httpContext == null)
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- if (candidateSet == null)
- {
- throw new ArgumentNullException(nameof(candidateSet));
- }
-
- Select(httpContext, candidateSet.Candidates);
- return Task.CompletedTask;
+ throw new ArgumentNullException(nameof(httpContext));
}
- internal static void Select(HttpContext httpContext, CandidateState[] candidateState)
+ if (candidateSet == null)
{
- // Fast path: We can specialize for trivial numbers of candidates since there can
- // be no ambiguities
- switch (candidateState.Length)
- {
- case 0:
- {
- // Do nothing
- break;
- }
-
- case 1:
- {
- ref var state = ref candidateState[0];
- if (CandidateSet.IsValidCandidate(ref state))
- {
- httpContext.SetEndpoint(state.Endpoint);
- httpContext.Request.RouteValues = state.Values!;
- }
-
- break;
- }
-
- default:
- {
- // Slow path: There's more than one candidate (to say nothing of validity) so we
- // have to process for ambiguities.
- ProcessFinalCandidates(httpContext, candidateState);
- break;
- }
- }
+ throw new ArgumentNullException(nameof(candidateSet));
}
- private static void ProcessFinalCandidates(
- HttpContext httpContext,
- CandidateState[] candidateState)
+ Select(httpContext, candidateSet.Candidates);
+ return Task.CompletedTask;
+ }
+
+ internal static void Select(HttpContext httpContext, CandidateState[] candidateState)
+ {
+ // Fast path: We can specialize for trivial numbers of candidates since there can
+ // be no ambiguities
+ switch (candidateState.Length)
{
- Endpoint? endpoint = null;
- RouteValueDictionary? values = null;
- int? foundScore = null;
- for (var i = 0; i < candidateState.Length; i++)
- {
- ref var state = ref candidateState[i];
- if (!CandidateSet.IsValidCandidate(ref state))
+ case 0:
{
- continue;
+ // Do nothing
+ break;
}
- if (foundScore == null)
+ case 1:
{
- // This is the first match we've seen - speculatively assign it.
- endpoint = state.Endpoint;
- values = state.Values;
- foundScore = state.Score;
+ ref var state = ref candidateState[0];
+ if (CandidateSet.IsValidCandidate(ref state))
+ {
+ httpContext.SetEndpoint(state.Endpoint);
+ httpContext.Request.RouteValues = state.Values!;
+ }
+
+ break;
}
- else if (foundScore < state.Score)
+
+ default:
{
- // This candidate is lower priority than the one we've seen
- // so far, we can stop.
- //
- // Don't worry about the 'null < state.Score' case, it returns false.
+ // Slow path: There's more than one candidate (to say nothing of validity) so we
+ // have to process for ambiguities.
+ ProcessFinalCandidates(httpContext, candidateState);
break;
}
- else if (foundScore == state.Score)
- {
- // This is the second match we've found of the same score, so there
- // must be an ambiguity.
- //
- // Don't worry about the 'null == state.Score' case, it returns false.
-
- ReportAmbiguity(candidateState);
+ }
+ }
- // Unreachable, ReportAmbiguity always throws.
- throw new NotSupportedException();
- }
+ private static void ProcessFinalCandidates(
+ HttpContext httpContext,
+ CandidateState[] candidateState)
+ {
+ Endpoint? endpoint = null;
+ RouteValueDictionary? values = null;
+ int? foundScore = null;
+ for (var i = 0; i < candidateState.Length; i++)
+ {
+ ref var state = ref candidateState[i];
+ if (!CandidateSet.IsValidCandidate(ref state))
+ {
+ continue;
}
- if (endpoint != null)
+ if (foundScore == null)
{
- httpContext.SetEndpoint(endpoint);
- httpContext.Request.RouteValues = values!;
+ // This is the first match we've seen - speculatively assign it.
+ endpoint = state.Endpoint;
+ values = state.Values;
+ foundScore = state.Score;
}
+ else if (foundScore < state.Score)
+ {
+ // This candidate is lower priority than the one we've seen
+ // so far, we can stop.
+ //
+ // Don't worry about the 'null < state.Score' case, it returns false.
+ break;
+ }
+ else if (foundScore == state.Score)
+ {
+ // This is the second match we've found of the same score, so there
+ // must be an ambiguity.
+ //
+ // Don't worry about the 'null == state.Score' case, it returns false.
+
+ ReportAmbiguity(candidateState);
+
+ // Unreachable, ReportAmbiguity always throws.
+ throw new NotSupportedException();
+ }
+ }
+
+ if (endpoint != null)
+ {
+ httpContext.SetEndpoint(endpoint);
+ httpContext.Request.RouteValues = values!;
}
+ }
- private static void ReportAmbiguity(CandidateState[] candidateState)
+ private static void ReportAmbiguity(CandidateState[] candidateState)
+ {
+ // If we get here it's the result of an ambiguity - we're OK with this
+ // being a littler slower and more allocatey.
+ var matches = new List<Endpoint>();
+ for (var i = 0; i < candidateState.Length; i++)
{
- // If we get here it's the result of an ambiguity - we're OK with this
- // being a littler slower and more allocatey.
- var matches = new List<Endpoint>();
- for (var i = 0; i < candidateState.Length; i++)
+ ref var state = ref candidateState[i];
+ if (CandidateSet.IsValidCandidate(ref state))
{
- ref var state = ref candidateState[i];
- if (CandidateSet.IsValidCandidate(ref state))
- {
- matches.Add(state.Endpoint);
- }
+ matches.Add(state.Endpoint);
}
-
- var message = Resources.FormatAmbiguousEndpoints(
- Environment.NewLine,
- string.Join(Environment.NewLine, matches.Select(e => e.DisplayName)));
- throw new AmbiguousMatchException(message);
}
+
+ var message = Resources.FormatAmbiguousEndpoints(
+ Environment.NewLine,
+ string.Join(Environment.NewLine, matches.Select(e => e.DisplayName)));
+ throw new AmbiguousMatchException(message);
}
}
diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs
index f789d50627..4f30c8df69 100644
--- a/src/Http/Routing/src/Matching/DfaMatcher.cs
+++ b/src/Http/Routing/src/Matching/DfaMatcher.cs
@@ -9,405 +9,404 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal sealed partial class DfaMatcher : Matcher
{
- internal sealed partial class DfaMatcher : Matcher
+ private readonly ILogger _logger;
+ private readonly EndpointSelector _selector;
+ private readonly DfaState[] _states;
+ private readonly int _maxSegmentCount;
+ private readonly bool _isDefaultEndpointSelector;
+
+ public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount)
{
- private readonly ILogger _logger;
- private readonly EndpointSelector _selector;
- private readonly DfaState[] _states;
- private readonly int _maxSegmentCount;
- private readonly bool _isDefaultEndpointSelector;
+ _logger = logger;
+ _selector = selector;
+ _states = states;
+ _maxSegmentCount = maxSegmentCount;
+ _isDefaultEndpointSelector = selector is DefaultEndpointSelector;
+ }
- public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount)
+ [SkipLocalsInit]
+ public sealed override Task MatchAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
{
- _logger = logger;
- _selector = selector;
- _states = states;
- _maxSegmentCount = maxSegmentCount;
- _isDefaultEndpointSelector = selector is DefaultEndpointSelector;
+ throw new ArgumentNullException(nameof(httpContext));
}
- [SkipLocalsInit]
- public sealed override Task MatchAsync(HttpContext httpContext)
- {
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- // All of the logging we do here is at level debug, so we can get away with doing a single check.
- var log = _logger.IsEnabled(LogLevel.Debug);
+ // All of the logging we do here is at level debug, so we can get away with doing a single check.
+ var log = _logger.IsEnabled(LogLevel.Debug);
- // The sequence of actions we take is optimized to avoid doing expensive work
- // like creating substrings, creating route value dictionaries, and calling
- // into policies like versioning.
- var path = httpContext.Request.Path.Value!;
+ // The sequence of actions we take is optimized to avoid doing expensive work
+ // like creating substrings, creating route value dictionaries, and calling
+ // into policies like versioning.
+ var path = httpContext.Request.Path.Value!;
- // First tokenize the path into series of segments.
- Span<PathSegment> buffer = stackalloc PathSegment[_maxSegmentCount];
- var count = FastPathTokenizer.Tokenize(path, buffer);
- var segments = buffer.Slice(0, count);
-
- // FindCandidateSet will process the DFA and return a candidate set. This does
- // some preliminary matching of the URL (mostly the literal segments).
- var (candidates, policies) = FindCandidateSet(httpContext, path, segments);
- var candidateCount = candidates.Length;
- if (candidateCount == 0)
- {
- if (log)
- {
- Log.CandidatesNotFound(_logger, path);
- }
-
- return Task.CompletedTask;
- }
+ // First tokenize the path into series of segments.
+ Span<PathSegment> buffer = stackalloc PathSegment[_maxSegmentCount];
+ var count = FastPathTokenizer.Tokenize(path, buffer);
+ var segments = buffer.Slice(0, count);
+ // FindCandidateSet will process the DFA and return a candidate set. This does
+ // some preliminary matching of the URL (mostly the literal segments).
+ var (candidates, policies) = FindCandidateSet(httpContext, path, segments);
+ var candidateCount = candidates.Length;
+ if (candidateCount == 0)
+ {
if (log)
{
- Log.CandidatesFound(_logger, path, candidates);
+ Log.CandidatesNotFound(_logger, path);
}
- var policyCount = policies.Length;
+ return Task.CompletedTask;
+ }
- // This is a fast path for single candidate, 0 policies and default selector
- if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector)
- {
- ref readonly var candidate = ref candidates[0];
+ if (log)
+ {
+ Log.CandidatesFound(_logger, path, candidates);
+ }
- // Just strict path matching (no route values)
- if (candidate.Flags == Candidate.CandidateFlags.None)
- {
- httpContext.SetEndpoint(candidate.Endpoint);
+ var policyCount = policies.Length;
- // We're done
- return Task.CompletedTask;
- }
+ // This is a fast path for single candidate, 0 policies and default selector
+ if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector)
+ {
+ ref readonly var candidate = ref candidates[0];
+
+ // Just strict path matching (no route values)
+ if (candidate.Flags == Candidate.CandidateFlags.None)
+ {
+ httpContext.SetEndpoint(candidate.Endpoint);
+
+ // We're done
+ return Task.CompletedTask;
}
+ }
- // At this point we have a candidate set, defined as a list of endpoints in
- // priority order.
- //
- // We don't yet know that any candidate can be considered a match, because
- // we haven't processed things like route constraints and complex segments.
+ // At this point we have a candidate set, defined as a list of endpoints in
+ // priority order.
+ //
+ // We don't yet know that any candidate can be considered a match, because
+ // we haven't processed things like route constraints and complex segments.
+ //
+ // Now we'll iterate each endpoint to capture route values, process constraints,
+ // and process complex segments.
+
+ // `candidates` has all of our internal state that we use to process the
+ // set of endpoints before we call the EndpointSelector.
+ //
+ // `candidateSet` is the mutable state that we pass to the EndpointSelector.
+ var candidateState = new CandidateState[candidateCount];
+
+ for (var i = 0; i < candidateCount; i++)
+ {
+ // PERF: using ref here to avoid copying around big structs.
//
- // Now we'll iterate each endpoint to capture route values, process constraints,
- // and process complex segments.
+ // Reminder!
+ // candidate: readonly data about the endpoint and how to match
+ // state: mutable storarge for our processing
+ ref readonly var candidate = ref candidates[i];
+ ref var state = ref candidateState[i];
+ state = new CandidateState(candidate.Endpoint, candidate.Score);
- // `candidates` has all of our internal state that we use to process the
- // set of endpoints before we call the EndpointSelector.
- //
- // `candidateSet` is the mutable state that we pass to the EndpointSelector.
- var candidateState = new CandidateState[candidateCount];
+ var flags = candidate.Flags;
- for (var i = 0; i < candidateCount; i++)
+ // First process all of the parameters and defaults.
+ if ((flags & Candidate.CandidateFlags.HasSlots) != 0)
{
- // PERF: using ref here to avoid copying around big structs.
+ // The Slots array has the default values of the route values in it.
//
- // Reminder!
- // candidate: readonly data about the endpoint and how to match
- // state: mutable storarge for our processing
- ref readonly var candidate = ref candidates[i];
- ref var state = ref candidateState[i];
- state = new CandidateState(candidate.Endpoint, candidate.Score);
+ // We want to create a new array for the route values based on Slots
+ // as a prototype.
+ var prototype = candidate.Slots;
+ var slots = new KeyValuePair<string, object?>[prototype.Length];
- var flags = candidate.Flags;
-
- // First process all of the parameters and defaults.
- if ((flags & Candidate.CandidateFlags.HasSlots) != 0)
+ if ((flags & Candidate.CandidateFlags.HasDefaults) != 0)
{
- // The Slots array has the default values of the route values in it.
- //
- // We want to create a new array for the route values based on Slots
- // as a prototype.
- var prototype = candidate.Slots;
- var slots = new KeyValuePair<string, object?>[prototype.Length];
-
- if ((flags & Candidate.CandidateFlags.HasDefaults) != 0)
- {
- Array.Copy(prototype, 0, slots, 0, prototype.Length);
- }
-
- if ((flags & Candidate.CandidateFlags.HasCaptures) != 0)
- {
- ProcessCaptures(slots, candidate.Captures, path, segments);
- }
-
- if ((flags & Candidate.CandidateFlags.HasCatchAll) != 0)
- {
- ProcessCatchAll(slots, candidate.CatchAll, path, segments);
- }
-
- state.Values = RouteValueDictionary.FromArray(slots);
+ Array.Copy(prototype, 0, slots, 0, prototype.Length);
}
- // Now that we have the route values, we need to process complex segments.
- // Complex segments go through an old API that requires a fully-materialized
- // route value dictionary.
- var isMatch = true;
- if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
+ if ((flags & Candidate.CandidateFlags.HasCaptures) != 0)
{
- state.Values ??= new RouteValueDictionary();
- if (!ProcessComplexSegments(candidate.Endpoint, candidate.ComplexSegments, path, segments, state.Values))
- {
- CandidateSet.SetValidity(ref state, false);
- isMatch = false;
- }
+ ProcessCaptures(slots, candidate.Captures, path, segments);
}
- if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
+ if ((flags & Candidate.CandidateFlags.HasCatchAll) != 0)
{
- state.Values ??= new RouteValueDictionary();
- if (!ProcessConstraints(candidate.Endpoint, candidate.Constraints, httpContext, state.Values))
- {
- CandidateSet.SetValidity(ref state, false);
- isMatch = false;
- }
+ ProcessCatchAll(slots, candidate.CatchAll, path, segments);
}
- if (log)
+ state.Values = RouteValueDictionary.FromArray(slots);
+ }
+
+ // Now that we have the route values, we need to process complex segments.
+ // Complex segments go through an old API that requires a fully-materialized
+ // route value dictionary.
+ var isMatch = true;
+ if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
+ {
+ state.Values ??= new RouteValueDictionary();
+ if (!ProcessComplexSegments(candidate.Endpoint, candidate.ComplexSegments, path, segments, state.Values))
{
- if (isMatch)
- {
- Log.CandidateValid(_logger, path, candidate.Endpoint);
- }
- else
- {
- Log.CandidateNotValid(_logger, path, candidate.Endpoint);
- }
+ CandidateSet.SetValidity(ref state, false);
+ isMatch = false;
}
}
- if (policyCount == 0 && _isDefaultEndpointSelector)
+ if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
{
- // Fast path that avoids allocating the candidate set.
- //
- // We can use this when there are no policies and we're using the default selector.
- DefaultEndpointSelector.Select(httpContext, candidateState);
- return Task.CompletedTask;
+ state.Values ??= new RouteValueDictionary();
+ if (!ProcessConstraints(candidate.Endpoint, candidate.Constraints, httpContext, state.Values))
+ {
+ CandidateSet.SetValidity(ref state, false);
+ isMatch = false;
+ }
}
- else if (policyCount == 0)
+
+ if (log)
{
- // Fast path that avoids a state machine.
- //
- // We can use this when there are no policies and a non-default selector.
- return _selector.SelectAsync(httpContext, new CandidateSet(candidateState));
+ if (isMatch)
+ {
+ Log.CandidateValid(_logger, path, candidate.Endpoint);
+ }
+ else
+ {
+ Log.CandidateNotValid(_logger, path, candidate.Endpoint);
+ }
}
-
- return SelectEndpointWithPoliciesAsync(httpContext, policies, new CandidateSet(candidateState));
}
- internal (Candidate[] candidates, IEndpointSelectorPolicy[] policies) FindCandidateSet(
- HttpContext httpContext,
- string path,
- ReadOnlySpan<PathSegment> segments)
+ if (policyCount == 0 && _isDefaultEndpointSelector)
{
- var states = _states;
-
- // Process each path segment
- var destination = 0;
- for (var i = 0; i < segments.Length; i++)
- {
- destination = states[destination].PathTransitions.GetDestination(path, segments[i]);
- }
+ // Fast path that avoids allocating the candidate set.
+ //
+ // We can use this when there are no policies and we're using the default selector.
+ DefaultEndpointSelector.Select(httpContext, candidateState);
+ return Task.CompletedTask;
+ }
+ else if (policyCount == 0)
+ {
+ // Fast path that avoids a state machine.
+ //
+ // We can use this when there are no policies and a non-default selector.
+ return _selector.SelectAsync(httpContext, new CandidateSet(candidateState));
+ }
- // Process an arbitrary number of policy-based decisions
- var policyTransitions = states[destination].PolicyTransitions;
- while (policyTransitions != null)
- {
- destination = policyTransitions.GetDestination(httpContext);
- policyTransitions = states[destination].PolicyTransitions;
- }
+ return SelectEndpointWithPoliciesAsync(httpContext, policies, new CandidateSet(candidateState));
+ }
- return (states[destination].Candidates, states[destination].Policies);
- }
+ internal (Candidate[] candidates, IEndpointSelectorPolicy[] policies) FindCandidateSet(
+ HttpContext httpContext,
+ string path,
+ ReadOnlySpan<PathSegment> segments)
+ {
+ var states = _states;
- private static void ProcessCaptures(
- KeyValuePair<string, object?>[] slots,
- (string parameterName, int segmentIndex, int slotIndex)[] captures,
- string path,
- ReadOnlySpan<PathSegment> segments)
+ // Process each path segment
+ var destination = 0;
+ for (var i = 0; i < segments.Length; i++)
{
- for (var i = 0; i < captures.Length; i++)
- {
- (var parameterName, var segmentIndex, var slotIndex) = captures[i];
-
- if ((uint)segmentIndex < (uint)segments.Length)
- {
- var segment = segments[segmentIndex];
- if (parameterName != null && segment.Length > 0)
- {
- slots[slotIndex] = new KeyValuePair<string, object?>(
- parameterName,
- path.Substring(segment.Start, segment.Length));
- }
- }
- }
+ destination = states[destination].PathTransitions.GetDestination(path, segments[i]);
}
- private static void ProcessCatchAll(
- KeyValuePair<string, object?>[] slots,
- in (string parameterName, int segmentIndex, int slotIndex) catchAll,
- string path,
- ReadOnlySpan<PathSegment> segments)
+ // Process an arbitrary number of policy-based decisions
+ var policyTransitions = states[destination].PolicyTransitions;
+ while (policyTransitions != null)
{
- // Read segmentIndex to local both to skip double read from stack value
- // and to use the same in-bounds validated variable to access the array.
- var segmentIndex = catchAll.segmentIndex;
- if ((uint)segmentIndex < (uint)segments.Length)
- {
- var segment = segments[segmentIndex];
- slots[catchAll.slotIndex] = new KeyValuePair<string, object?>(
- catchAll.parameterName,
- path.Substring(segment.Start));
- }
+ destination = policyTransitions.GetDestination(httpContext);
+ policyTransitions = states[destination].PolicyTransitions;
}
- private bool ProcessComplexSegments(
- Endpoint endpoint,
- (RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
- string path,
- ReadOnlySpan<PathSegment> segments,
- RouteValueDictionary values)
+ return (states[destination].Candidates, states[destination].Policies);
+ }
+
+ private static void ProcessCaptures(
+ KeyValuePair<string, object?>[] slots,
+ (string parameterName, int segmentIndex, int slotIndex)[] captures,
+ string path,
+ ReadOnlySpan<PathSegment> segments)
+ {
+ for (var i = 0; i < captures.Length; i++)
{
- for (var i = 0; i < complexSegments.Length; i++)
+ (var parameterName, var segmentIndex, var slotIndex) = captures[i];
+
+ if ((uint)segmentIndex < (uint)segments.Length)
{
- (var complexSegment, var segmentIndex) = complexSegments[i];
var segment = segments[segmentIndex];
- var text = path.AsSpan(segment.Start, segment.Length);
- if (!RoutePatternMatcher.MatchComplexSegment(complexSegment, text, values))
+ if (parameterName != null && segment.Length > 0)
{
- Log.CandidateRejectedByComplexSegment(_logger, path, endpoint, complexSegment);
- return false;
+ slots[slotIndex] = new KeyValuePair<string, object?>(
+ parameterName,
+ path.Substring(segment.Start, segment.Length));
}
}
+ }
+ }
- return true;
+ private static void ProcessCatchAll(
+ KeyValuePair<string, object?>[] slots,
+ in (string parameterName, int segmentIndex, int slotIndex) catchAll,
+ string path,
+ ReadOnlySpan<PathSegment> segments)
+ {
+ // Read segmentIndex to local both to skip double read from stack value
+ // and to use the same in-bounds validated variable to access the array.
+ var segmentIndex = catchAll.segmentIndex;
+ if ((uint)segmentIndex < (uint)segments.Length)
+ {
+ var segment = segments[segmentIndex];
+ slots[catchAll.slotIndex] = new KeyValuePair<string, object?>(
+ catchAll.parameterName,
+ path.Substring(segment.Start));
}
+ }
- private bool ProcessConstraints(
- Endpoint endpoint,
- KeyValuePair<string, IRouteConstraint>[] constraints,
- HttpContext httpContext,
- RouteValueDictionary values)
+ private bool ProcessComplexSegments(
+ Endpoint endpoint,
+ (RoutePatternPathSegment pathSegment, int segmentIndex)[] complexSegments,
+ string path,
+ ReadOnlySpan<PathSegment> segments,
+ RouteValueDictionary values)
+ {
+ for (var i = 0; i < complexSegments.Length; i++)
{
- for (var i = 0; i < constraints.Length; i++)
+ (var complexSegment, var segmentIndex) = complexSegments[i];
+ var segment = segments[segmentIndex];
+ var text = path.AsSpan(segment.Start, segment.Length);
+ if (!RoutePatternMatcher.MatchComplexSegment(complexSegment, text, values))
{
- var constraint = constraints[i];
- if (!constraint.Value.Match(httpContext, NullRouter.Instance, constraint.Key, values, RouteDirection.IncomingRequest))
- {
- Log.CandidateRejectedByConstraint(_logger, httpContext.Request.Path, endpoint, constraint.Key, constraint.Value, values[constraint.Key]);
- return false;
- }
+ Log.CandidateRejectedByComplexSegment(_logger, path, endpoint, complexSegment);
+ return false;
}
-
- return true;
}
- private async Task SelectEndpointWithPoliciesAsync(
- HttpContext httpContext,
- IEndpointSelectorPolicy[] policies,
- CandidateSet candidateSet)
+ return true;
+ }
+
+ private bool ProcessConstraints(
+ Endpoint endpoint,
+ KeyValuePair<string, IRouteConstraint>[] constraints,
+ HttpContext httpContext,
+ RouteValueDictionary values)
+ {
+ for (var i = 0; i < constraints.Length; i++)
{
- for (var i = 0; i < policies.Length; i++)
+ var constraint = constraints[i];
+ if (!constraint.Value.Match(httpContext, NullRouter.Instance, constraint.Key, values, RouteDirection.IncomingRequest))
{
- var policy = policies[i];
- await policy.ApplyAsync(httpContext, candidateSet);
- if (httpContext.GetEndpoint() != null)
- {
- // This is a short circuit, the selector chose an endpoint.
- return;
- }
+ Log.CandidateRejectedByConstraint(_logger, httpContext.Request.Path, endpoint, constraint.Key, constraint.Value, values[constraint.Key]);
+ return false;
}
-
- await _selector.SelectAsync(httpContext, candidateSet);
}
- private static partial class Log
+ return true;
+ }
+
+ private async Task SelectEndpointWithPoliciesAsync(
+ HttpContext httpContext,
+ IEndpointSelectorPolicy[] policies,
+ CandidateSet candidateSet)
+ {
+ for (var i = 0; i < policies.Length; i++)
{
- [LoggerMessage(1000, LogLevel.Debug,
- "No candidates found for the request path '{Path}'",
- EventName = "CandidatesNotFound",
- SkipEnabledCheck = true)]
- public static partial void CandidatesNotFound(ILogger logger, string path);
-
- public static void CandidatesFound(ILogger logger, string path, Candidate[] candidates)
- => CandidatesFound(logger, candidates.Length, path);
-
- [LoggerMessage(1001, LogLevel.Debug,
- "{CandidateCount} candidate(s) found for the request path '{Path}'",
- EventName = "CandidatesFound",
- SkipEnabledCheck = true)]
- private static partial void CandidatesFound(ILogger logger, int candidateCount, string path);
-
- public static void CandidateRejectedByComplexSegment(ILogger logger, string path, Endpoint endpoint, RoutePatternPathSegment segment)
+ var policy = policies[i];
+ await policy.ApplyAsync(httpContext, candidateSet);
+ if (httpContext.GetEndpoint() != null)
{
- // This should return a real pattern since we're processing complex segments.... but just in case.
- if (logger.IsEnabled(LogLevel.Debug))
- {
- var routePattern = GetRoutePattern(endpoint);
- CandidateRejectedByComplexSegment(logger, endpoint.DisplayName, routePattern, segment.DebuggerToString(), path);
- }
+ // This is a short circuit, the selector chose an endpoint.
+ return;
}
+ }
- [LoggerMessage(1002, LogLevel.Debug,
- "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by complex segment '{Segment}' for the request path '{Path}'",
- EventName = "CandidateRejectedByComplexSegment",
- SkipEnabledCheck = true)]
- private static partial void CandidateRejectedByComplexSegment(ILogger logger, string? endpoint, string routePattern, string segment, string path);
+ await _selector.SelectAsync(httpContext, candidateSet);
+ }
- public static void CandidateRejectedByConstraint(ILogger logger, string path, Endpoint endpoint, string constraintName, IRouteConstraint constraint, object? value)
+ private static partial class Log
+ {
+ [LoggerMessage(1000, LogLevel.Debug,
+ "No candidates found for the request path '{Path}'",
+ EventName = "CandidatesNotFound",
+ SkipEnabledCheck = true)]
+ public static partial void CandidatesNotFound(ILogger logger, string path);
+
+ public static void CandidatesFound(ILogger logger, string path, Candidate[] candidates)
+ => CandidatesFound(logger, candidates.Length, path);
+
+ [LoggerMessage(1001, LogLevel.Debug,
+ "{CandidateCount} candidate(s) found for the request path '{Path}'",
+ EventName = "CandidatesFound",
+ SkipEnabledCheck = true)]
+ private static partial void CandidatesFound(ILogger logger, int candidateCount, string path);
+
+ public static void CandidateRejectedByComplexSegment(ILogger logger, string path, Endpoint endpoint, RoutePatternPathSegment segment)
+ {
+ // This should return a real pattern since we're processing complex segments.... but just in case.
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // This should return a real pattern since we're processing constraints.... but just in case.
- if (logger.IsEnabled(LogLevel.Debug))
- {
- var routePattern = GetRoutePattern(endpoint);
- CandidateRejectedByConstraint(logger, endpoint.DisplayName, routePattern, constraintName, constraint.ToString(), value, path);
- }
+ var routePattern = GetRoutePattern(endpoint);
+ CandidateRejectedByComplexSegment(logger, endpoint.DisplayName, routePattern, segment.DebuggerToString(), path);
}
+ }
- [LoggerMessage(1003, LogLevel.Debug,
- "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by constraint '{ConstraintName}':'{Constraint}' with value '{RouteValue}' for the request path '{Path}'",
- EventName = "CandidateRejectedByConstraint",
- SkipEnabledCheck = true)]
- private static partial void CandidateRejectedByConstraint(ILogger logger, string? endpoint, string routePattern, string constraintName, string? constraint, object? routeValue, string path);
+ [LoggerMessage(1002, LogLevel.Debug,
+ "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by complex segment '{Segment}' for the request path '{Path}'",
+ EventName = "CandidateRejectedByComplexSegment",
+ SkipEnabledCheck = true)]
+ private static partial void CandidateRejectedByComplexSegment(ILogger logger, string? endpoint, string routePattern, string segment, string path);
- public static void CandidateNotValid(ILogger logger, string path, Endpoint endpoint)
+ public static void CandidateRejectedByConstraint(ILogger logger, string path, Endpoint endpoint, string constraintName, IRouteConstraint constraint, object? value)
+ {
+ // This should return a real pattern since we're processing constraints.... but just in case.
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // This can be the fallback value because it really might not be a route endpoint
- if (logger.IsEnabled(LogLevel.Debug))
- {
- var routePattern = GetRoutePattern(endpoint);
- CandidateNotValid(logger, endpoint.DisplayName, routePattern, path);
- }
+ var routePattern = GetRoutePattern(endpoint);
+ CandidateRejectedByConstraint(logger, endpoint.DisplayName, routePattern, constraintName, constraint.ToString(), value, path);
}
+ }
- [LoggerMessage(1004, LogLevel.Debug,
- "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is not valid for the request path '{Path}'",
- EventName = "CandidateNotValid",
- SkipEnabledCheck = true)]
- private static partial void CandidateNotValid(ILogger logger, string? endpoint, string routePattern, string path);
+ [LoggerMessage(1003, LogLevel.Debug,
+ "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' was rejected by constraint '{ConstraintName}':'{Constraint}' with value '{RouteValue}' for the request path '{Path}'",
+ EventName = "CandidateRejectedByConstraint",
+ SkipEnabledCheck = true)]
+ private static partial void CandidateRejectedByConstraint(ILogger logger, string? endpoint, string routePattern, string constraintName, string? constraint, object? routeValue, string path);
- public static void CandidateValid(ILogger logger, string path, Endpoint endpoint)
+ public static void CandidateNotValid(ILogger logger, string path, Endpoint endpoint)
+ {
+ // This can be the fallback value because it really might not be a route endpoint
+ if (logger.IsEnabled(LogLevel.Debug))
{
- // This can be the fallback value because it really might not be a route endpoint
- if (logger.IsEnabled(LogLevel.Debug))
- {
- var routePattern = GetRoutePattern(endpoint);
- CandidateValid(logger, endpoint.DisplayName, routePattern, path);
- }
+ var routePattern = GetRoutePattern(endpoint);
+ CandidateNotValid(logger, endpoint.DisplayName, routePattern, path);
}
+ }
- [LoggerMessage(1005, LogLevel.Debug,
- "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is valid for the request path '{Path}'",
- EventName = "CandidateValid",
- SkipEnabledCheck = true)]
- private static partial void CandidateValid(ILogger logger, string? endpoint, string routePattern, string path);
+ [LoggerMessage(1004, LogLevel.Debug,
+ "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is not valid for the request path '{Path}'",
+ EventName = "CandidateNotValid",
+ SkipEnabledCheck = true)]
+ private static partial void CandidateNotValid(ILogger logger, string? endpoint, string routePattern, string path);
- private static string GetRoutePattern(Endpoint endpoint)
+ public static void CandidateValid(ILogger logger, string path, Endpoint endpoint)
+ {
+ // This can be the fallback value because it really might not be a route endpoint
+ if (logger.IsEnabled(LogLevel.Debug))
{
- return (endpoint as RouteEndpoint)?.RoutePattern?.RawText ?? "(none)";
+ var routePattern = GetRoutePattern(endpoint);
+ CandidateValid(logger, endpoint.DisplayName, routePattern, path);
}
}
+
+ [LoggerMessage(1005, LogLevel.Debug,
+ "Endpoint '{Endpoint}' with route pattern '{RoutePattern}' is valid for the request path '{Path}'",
+ EventName = "CandidateValid",
+ SkipEnabledCheck = true)]
+ private static partial void CandidateValid(ILogger logger, string? endpoint, string routePattern, string path);
+
+ private static string GetRoutePattern(Endpoint endpoint)
+ {
+ return (endpoint as RouteEndpoint)?.RoutePattern?.RawText ?? "(none)";
+ }
}
}
diff --git a/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs b/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs
index 2ceeb93d37..56b3b93d00 100644
--- a/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs
+++ b/src/Http/Routing/src/Matching/DfaMatcherBuilder.cs
@@ -12,937 +12,936 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class DfaMatcherBuilder : MatcherBuilder
{
- internal class DfaMatcherBuilder : MatcherBuilder
+ private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
+
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly ParameterPolicyFactory _parameterPolicyFactory;
+ private readonly EndpointSelector _selector;
+ private readonly IEndpointSelectorPolicy[] _endpointSelectorPolicies;
+ private readonly INodeBuilderPolicy[] _nodeBuilders;
+ private readonly EndpointComparer _comparer;
+
+ // These collections are reused when building candidates
+ private readonly Dictionary<string, int> _assignments;
+ private readonly List<KeyValuePair<string, object>> _slots;
+ private readonly List<(string parameterName, int segmentIndex, int slotIndex)> _captures;
+ private readonly List<(RoutePatternPathSegment pathSegment, int segmentIndex)> _complexSegments;
+ private readonly List<KeyValuePair<string, IRouteConstraint>> _constraints;
+
+ private int _stateIndex;
+
+ public DfaMatcherBuilder(
+ ILoggerFactory loggerFactory,
+ ParameterPolicyFactory parameterPolicyFactory,
+ EndpointSelector selector,
+ IEnumerable<MatcherPolicy> policies)
{
- private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
+ _loggerFactory = loggerFactory;
+ _parameterPolicyFactory = parameterPolicyFactory;
+ _selector = selector;
+
+ var (nodeBuilderPolicies, endpointComparerPolicies, endpointSelectorPolicies) = ExtractPolicies(policies.OrderBy(p => p.Order));
+ _endpointSelectorPolicies = endpointSelectorPolicies;
+ _nodeBuilders = nodeBuilderPolicies;
+ _comparer = new EndpointComparer(endpointComparerPolicies);
+
+ _assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
+ _slots = new List<KeyValuePair<string, object>>();
+ _captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
+ _complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
+ _constraints = new List<KeyValuePair<string, IRouteConstraint>>();
+ }
- private readonly ILoggerFactory _loggerFactory;
- private readonly ParameterPolicyFactory _parameterPolicyFactory;
- private readonly EndpointSelector _selector;
- private readonly IEndpointSelectorPolicy[] _endpointSelectorPolicies;
- private readonly INodeBuilderPolicy[] _nodeBuilders;
- private readonly EndpointComparer _comparer;
-
- // These collections are reused when building candidates
- private readonly Dictionary<string, int> _assignments;
- private readonly List<KeyValuePair<string, object>> _slots;
- private readonly List<(string parameterName, int segmentIndex, int slotIndex)> _captures;
- private readonly List<(RoutePatternPathSegment pathSegment, int segmentIndex)> _complexSegments;
- private readonly List<KeyValuePair<string, IRouteConstraint>> _constraints;
-
- private int _stateIndex;
-
- public DfaMatcherBuilder(
- ILoggerFactory loggerFactory,
- ParameterPolicyFactory parameterPolicyFactory,
- EndpointSelector selector,
- IEnumerable<MatcherPolicy> policies)
- {
- _loggerFactory = loggerFactory;
- _parameterPolicyFactory = parameterPolicyFactory;
- _selector = selector;
-
- var (nodeBuilderPolicies, endpointComparerPolicies, endpointSelectorPolicies) = ExtractPolicies(policies.OrderBy(p => p.Order));
- _endpointSelectorPolicies = endpointSelectorPolicies;
- _nodeBuilders = nodeBuilderPolicies;
- _comparer = new EndpointComparer(endpointComparerPolicies);
-
- _assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
- _slots = new List<KeyValuePair<string, object>>();
- _captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
- _complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
- _constraints = new List<KeyValuePair<string, IRouteConstraint>>();
- }
+ // Used in tests
+ internal EndpointComparer Comparer => _comparer;
- // Used in tests
- internal EndpointComparer Comparer => _comparer;
+ public override void AddEndpoint(RouteEndpoint endpoint)
+ {
+ _endpoints.Add(endpoint);
+ }
+
+ public DfaNode BuildDfaTree(bool includeLabel = false)
+ {
+ // Since we're doing a BFS we will process each 'level' of the tree in stages
+ // this list will hold the set of items we need to process at the current
+ // stage.
+ var work = new List<DfaBuilderWorkerWorkItem>(_endpoints.Count);
- public override void AddEndpoint(RouteEndpoint endpoint)
+ var root = new DfaNode() { PathDepth = 0, Label = includeLabel ? "/" : null };
+
+ // To prepare for this we need to compute the max depth, as well as
+ // a seed list of items to process (entry, root).
+ var maxDepth = 0;
+ for (var i = 0; i < _endpoints.Count; i++)
{
- _endpoints.Add(endpoint);
+ var endpoint = _endpoints[i];
+ var precedenceDigit = GetPrecedenceDigitAtDepth(endpoint, depth: 0);
+ work.Add(new DfaBuilderWorkerWorkItem(endpoint, precedenceDigit, new List<DfaNode>() { root, }));
+ maxDepth = Math.Max(maxDepth, endpoint.RoutePattern.PathSegments.Count);
}
- public DfaNode BuildDfaTree(bool includeLabel = false)
+ // Sort work at each level by *PRECEDENCE OF THE CURRENT SEGMENT*.
+ //
+ // We build the tree by doing a BFS over the list of entries. This is important
+ // because a 'parameter' node can also traverse the same paths that literal nodes
+ // traverse. This means that we need to order the entries first, or else we will
+ // miss possible edges in the DFA.
+ //
+ // We'll sort the matches again later using the *real* comparer once building the
+ // precedence part of the DFA is over.
+ var precedenceDigitComparer = Comparer<DfaBuilderWorkerWorkItem>.Create((x, y) =>
{
- // Since we're doing a BFS we will process each 'level' of the tree in stages
- // this list will hold the set of items we need to process at the current
- // stage.
- var work = new List<DfaBuilderWorkerWorkItem>(_endpoints.Count);
-
- var root = new DfaNode() { PathDepth = 0, Label = includeLabel ? "/" : null };
+ return x.PrecedenceDigit.CompareTo(y.PrecedenceDigit);
+ });
- // To prepare for this we need to compute the max depth, as well as
- // a seed list of items to process (entry, root).
- var maxDepth = 0;
- for (var i = 0; i < _endpoints.Count; i++)
- {
- var endpoint = _endpoints[i];
- var precedenceDigit = GetPrecedenceDigitAtDepth(endpoint, depth: 0);
- work.Add(new DfaBuilderWorkerWorkItem(endpoint, precedenceDigit, new List<DfaNode>() { root, }));
- maxDepth = Math.Max(maxDepth, endpoint.RoutePattern.PathSegments.Count);
- }
+ var dfaWorker = new DfaBuilderWorker(work, precedenceDigitComparer, includeLabel, _parameterPolicyFactory);
- // Sort work at each level by *PRECEDENCE OF THE CURRENT SEGMENT*.
- //
- // We build the tree by doing a BFS over the list of entries. This is important
- // because a 'parameter' node can also traverse the same paths that literal nodes
- // traverse. This means that we need to order the entries first, or else we will
- // miss possible edges in the DFA.
- //
- // We'll sort the matches again later using the *real* comparer once building the
- // precedence part of the DFA is over.
- var precedenceDigitComparer = Comparer<DfaBuilderWorkerWorkItem>.Create((x, y) =>
- {
- return x.PrecedenceDigit.CompareTo(y.PrecedenceDigit);
- });
+ // Now we process the entries a level at a time.
+ for (var depth = 0; depth <= maxDepth; depth++)
+ {
+ dfaWorker.ProcessLevel(depth);
+ }
- var dfaWorker = new DfaBuilderWorker(work, precedenceDigitComparer, includeLabel, _parameterPolicyFactory);
+ // Build the trees of policy nodes (like HTTP methods). Post-order traversal
+ // means that we won't have infinite recursion.
+ root.Visit(ApplyPolicies);
- // Now we process the entries a level at a time.
- for (var depth = 0; depth <= maxDepth; depth++)
- {
- dfaWorker.ProcessLevel(depth);
- }
+ return root;
+ }
- // Build the trees of policy nodes (like HTTP methods). Post-order traversal
- // means that we won't have infinite recursion.
- root.Visit(ApplyPolicies);
+ private class DfaBuilderWorker
+ {
+ private List<DfaBuilderWorkerWorkItem> _previousWork;
+ private List<DfaBuilderWorkerWorkItem> _work;
+ private int _workCount;
+ private readonly Comparer<DfaBuilderWorkerWorkItem> _precedenceDigitComparer;
+ private readonly bool _includeLabel;
+ private readonly ParameterPolicyFactory _parameterPolicyFactory;
- return root;
+ public DfaBuilderWorker(
+ List<DfaBuilderWorkerWorkItem> work,
+ Comparer<DfaBuilderWorkerWorkItem> precedenceDigitComparer,
+ bool includeLabel,
+ ParameterPolicyFactory parameterPolicyFactory)
+ {
+ _work = work;
+ _previousWork = new List<DfaBuilderWorkerWorkItem>();
+ _workCount = work.Count;
+ _precedenceDigitComparer = precedenceDigitComparer;
+ _includeLabel = includeLabel;
+ _parameterPolicyFactory = parameterPolicyFactory;
}
- private class DfaBuilderWorker
+ // Each time we process a level of the DFA we keep a list of work items consisting on the nodes we need to evaluate
+ // their precendence and their parent nodes. We sort nodes by precedence on each level, which means that nodes are
+ // evaluated in the following order: (literals, constrained parameters/complex segments, parameters, constrainted catch-alls and catch-alls)
+ // When we process a stage we build a list of the next set of workitems we need to evaluate. We also keep around the
+ // list of workitems from the previous level so that we can reuse all the nested lists while we are evaluating the current level.
+ internal void ProcessLevel(int depth)
{
- private List<DfaBuilderWorkerWorkItem> _previousWork;
- private List<DfaBuilderWorkerWorkItem> _work;
- private int _workCount;
- private readonly Comparer<DfaBuilderWorkerWorkItem> _precedenceDigitComparer;
- private readonly bool _includeLabel;
- private readonly ParameterPolicyFactory _parameterPolicyFactory;
+ // As we process items, collect the next set of items.
+ var nextWork = _previousWork;
+ var nextWorkCount = 0;
- public DfaBuilderWorker(
- List<DfaBuilderWorkerWorkItem> work,
- Comparer<DfaBuilderWorkerWorkItem> precedenceDigitComparer,
- bool includeLabel,
- ParameterPolicyFactory parameterPolicyFactory)
- {
- _work = work;
- _previousWork = new List<DfaBuilderWorkerWorkItem>();
- _workCount = work.Count;
- _precedenceDigitComparer = precedenceDigitComparer;
- _includeLabel = includeLabel;
- _parameterPolicyFactory = parameterPolicyFactory;
- }
+ // See comments on precedenceDigitComparer
+ _work.Sort(0, _workCount, _precedenceDigitComparer);
- // Each time we process a level of the DFA we keep a list of work items consisting on the nodes we need to evaluate
- // their precendence and their parent nodes. We sort nodes by precedence on each level, which means that nodes are
- // evaluated in the following order: (literals, constrained parameters/complex segments, parameters, constrainted catch-alls and catch-alls)
- // When we process a stage we build a list of the next set of workitems we need to evaluate. We also keep around the
- // list of workitems from the previous level so that we can reuse all the nested lists while we are evaluating the current level.
- internal void ProcessLevel(int depth)
+ for (var i = 0; i < _workCount; i++)
{
- // As we process items, collect the next set of items.
- var nextWork = _previousWork;
- var nextWorkCount = 0;
+ var (endpoint, _, parents) = _work[i];
- // See comments on precedenceDigitComparer
- _work.Sort(0, _workCount, _precedenceDigitComparer);
-
- for (var i = 0; i < _workCount; i++)
+ if (!HasAdditionalRequiredSegments(endpoint, depth))
{
- var (endpoint, _, parents) = _work[i];
-
- if (!HasAdditionalRequiredSegments(endpoint, depth))
+ for (var j = 0; j < parents.Count; j++)
{
- for (var j = 0; j < parents.Count; j++)
- {
- var parent = parents[j];
- parent.AddMatch(endpoint);
- }
+ var parent = parents[j];
+ parent.AddMatch(endpoint);
}
+ }
- // Find the parents of this edge at the current depth
- List<DfaNode> nextParents;
- if (nextWorkCount < nextWork.Count)
- {
- nextParents = nextWork[nextWorkCount].Parents;
- nextParents.Clear();
+ // Find the parents of this edge at the current depth
+ List<DfaNode> nextParents;
+ if (nextWorkCount < nextWork.Count)
+ {
+ nextParents = nextWork[nextWorkCount].Parents;
+ nextParents.Clear();
- var nextPrecedenceDigit = GetPrecedenceDigitAtDepth(endpoint, depth + 1);
- nextWork[nextWorkCount] = new DfaBuilderWorkerWorkItem(endpoint, nextPrecedenceDigit, nextParents);
- }
- else
- {
- nextParents = new List<DfaNode>();
+ var nextPrecedenceDigit = GetPrecedenceDigitAtDepth(endpoint, depth + 1);
+ nextWork[nextWorkCount] = new DfaBuilderWorkerWorkItem(endpoint, nextPrecedenceDigit, nextParents);
+ }
+ else
+ {
+ nextParents = new List<DfaNode>();
- // Add to the next set of work now so the list will be reused
- // even if there are no parents
- var nextPrecedenceDigit = GetPrecedenceDigitAtDepth(endpoint, depth + 1);
- nextWork.Add(new DfaBuilderWorkerWorkItem(endpoint, nextPrecedenceDigit, nextParents));
- }
+ // Add to the next set of work now so the list will be reused
+ // even if there are no parents
+ var nextPrecedenceDigit = GetPrecedenceDigitAtDepth(endpoint, depth + 1);
+ nextWork.Add(new DfaBuilderWorkerWorkItem(endpoint, nextPrecedenceDigit, nextParents));
+ }
- var segment = GetCurrentSegment(endpoint, depth);
- if (segment == null)
- {
- continue;
- }
+ var segment = GetCurrentSegment(endpoint, depth);
+ if (segment == null)
+ {
+ continue;
+ }
- ProcessSegment(endpoint, parents, nextParents, segment);
+ ProcessSegment(endpoint, parents, nextParents, segment);
- if (nextParents.Count > 0)
- {
- nextWorkCount++;
- }
+ if (nextParents.Count > 0)
+ {
+ nextWorkCount++;
}
-
- // Prepare to process the next stage.
- _previousWork = _work;
- _work = nextWork;
- _workCount = nextWorkCount;
}
- private void ProcessSegment(
- RouteEndpoint endpoint,
- List<DfaNode> parents,
- List<DfaNode> nextParents,
- RoutePatternPathSegment segment)
+ // Prepare to process the next stage.
+ _previousWork = _work;
+ _work = nextWork;
+ _workCount = nextWorkCount;
+ }
+
+ private void ProcessSegment(
+ RouteEndpoint endpoint,
+ List<DfaNode> parents,
+ List<DfaNode> nextParents,
+ RoutePatternPathSegment segment)
+ {
+ for (var i = 0; i < parents.Count; i++)
{
- for (var i = 0; i < parents.Count; i++)
+ var parent = parents[i];
+ var part = segment.Parts[0];
+ var parameterPart = part as RoutePatternParameterPart;
+ if (segment.IsSimple && part is RoutePatternLiteralPart literalPart)
+ {
+ AddLiteralNode(_includeLabel, nextParents, parent, literalPart.Content);
+ }
+ else if (segment.IsSimple && parameterPart != null && parameterPart.IsCatchAll)
{
- var parent = parents[i];
- var part = segment.Parts[0];
- var parameterPart = part as RoutePatternParameterPart;
- if (segment.IsSimple && part is RoutePatternLiteralPart literalPart)
+ // A catch all should traverse all literal nodes as well as parameter nodes
+ // we don't need to create the parameter node here because of ordering
+ // all catchalls will be processed after all parameters.
+ if (parent.Literals != null)
{
- AddLiteralNode(_includeLabel, nextParents, parent, literalPart.Content);
+ nextParents.AddRange(parent.Literals.Values);
}
- else if (segment.IsSimple && parameterPart != null && parameterPart.IsCatchAll)
+ if (parent.Parameters != null)
{
- // A catch all should traverse all literal nodes as well as parameter nodes
- // we don't need to create the parameter node here because of ordering
- // all catchalls will be processed after all parameters.
- if (parent.Literals != null)
- {
- nextParents.AddRange(parent.Literals.Values);
- }
- if (parent.Parameters != null)
- {
- nextParents.Add(parent.Parameters);
- }
+ nextParents.Add(parent.Parameters);
+ }
- // We also create a 'catchall' here. We don't do further traversals
- // on the catchall node because only catchalls can end up here. The
- // catchall node allows us to capture an unlimited amount of segments
- // and also to match a zero-length segment, which a parameter node
- // doesn't allow.
- if (parent.CatchAll == null)
+ // We also create a 'catchall' here. We don't do further traversals
+ // on the catchall node because only catchalls can end up here. The
+ // catchall node allows us to capture an unlimited amount of segments
+ // and also to match a zero-length segment, which a parameter node
+ // doesn't allow.
+ if (parent.CatchAll == null)
+ {
+ parent.CatchAll = new DfaNode()
{
- parent.CatchAll = new DfaNode()
- {
- PathDepth = parent.PathDepth + 1,
- Label = _includeLabel ? parent.Label + "{*...}/" : null,
- };
-
- // The catchall node just loops.
- parent.CatchAll.Parameters = parent.CatchAll;
- parent.CatchAll.CatchAll = parent.CatchAll;
- }
+ PathDepth = parent.PathDepth + 1,
+ Label = _includeLabel ? parent.Label + "{*...}/" : null,
+ };
- parent.CatchAll.AddMatch(endpoint);
+ // The catchall node just loops.
+ parent.CatchAll.Parameters = parent.CatchAll;
+ parent.CatchAll.CatchAll = parent.CatchAll;
}
- else if (segment.IsSimple && parameterPart != null && TryGetRequiredValue(endpoint.RoutePattern, parameterPart, out var requiredValue))
- {
- // If the parameter has a matching required value, replace the parameter with the required value
- // as a literal. This should use the parameter's transformer (if present)
- // e.g. Template: Home/{action}, Required values: { action = "Index" }, Result: Home/Index
- AddRequiredLiteralValue(endpoint, nextParents, parent, parameterPart, requiredValue);
- }
- else if (segment.IsSimple && parameterPart != null)
+ parent.CatchAll.AddMatch(endpoint);
+ }
+ else if (segment.IsSimple && parameterPart != null && TryGetRequiredValue(endpoint.RoutePattern, parameterPart, out var requiredValue))
+ {
+ // If the parameter has a matching required value, replace the parameter with the required value
+ // as a literal. This should use the parameter's transformer (if present)
+ // e.g. Template: Home/{action}, Required values: { action = "Index" }, Result: Home/Index
+
+ AddRequiredLiteralValue(endpoint, nextParents, parent, parameterPart, requiredValue);
+ }
+ else if (segment.IsSimple && parameterPart != null)
+ {
+ if (parent.Parameters == null)
{
- if (parent.Parameters == null)
+ parent.Parameters = new DfaNode()
{
- parent.Parameters = new DfaNode()
- {
- PathDepth = parent.PathDepth + 1,
- Label = _includeLabel ? parent.Label + "{...}/" : null,
- };
- }
+ PathDepth = parent.PathDepth + 1,
+ Label = _includeLabel ? parent.Label + "{...}/" : null,
+ };
+ }
- if (parent.Literals != null)
+ if (parent.Literals != null)
+ {
+ // If the parameter contains constraints, we can be smarter about it and evaluate them while we build the tree.
+ // If the literal doesn't match any of the constraints, we can prune the branch.
+ // For example, for a parameter in a route {lang:length(2)} and a parent literal "ABC", we can check that "ABC"
+ // doesn't meet the parameter constraint (length(2)) when building the tree, and avoid the extra nodes.
+ if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicyReferences))
{
- // If the parameter contains constraints, we can be smarter about it and evaluate them while we build the tree.
- // If the literal doesn't match any of the constraints, we can prune the branch.
- // For example, for a parameter in a route {lang:length(2)} and a parent literal "ABC", we can check that "ABC"
- // doesn't meet the parameter constraint (length(2)) when building the tree, and avoid the extra nodes.
- if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicyReferences))
- {
- // We filter out sibling literals that don't match one of the constraints in the segment to avoid adding nodes to the DFA
- // that will never match a route and which will result in a much higher memory usage.
- AddParentsWithMatchingLiteralConstraints(nextParents, parent, parameterPart, parameterPolicyReferences);
- }
- else
- {
- // This means the current parameter we are evaluating doesn't contain any constraint, so we need to
- // traverse all literal nodes as well as the parameter node.
- nextParents.AddRange(parent.Literals.Values);
- }
+ // We filter out sibling literals that don't match one of the constraints in the segment to avoid adding nodes to the DFA
+ // that will never match a route and which will result in a much higher memory usage.
+ AddParentsWithMatchingLiteralConstraints(nextParents, parent, parameterPart, parameterPolicyReferences);
}
-
- nextParents.Add(parent.Parameters);
- }
- else
- {
- // Complex segment - we treat these are parameters here and do the
- // expensive processing later. We don't want to spend time processing
- // complex segments unless they are the best match, and treating them
- // like parameters in the DFA allows us to do just that.
- if (parent.Parameters == null)
+ else
{
- parent.Parameters = new DfaNode()
- {
- PathDepth = parent.PathDepth + 1,
- Label = _includeLabel ? parent.Label + "{...}/" : null,
- };
+ // This means the current parameter we are evaluating doesn't contain any constraint, so we need to
+ // traverse all literal nodes as well as the parameter node.
+ nextParents.AddRange(parent.Literals.Values);
}
+ }
- if (parent.Literals != null)
+ nextParents.Add(parent.Parameters);
+ }
+ else
+ {
+ // Complex segment - we treat these are parameters here and do the
+ // expensive processing later. We don't want to spend time processing
+ // complex segments unless they are the best match, and treating them
+ // like parameters in the DFA allows us to do just that.
+ if (parent.Parameters == null)
+ {
+ parent.Parameters = new DfaNode()
{
- // For a complex segment like this, we can evaluate the literals and avoid adding extra nodes to
- // the tree on cases where the literal won't ever be able to match the complex parameter.
- // For example, if we have a complex parameter {a}-{b}.{c?} and a literal "Hello" we can guarantee
- // that it will never be a match.
+ PathDepth = parent.PathDepth + 1,
+ Label = _includeLabel ? parent.Label + "{...}/" : null,
+ };
+ }
- // We filter out sibling literals that don't match the complex parameter segment to avoid adding nodes to the DFA
- // that will never match a route and which will result in a much higher memory usage.
- AddParentsMatchingComplexSegment(endpoint, nextParents, segment, parent, parameterPart);
- }
- nextParents.Add(parent.Parameters);
+ if (parent.Literals != null)
+ {
+ // For a complex segment like this, we can evaluate the literals and avoid adding extra nodes to
+ // the tree on cases where the literal won't ever be able to match the complex parameter.
+ // For example, if we have a complex parameter {a}-{b}.{c?} and a literal "Hello" we can guarantee
+ // that it will never be a match.
+
+ // We filter out sibling literals that don't match the complex parameter segment to avoid adding nodes to the DFA
+ // that will never match a route and which will result in a much higher memory usage.
+ AddParentsMatchingComplexSegment(endpoint, nextParents, segment, parent, parameterPart);
}
+ nextParents.Add(parent.Parameters);
}
}
+ }
- private void AddParentsMatchingComplexSegment(RouteEndpoint endpoint, List<DfaNode> nextParents, RoutePatternPathSegment segment, DfaNode parent, RoutePatternParameterPart parameterPart)
+ private void AddParentsMatchingComplexSegment(RouteEndpoint endpoint, List<DfaNode> nextParents, RoutePatternPathSegment segment, DfaNode parent, RoutePatternParameterPart parameterPart)
+ {
+ var routeValues = new RouteValueDictionary();
+ foreach (var literal in parent.Literals.Keys)
{
- var routeValues = new RouteValueDictionary();
- foreach (var literal in parent.Literals.Keys)
+ if (RoutePatternMatcher.MatchComplexSegment(segment, literal, routeValues))
{
- if (RoutePatternMatcher.MatchComplexSegment(segment, literal, routeValues))
+ // If we got here (rare) it means that the literal matches the complex segment (for example the literal is something A-B)
+ // there is another thing we can try here, which is to evaluate the policies for the parts in case they have one (for example {a:length(4)}-{b:regex(\d+)})
+ // so that even if it maps closely to a complex parameter we have a chance to discard it and avoid adding the extra branches.
+ var passedAllPolicies = true;
+ for (var i = 0; i < segment.Parts.Count; i++)
{
- // If we got here (rare) it means that the literal matches the complex segment (for example the literal is something A-B)
- // there is another thing we can try here, which is to evaluate the policies for the parts in case they have one (for example {a:length(4)}-{b:regex(\d+)})
- // so that even if it maps closely to a complex parameter we have a chance to discard it and avoid adding the extra branches.
- var passedAllPolicies = true;
- for (var i = 0; i < segment.Parts.Count; i++)
+ var segmentPart = segment.Parts[i];
+ if (segmentPart is not RoutePatternParameterPart partParameter)
{
- var segmentPart = segment.Parts[i];
- if (segmentPart is not RoutePatternParameterPart partParameter)
- {
- // We skip over the literals and the separator since we already checked against them
- continue;
- }
+ // We skip over the literals and the separator since we already checked against them
+ continue;
+ }
- if (!routeValues.TryGetValue(partParameter.Name, out var parameterValue))
- {
- // We have a pattern like {a}-{b}.{part?} and a literal "a-b". Since we've matched the complex segment it means that the optional
- // parameter was not specified, so we skip it.
- Debug.Assert(i == segment.Parts.Count - 1 && partParameter.IsOptional);
- continue;
- }
+ if (!routeValues.TryGetValue(partParameter.Name, out var parameterValue))
+ {
+ // We have a pattern like {a}-{b}.{part?} and a literal "a-b". Since we've matched the complex segment it means that the optional
+ // parameter was not specified, so we skip it.
+ Debug.Assert(i == segment.Parts.Count - 1 && partParameter.IsOptional);
+ continue;
+ }
- if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(partParameter.Name, out var parameterPolicyReferences))
+ if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(partParameter.Name, out var parameterPolicyReferences))
+ {
+ for (var j = 0; j < parameterPolicyReferences.Count; j++)
{
- for (var j = 0; j < parameterPolicyReferences.Count; j++)
+ var reference = parameterPolicyReferences[j];
+ var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
+ if (parameterPolicy is IParameterLiteralNodeMatchingPolicy constraint && !constraint.MatchesLiteral(partParameter.Name, (string)parameterValue))
{
- var reference = parameterPolicyReferences[j];
- var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
- if (parameterPolicy is IParameterLiteralNodeMatchingPolicy constraint && !constraint.MatchesLiteral(partParameter.Name, (string)parameterValue))
- {
- passedAllPolicies = false;
- break;
- }
+ passedAllPolicies = false;
+ break;
}
}
}
-
- if (passedAllPolicies)
- {
- nextParents.Add(parent.Literals[literal]);
- }
}
- routeValues.Clear();
+ if (passedAllPolicies)
+ {
+ nextParents.Add(parent.Literals[literal]);
+ }
}
+
+ routeValues.Clear();
}
+ }
- private void AddParentsWithMatchingLiteralConstraints(List<DfaNode> nextParents, DfaNode parent, RoutePatternParameterPart parameterPart, IReadOnlyList<RoutePatternParameterPolicyReference> parameterPolicyReferences)
- {
- // The list of parameters that fail to meet at least one IParameterLiteralNodeMatchingPolicy.
- var hasFailingPolicy = parent.Literals.Keys.Count < 32 ?
- (stackalloc bool[32]).Slice(0, parent.Literals.Keys.Count) :
- new bool[parent.Literals.Keys.Count];
+ private void AddParentsWithMatchingLiteralConstraints(List<DfaNode> nextParents, DfaNode parent, RoutePatternParameterPart parameterPart, IReadOnlyList<RoutePatternParameterPolicyReference> parameterPolicyReferences)
+ {
+ // The list of parameters that fail to meet at least one IParameterLiteralNodeMatchingPolicy.
+ var hasFailingPolicy = parent.Literals.Keys.Count < 32 ?
+ (stackalloc bool[32]).Slice(0, parent.Literals.Keys.Count) :
+ new bool[parent.Literals.Keys.Count];
- // Whether or not all parameters have failed to meet at least one constraint.
- for (var i = 0; i < parameterPolicyReferences.Count; i++)
+ // Whether or not all parameters have failed to meet at least one constraint.
+ for (var i = 0; i < parameterPolicyReferences.Count; i++)
+ {
+ var reference = parameterPolicyReferences[i];
+ var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
+ if (parameterPolicy is IParameterLiteralNodeMatchingPolicy constraint)
{
- var reference = parameterPolicyReferences[i];
- var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
- if (parameterPolicy is IParameterLiteralNodeMatchingPolicy constraint)
+ var literalIndex = 0;
+ var allFailed = true;
+ foreach (var literal in parent.Literals.Keys)
{
- var literalIndex = 0;
- var allFailed = true;
- foreach (var literal in parent.Literals.Keys)
+ if (!hasFailingPolicy[literalIndex] && !constraint.MatchesLiteral(parameterPart.Name, literal))
{
- if (!hasFailingPolicy[literalIndex] && !constraint.MatchesLiteral(parameterPart.Name, literal))
- {
- hasFailingPolicy[literalIndex] = true;
- }
-
- allFailed &= hasFailingPolicy[literalIndex];
-
- literalIndex++;
+ hasFailingPolicy[literalIndex] = true;
}
- if (allFailed)
- {
- // If we get here it means that all literals have failed at least one policy, which means we can skip checking policies
- // and return early. This will be a very common case when your constraints are things like "int,length or a regex".
- return;
- }
+ allFailed &= hasFailingPolicy[literalIndex];
+
+ literalIndex++;
}
- }
- var k = 0;
- foreach (var literal in parent.Literals.Values)
- {
- if (!hasFailingPolicy[k])
+ if (allFailed)
{
- nextParents.Add(literal);
+ // If we get here it means that all literals have failed at least one policy, which means we can skip checking policies
+ // and return early. This will be a very common case when your constraints are things like "int,length or a regex".
+ return;
}
- k++;
}
}
- private void AddRequiredLiteralValue(RouteEndpoint endpoint, List<DfaNode> nextParents, DfaNode parent, RoutePatternParameterPart parameterPart, object requiredValue)
+ var k = 0;
+ foreach (var literal in parent.Literals.Values)
{
- if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicyReferences))
+ if (!hasFailingPolicy[k])
{
- for (var k = 0; k < parameterPolicyReferences.Count; k++)
- {
- var reference = parameterPolicyReferences[k];
- var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
- if (parameterPolicy is IOutboundParameterTransformer parameterTransformer)
- {
- requiredValue = parameterTransformer.TransformOutbound(requiredValue);
- break;
- }
- }
+ nextParents.Add(literal);
}
-
- var literalValue = requiredValue?.ToString() ?? throw new InvalidOperationException($"Required value for literal '{parameterPart.Name}' must evaluate to a non-null string.");
-
- AddLiteralNode(_includeLabel, nextParents, parent, literalValue);
+ k++;
}
}
- private static void AddLiteralNode(bool includeLabel, List<DfaNode> nextParents, DfaNode parent, string literal)
+ private void AddRequiredLiteralValue(RouteEndpoint endpoint, List<DfaNode> nextParents, DfaNode parent, RoutePatternParameterPart parameterPart, object requiredValue)
{
- DfaNode next = null;
- if (parent.Literals == null ||
- !parent.Literals.TryGetValue(literal, out next))
+ if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicyReferences))
{
- next = new DfaNode()
+ for (var k = 0; k < parameterPolicyReferences.Count; k++)
{
- PathDepth = parent.PathDepth + 1,
- Label = includeLabel ? parent.Label + literal + "/" : null,
- };
- parent.AddLiteral(literal, next);
+ var reference = parameterPolicyReferences[k];
+ var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
+ if (parameterPolicy is IOutboundParameterTransformer parameterTransformer)
+ {
+ requiredValue = parameterTransformer.TransformOutbound(requiredValue);
+ break;
+ }
+ }
}
- nextParents.Add(next);
+ var literalValue = requiredValue?.ToString() ?? throw new InvalidOperationException($"Required value for literal '{parameterPart.Name}' must evaluate to a non-null string.");
+
+ AddLiteralNode(_includeLabel, nextParents, parent, literalValue);
}
+ }
- private static RoutePatternPathSegment GetCurrentSegment(RouteEndpoint endpoint, int depth)
+ private static void AddLiteralNode(bool includeLabel, List<DfaNode> nextParents, DfaNode parent, string literal)
+ {
+ DfaNode next = null;
+ if (parent.Literals == null ||
+ !parent.Literals.TryGetValue(literal, out next))
{
- if (depth < endpoint.RoutePattern.PathSegments.Count)
+ next = new DfaNode()
{
- return endpoint.RoutePattern.PathSegments[depth];
- }
+ PathDepth = parent.PathDepth + 1,
+ Label = includeLabel ? parent.Label + literal + "/" : null,
+ };
+ parent.AddLiteral(literal, next);
+ }
- if (endpoint.RoutePattern.PathSegments.Count == 0)
- {
- return null;
- }
+ nextParents.Add(next);
+ }
- var lastSegment = endpoint.RoutePattern.PathSegments[endpoint.RoutePattern.PathSegments.Count - 1];
- if (lastSegment.IsSimple && lastSegment.Parts[0] is RoutePatternParameterPart parameterPart && parameterPart.IsCatchAll)
- {
- return lastSegment;
- }
+ private static RoutePatternPathSegment GetCurrentSegment(RouteEndpoint endpoint, int depth)
+ {
+ if (depth < endpoint.RoutePattern.PathSegments.Count)
+ {
+ return endpoint.RoutePattern.PathSegments[depth];
+ }
+ if (endpoint.RoutePattern.PathSegments.Count == 0)
+ {
return null;
}
- private static int GetPrecedenceDigitAtDepth(RouteEndpoint endpoint, int depth)
+ var lastSegment = endpoint.RoutePattern.PathSegments[endpoint.RoutePattern.PathSegments.Count - 1];
+ if (lastSegment.IsSimple && lastSegment.Parts[0] is RoutePatternParameterPart parameterPart && parameterPart.IsCatchAll)
{
- var segment = GetCurrentSegment(endpoint, depth);
- if (segment is null)
- {
- // Treat "no segment" as high priority. it won't effect the algorithm, but we need to define a sort-order.
- return 0;
- }
-
- return RoutePrecedence.ComputeInboundPrecedenceDigit(endpoint.RoutePattern, segment);
+ return lastSegment;
}
- public override Matcher Build()
+ return null;
+ }
+
+ private static int GetPrecedenceDigitAtDepth(RouteEndpoint endpoint, int depth)
+ {
+ var segment = GetCurrentSegment(endpoint, depth);
+ if (segment is null)
{
+ // Treat "no segment" as high priority. it won't effect the algorithm, but we need to define a sort-order.
+ return 0;
+ }
+
+ return RoutePrecedence.ComputeInboundPrecedenceDigit(endpoint.RoutePattern, segment);
+ }
+
+ public override Matcher Build()
+ {
#if DEBUG
- var includeLabel = true;
+ var includeLabel = true;
#else
var includeLabel = false;
#endif
- var root = BuildDfaTree(includeLabel);
-
- // State count is the number of nodes plus an exit state
- var stateCount = 1;
- var maxSegmentCount = 0;
- root.Visit((node) =>
- {
- stateCount++;
- maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth);
- });
- _stateIndex = 0;
-
- // The max segment count is the maximum path-node-depth +1. We need
- // the +1 to capture any additional content after the 'last' segment.
- maxSegmentCount++;
-
- var states = new DfaState[stateCount];
- var exitDestination = stateCount - 1;
- AddNode(root, states, exitDestination);
-
- // The root state only has a jump table.
- states[exitDestination] = new DfaState(
- Array.Empty<Candidate>(),
- Array.Empty<IEndpointSelectorPolicy>(),
- JumpTableBuilder.Build(exitDestination, exitDestination, null),
- null);
-
- return new DfaMatcher(_loggerFactory.CreateLogger<DfaMatcher>(), _selector, states, maxSegmentCount);
- }
+ var root = BuildDfaTree(includeLabel);
- private int AddNode(
- DfaNode node,
- DfaState[] states,
- int exitDestination)
+ // State count is the number of nodes plus an exit state
+ var stateCount = 1;
+ var maxSegmentCount = 0;
+ root.Visit((node) =>
{
- node.Matches?.Sort(_comparer);
+ stateCount++;
+ maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth);
+ });
+ _stateIndex = 0;
+
+ // The max segment count is the maximum path-node-depth +1. We need
+ // the +1 to capture any additional content after the 'last' segment.
+ maxSegmentCount++;
+
+ var states = new DfaState[stateCount];
+ var exitDestination = stateCount - 1;
+ AddNode(root, states, exitDestination);
+
+ // The root state only has a jump table.
+ states[exitDestination] = new DfaState(
+ Array.Empty<Candidate>(),
+ Array.Empty<IEndpointSelectorPolicy>(),
+ JumpTableBuilder.Build(exitDestination, exitDestination, null),
+ null);
+
+ return new DfaMatcher(_loggerFactory.CreateLogger<DfaMatcher>(), _selector, states, maxSegmentCount);
+ }
- var currentStateIndex = _stateIndex;
+ private int AddNode(
+ DfaNode node,
+ DfaState[] states,
+ int exitDestination)
+ {
+ node.Matches?.Sort(_comparer);
- var currentDefaultDestination = exitDestination;
- var currentExitDestination = exitDestination;
- (string text, int destination)[] pathEntries = null;
- PolicyJumpTableEdge[] policyEntries = null;
+ var currentStateIndex = _stateIndex;
- if (node.Literals != null)
- {
- pathEntries = new (string text, int destination)[node.Literals.Count];
+ var currentDefaultDestination = exitDestination;
+ var currentExitDestination = exitDestination;
+ (string text, int destination)[] pathEntries = null;
+ PolicyJumpTableEdge[] policyEntries = null;
- var index = 0;
- foreach (var kvp in node.Literals)
- {
- var transition = Transition(kvp.Value);
- pathEntries[index++] = (kvp.Key, transition);
- }
- }
+ if (node.Literals != null)
+ {
+ pathEntries = new (string text, int destination)[node.Literals.Count];
- if (node.Parameters != null &&
- node.CatchAll != null &&
- ReferenceEquals(node.Parameters, node.CatchAll))
+ var index = 0;
+ foreach (var kvp in node.Literals)
{
- // This node has a single transition to but it should accept zero-width segments
- // this can happen when a node only has catchall parameters.
- currentExitDestination = currentDefaultDestination = Transition(node.Parameters);
- }
- else if (node.Parameters != null && node.CatchAll != null)
- {
- // This node has a separate transition for zero-width segments
- // this can happen when a node has both parameters and catchall parameters.
- currentDefaultDestination = Transition(node.Parameters);
- currentExitDestination = Transition(node.CatchAll);
- }
- else if (node.Parameters != null)
- {
- // This node has parameters but no catchall.
- currentDefaultDestination = Transition(node.Parameters);
- }
- else if (node.CatchAll != null)
- {
- // This node has a catchall but no parameters
- currentExitDestination = currentDefaultDestination = Transition(node.CatchAll);
+ var transition = Transition(kvp.Value);
+ pathEntries[index++] = (kvp.Key, transition);
}
+ }
- if (node.PolicyEdges != null && node.PolicyEdges.Count > 0)
- {
- policyEntries = new PolicyJumpTableEdge[node.PolicyEdges.Count];
+ if (node.Parameters != null &&
+ node.CatchAll != null &&
+ ReferenceEquals(node.Parameters, node.CatchAll))
+ {
+ // This node has a single transition to but it should accept zero-width segments
+ // this can happen when a node only has catchall parameters.
+ currentExitDestination = currentDefaultDestination = Transition(node.Parameters);
+ }
+ else if (node.Parameters != null && node.CatchAll != null)
+ {
+ // This node has a separate transition for zero-width segments
+ // this can happen when a node has both parameters and catchall parameters.
+ currentDefaultDestination = Transition(node.Parameters);
+ currentExitDestination = Transition(node.CatchAll);
+ }
+ else if (node.Parameters != null)
+ {
+ // This node has parameters but no catchall.
+ currentDefaultDestination = Transition(node.Parameters);
+ }
+ else if (node.CatchAll != null)
+ {
+ // This node has a catchall but no parameters
+ currentExitDestination = currentDefaultDestination = Transition(node.CatchAll);
+ }
- var index = 0;
- foreach (var kvp in node.PolicyEdges)
- {
- policyEntries[index++] = new PolicyJumpTableEdge(kvp.Key, Transition(kvp.Value));
- }
+ if (node.PolicyEdges != null && node.PolicyEdges.Count > 0)
+ {
+ policyEntries = new PolicyJumpTableEdge[node.PolicyEdges.Count];
+
+ var index = 0;
+ foreach (var kvp in node.PolicyEdges)
+ {
+ policyEntries[index++] = new PolicyJumpTableEdge(kvp.Key, Transition(kvp.Value));
}
+ }
- var candidates = CreateCandidates(node.Matches);
+ var candidates = CreateCandidates(node.Matches);
- // Perf: most of the time there aren't any endpoint selector policies, create
- // this lazily.
- List<IEndpointSelectorPolicy> endpointSelectorPolicies = null;
- if (node.Matches?.Count > 0)
+ // Perf: most of the time there aren't any endpoint selector policies, create
+ // this lazily.
+ List<IEndpointSelectorPolicy> endpointSelectorPolicies = null;
+ if (node.Matches?.Count > 0)
+ {
+ for (var i = 0; i < _endpointSelectorPolicies.Length; i++)
{
- for (var i = 0; i < _endpointSelectorPolicies.Length; i++)
+ var endpointSelectorPolicy = _endpointSelectorPolicies[i];
+ if (endpointSelectorPolicy.AppliesToEndpoints(node.Matches))
{
- var endpointSelectorPolicy = _endpointSelectorPolicies[i];
- if (endpointSelectorPolicy.AppliesToEndpoints(node.Matches))
+ if (endpointSelectorPolicies == null)
{
- if (endpointSelectorPolicies == null)
- {
- endpointSelectorPolicies = new List<IEndpointSelectorPolicy>();
- }
-
- endpointSelectorPolicies.Add(endpointSelectorPolicy);
+ endpointSelectorPolicies = new List<IEndpointSelectorPolicy>();
}
+
+ endpointSelectorPolicies.Add(endpointSelectorPolicy);
}
}
+ }
- states[currentStateIndex] = new DfaState(
- candidates,
- endpointSelectorPolicies?.ToArray() ?? Array.Empty<IEndpointSelectorPolicy>(),
- JumpTableBuilder.Build(currentDefaultDestination, currentExitDestination, pathEntries),
- // Use the final exit destination when building the policy state.
- // We don't want to use either of the current destinations because they refer routing states,
- // and a policy state should never transition back to a routing state.
- BuildPolicy(exitDestination, node.NodeBuilder, policyEntries));
+ states[currentStateIndex] = new DfaState(
+ candidates,
+ endpointSelectorPolicies?.ToArray() ?? Array.Empty<IEndpointSelectorPolicy>(),
+ JumpTableBuilder.Build(currentDefaultDestination, currentExitDestination, pathEntries),
+ // Use the final exit destination when building the policy state.
+ // We don't want to use either of the current destinations because they refer routing states,
+ // and a policy state should never transition back to a routing state.
+ BuildPolicy(exitDestination, node.NodeBuilder, policyEntries));
- return currentStateIndex;
+ return currentStateIndex;
- int Transition(DfaNode next)
+ int Transition(DfaNode next)
+ {
+ // Break cycles
+ if (ReferenceEquals(node, next))
{
- // Break cycles
- if (ReferenceEquals(node, next))
- {
- return _stateIndex;
- }
- else
- {
- _stateIndex++;
- return AddNode(next, states, exitDestination);
- }
+ return _stateIndex;
}
- }
-
- private static PolicyJumpTable BuildPolicy(int exitDestination, INodeBuilderPolicy nodeBuilder, PolicyJumpTableEdge[] policyEntries)
- {
- if (policyEntries == null)
+ else
{
- return null;
+ _stateIndex++;
+ return AddNode(next, states, exitDestination);
}
+ }
+ }
- return nodeBuilder.BuildJumpTable(exitDestination, policyEntries);
+ private static PolicyJumpTable BuildPolicy(int exitDestination, INodeBuilderPolicy nodeBuilder, PolicyJumpTableEdge[] policyEntries)
+ {
+ if (policyEntries == null)
+ {
+ return null;
}
- // Builds an array of candidates for a node, assigns a 'score' for each
- // endpoint.
- internal Candidate[] CreateCandidates(IReadOnlyList<Endpoint> endpoints)
+ return nodeBuilder.BuildJumpTable(exitDestination, policyEntries);
+ }
+
+ // Builds an array of candidates for a node, assigns a 'score' for each
+ // endpoint.
+ internal Candidate[] CreateCandidates(IReadOnlyList<Endpoint> endpoints)
+ {
+ if (endpoints == null || endpoints.Count == 0)
{
- if (endpoints == null || endpoints.Count == 0)
- {
- return Array.Empty<Candidate>();
- }
+ return Array.Empty<Candidate>();
+ }
- var candiates = new Candidate[endpoints.Count];
+ var candiates = new Candidate[endpoints.Count];
- var score = 0;
- var examplar = endpoints[0];
- candiates[0] = CreateCandidate(examplar, score);
+ var score = 0;
+ var examplar = endpoints[0];
+ candiates[0] = CreateCandidate(examplar, score);
- for (var i = 1; i < endpoints.Count; i++)
+ for (var i = 1; i < endpoints.Count; i++)
+ {
+ var endpoint = endpoints[i];
+ if (!_comparer.Equals(examplar, endpoint))
{
- var endpoint = endpoints[i];
- if (!_comparer.Equals(examplar, endpoint))
- {
- // This endpoint doesn't have the same priority.
- examplar = endpoint;
- score++;
- }
-
- candiates[i] = CreateCandidate(endpoint, score);
+ // This endpoint doesn't have the same priority.
+ examplar = endpoint;
+ score++;
}
- return candiates;
+ candiates[i] = CreateCandidate(endpoint, score);
}
- // internal for tests
- internal Candidate CreateCandidate(Endpoint endpoint, int score)
+ return candiates;
+ }
+
+ // internal for tests
+ internal Candidate CreateCandidate(Endpoint endpoint, int score)
+ {
+ (string parameterName, int segmentIndex, int slotIndex) catchAll = default;
+
+ if (endpoint is RouteEndpoint routeEndpoint)
{
- (string parameterName, int segmentIndex, int slotIndex) catchAll = default;
+ _assignments.Clear();
+ _slots.Clear();
+ _captures.Clear();
+ _complexSegments.Clear();
+ _constraints.Clear();
- if (endpoint is RouteEndpoint routeEndpoint)
+ foreach (var kvp in routeEndpoint.RoutePattern.Defaults)
{
- _assignments.Clear();
- _slots.Clear();
- _captures.Clear();
- _complexSegments.Clear();
- _constraints.Clear();
+ _assignments.Add(kvp.Key, _assignments.Count);
+ _slots.Add(kvp);
+ }
- foreach (var kvp in routeEndpoint.RoutePattern.Defaults)
+ for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
+ {
+ var segment = routeEndpoint.RoutePattern.PathSegments[i];
+ if (!segment.IsSimple)
{
- _assignments.Add(kvp.Key, _assignments.Count);
- _slots.Add(kvp);
+ continue;
}
- for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
+ var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
+ if (parameterPart == null)
{
- var segment = routeEndpoint.RoutePattern.PathSegments[i];
- if (!segment.IsSimple)
- {
- continue;
- }
-
- var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
- if (parameterPart == null)
- {
- continue;
- }
-
- if (!_assignments.TryGetValue(parameterPart.Name, out var slotIndex))
- {
- slotIndex = _assignments.Count;
- _assignments.Add(parameterPart.Name, slotIndex);
+ continue;
+ }
- // A parameter can have a required value, default value/catch all, or be a normal parameter
- // Add the required value or default value as the slot's initial value
- if (TryGetRequiredValue(routeEndpoint.RoutePattern, parameterPart, out var requiredValue))
- {
- _slots.Add(new KeyValuePair<string, object>(parameterPart.Name, requiredValue));
- }
- else
- {
- var hasDefaultValue = parameterPart.Default != null || parameterPart.IsCatchAll;
- _slots.Add(hasDefaultValue ? new KeyValuePair<string, object>(parameterPart.Name, parameterPart.Default) : default);
- }
- }
+ if (!_assignments.TryGetValue(parameterPart.Name, out var slotIndex))
+ {
+ slotIndex = _assignments.Count;
+ _assignments.Add(parameterPart.Name, slotIndex);
- if (TryGetRequiredValue(routeEndpoint.RoutePattern, parameterPart, out _))
- {
- // Don't capture a parameter if it has a required value
- // There is no need because a parameter with a required value is matched as a literal
- }
- else if (parameterPart.IsCatchAll)
+ // A parameter can have a required value, default value/catch all, or be a normal parameter
+ // Add the required value or default value as the slot's initial value
+ if (TryGetRequiredValue(routeEndpoint.RoutePattern, parameterPart, out var requiredValue))
{
- catchAll = (parameterPart.Name, i, slotIndex);
+ _slots.Add(new KeyValuePair<string, object>(parameterPart.Name, requiredValue));
}
else
{
- _captures.Add((parameterPart.Name, i, slotIndex));
+ var hasDefaultValue = parameterPart.Default != null || parameterPart.IsCatchAll;
+ _slots.Add(hasDefaultValue ? new KeyValuePair<string, object>(parameterPart.Name, parameterPart.Default) : default);
}
}
- for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
+ if (TryGetRequiredValue(routeEndpoint.RoutePattern, parameterPart, out _))
{
- var segment = routeEndpoint.RoutePattern.PathSegments[i];
- if (segment.IsSimple)
- {
- continue;
- }
-
- _complexSegments.Add((segment, i));
+ // Don't capture a parameter if it has a required value
+ // There is no need because a parameter with a required value is matched as a literal
}
-
- foreach (var kvp in routeEndpoint.RoutePattern.ParameterPolicies)
+ else if (parameterPart.IsCatchAll)
{
- var parameter = routeEndpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
- var parameterPolicyReferences = kvp.Value;
- for (var i = 0; i < parameterPolicyReferences.Count; i++)
- {
- var reference = parameterPolicyReferences[i];
- var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
- if (parameterPolicy is IRouteConstraint routeConstraint)
- {
- _constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
- }
- }
+ catchAll = (parameterPart.Name, i, slotIndex);
+ }
+ else
+ {
+ _captures.Add((parameterPart.Name, i, slotIndex));
}
-
- return new Candidate(
- endpoint,
- score,
- _slots.ToArray(),
- _captures.ToArray(),
- catchAll,
- _complexSegments.ToArray(),
- _constraints.ToArray());
- }
- else
- {
- return new Candidate(
- endpoint,
- score,
- Array.Empty<KeyValuePair<string, object>>(),
- Array.Empty<(string parameterName, int segmentIndex, int slotIndex)>(),
- catchAll,
- Array.Empty<(RoutePatternPathSegment pathSegment, int segmentIndex)>(),
- Array.Empty<KeyValuePair<string, IRouteConstraint>>());
}
- }
- private static bool HasAdditionalRequiredSegments(RouteEndpoint endpoint, int depth)
- {
- for (var i = depth; i < endpoint.RoutePattern.PathSegments.Count; i++)
+ for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
{
- var segment = endpoint.RoutePattern.PathSegments[i];
- if (!segment.IsSimple)
+ var segment = routeEndpoint.RoutePattern.PathSegments[i];
+ if (segment.IsSimple)
{
- // Complex segments always require more processing
- return true;
+ continue;
}
- var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
- if (parameterPart == null)
- {
- // It's a literal
- return true;
- }
+ _complexSegments.Add((segment, i));
+ }
- if (!parameterPart.IsOptional &&
- !parameterPart.IsCatchAll &&
- parameterPart.Default == null)
+ foreach (var kvp in routeEndpoint.RoutePattern.ParameterPolicies)
+ {
+ var parameter = routeEndpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
+ var parameterPolicyReferences = kvp.Value;
+ for (var i = 0; i < parameterPolicyReferences.Count; i++)
{
- return true;
+ var reference = parameterPolicyReferences[i];
+ var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
+ if (parameterPolicy is IRouteConstraint routeConstraint)
+ {
+ _constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
+ }
}
}
- return false;
+ return new Candidate(
+ endpoint,
+ score,
+ _slots.ToArray(),
+ _captures.ToArray(),
+ catchAll,
+ _complexSegments.ToArray(),
+ _constraints.ToArray());
+ }
+ else
+ {
+ return new Candidate(
+ endpoint,
+ score,
+ Array.Empty<KeyValuePair<string, object>>(),
+ Array.Empty<(string parameterName, int segmentIndex, int slotIndex)>(),
+ catchAll,
+ Array.Empty<(RoutePatternPathSegment pathSegment, int segmentIndex)>(),
+ Array.Empty<KeyValuePair<string, IRouteConstraint>>());
}
+ }
- private void ApplyPolicies(DfaNode node)
+ private static bool HasAdditionalRequiredSegments(RouteEndpoint endpoint, int depth)
+ {
+ for (var i = depth; i < endpoint.RoutePattern.PathSegments.Count; i++)
{
- if (node.Matches == null || node.Matches.Count == 0)
+ var segment = endpoint.RoutePattern.PathSegments[i];
+ if (!segment.IsSimple)
{
- return;
+ // Complex segments always require more processing
+ return true;
}
- // We're done with the precedence based work. Sort the endpoints
- // before applying policies for simplicity in policy-related code.
- node.Matches.Sort(_comparer);
+ var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
+ if (parameterPart == null)
+ {
+ // It's a literal
+ return true;
+ }
- // Start with the current node as the root.
- var work = new List<DfaNode>() { node, };
- List<DfaNode> previousWork = null;
- for (var i = 0; i < _nodeBuilders.Length; i++)
+ if (!parameterPart.IsOptional &&
+ !parameterPart.IsCatchAll &&
+ parameterPart.Default == null)
{
- var nodeBuilder = _nodeBuilders[i];
+ return true;
+ }
+ }
- // Build a list of each
- List<DfaNode> nextWork;
- if (previousWork == null)
- {
- nextWork = new List<DfaNode>();
- }
- else
+ return false;
+ }
+
+ private void ApplyPolicies(DfaNode node)
+ {
+ if (node.Matches == null || node.Matches.Count == 0)
+ {
+ return;
+ }
+
+ // We're done with the precedence based work. Sort the endpoints
+ // before applying policies for simplicity in policy-related code.
+ node.Matches.Sort(_comparer);
+
+ // Start with the current node as the root.
+ var work = new List<DfaNode>() { node, };
+ List<DfaNode> previousWork = null;
+ for (var i = 0; i < _nodeBuilders.Length; i++)
+ {
+ var nodeBuilder = _nodeBuilders[i];
+
+ // Build a list of each
+ List<DfaNode> nextWork;
+ if (previousWork == null)
+ {
+ nextWork = new List<DfaNode>();
+ }
+ else
+ {
+ // Reuse previous collection for the next collection
+ previousWork.Clear();
+ nextWork = previousWork;
+ }
+
+ for (var j = 0; j < work.Count; j++)
+ {
+ var parent = work[j];
+ if (!nodeBuilder.AppliesToEndpoints(parent.Matches ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>()))
{
- // Reuse previous collection for the next collection
- previousWork.Clear();
- nextWork = previousWork;
+ // This node-builder doesn't care about this node, so add it to the list
+ // to be processed by the next node-builder.
+ nextWork.Add(parent);
+ continue;
}
- for (var j = 0; j < work.Count; j++)
+ // This node-builder does apply to this node, so we need to create new nodes for each edge,
+ // and then attach them to the parent.
+ var edges = nodeBuilder.GetEdges(parent.Matches ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>());
+ for (var k = 0; k < edges.Count; k++)
{
- var parent = work[j];
- if (!nodeBuilder.AppliesToEndpoints(parent.Matches ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>()))
- {
- // This node-builder doesn't care about this node, so add it to the list
- // to be processed by the next node-builder.
- nextWork.Add(parent);
- continue;
- }
+ var edge = edges[k];
- // This node-builder does apply to this node, so we need to create new nodes for each edge,
- // and then attach them to the parent.
- var edges = nodeBuilder.GetEdges(parent.Matches ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>());
- for (var k = 0; k < edges.Count; k++)
+ var next = new DfaNode()
{
- var edge = edges[k];
-
- var next = new DfaNode()
- {
- // If parent label is null then labels are not being included
- Label = (parent.Label != null) ? parent.Label + " " + edge.State.ToString() : null,
- };
-
- if (edge.Endpoints.Count > 0)
- {
- next.AddMatches(edge.Endpoints);
- }
- nextWork.Add(next);
+ // If parent label is null then labels are not being included
+ Label = (parent.Label != null) ? parent.Label + " " + edge.State.ToString() : null,
+ };
- parent.AddPolicyEdge(edge.State, next);
+ if (edge.Endpoints.Count > 0)
+ {
+ next.AddMatches(edge.Endpoints);
}
+ nextWork.Add(next);
- // Associate the node-builder so we can build a jump table later.
- parent.NodeBuilder = nodeBuilder;
-
- // The parent no longer has matches, it's not considered a terminal node.
- parent.Matches?.Clear();
+ parent.AddPolicyEdge(edge.State, next);
}
- previousWork = work;
- work = nextWork;
+ // Associate the node-builder so we can build a jump table later.
+ parent.NodeBuilder = nodeBuilder;
+
+ // The parent no longer has matches, it's not considered a terminal node.
+ parent.Matches?.Clear();
}
+
+ previousWork = work;
+ work = nextWork;
}
+ }
- private static (INodeBuilderPolicy[] nodeBuilderPolicies, IEndpointComparerPolicy[] endpointComparerPolicies, IEndpointSelectorPolicy[] endpointSelectorPolicies) ExtractPolicies(IEnumerable<MatcherPolicy> policies)
- {
- var nodeBuilderPolicies = new List<INodeBuilderPolicy>();
- var endpointComparerPolicies = new List<IEndpointComparerPolicy>();
- var endpointSelectorPolicies = new List<IEndpointSelectorPolicy>();
+ private static (INodeBuilderPolicy[] nodeBuilderPolicies, IEndpointComparerPolicy[] endpointComparerPolicies, IEndpointSelectorPolicy[] endpointSelectorPolicies) ExtractPolicies(IEnumerable<MatcherPolicy> policies)
+ {
+ var nodeBuilderPolicies = new List<INodeBuilderPolicy>();
+ var endpointComparerPolicies = new List<IEndpointComparerPolicy>();
+ var endpointSelectorPolicies = new List<IEndpointSelectorPolicy>();
- foreach (var policy in policies)
+ foreach (var policy in policies)
+ {
+ if (policy is INodeBuilderPolicy nodeBuilderPolicy)
{
- if (policy is INodeBuilderPolicy nodeBuilderPolicy)
- {
- nodeBuilderPolicies.Add(nodeBuilderPolicy);
- }
-
- if (policy is IEndpointComparerPolicy endpointComparerPolicy)
- {
- endpointComparerPolicies.Add(endpointComparerPolicy);
- }
-
- if (policy is IEndpointSelectorPolicy endpointSelectorPolicy)
- {
- endpointSelectorPolicies.Add(endpointSelectorPolicy);
- }
+ nodeBuilderPolicies.Add(nodeBuilderPolicy);
}
- return (nodeBuilderPolicies.ToArray(), endpointComparerPolicies.ToArray(), endpointSelectorPolicies.ToArray());
- }
+ if (policy is IEndpointComparerPolicy endpointComparerPolicy)
+ {
+ endpointComparerPolicies.Add(endpointComparerPolicy);
+ }
- private static bool TryGetRequiredValue(RoutePattern routePattern, RoutePatternParameterPart parameterPart, out object value)
- {
- if (!routePattern.RequiredValues.TryGetValue(parameterPart.Name, out value))
+ if (policy is IEndpointSelectorPolicy endpointSelectorPolicy)
{
- return false;
+ endpointSelectorPolicies.Add(endpointSelectorPolicy);
}
+ }
+
+ return (nodeBuilderPolicies.ToArray(), endpointComparerPolicies.ToArray(), endpointSelectorPolicies.ToArray());
+ }
- return !RouteValueEqualityComparer.Default.Equals(value, string.Empty);
+ private static bool TryGetRequiredValue(RoutePattern routePattern, RoutePatternParameterPart parameterPart, out object value)
+ {
+ if (!routePattern.RequiredValues.TryGetValue(parameterPart.Name, out value))
+ {
+ return false;
}
- private record struct DfaBuilderWorkerWorkItem(RouteEndpoint Endpoint, int PrecedenceDigit, List<DfaNode> Parents);
+ return !RouteValueEqualityComparer.Default.Equals(value, string.Empty);
}
+
+ private record struct DfaBuilderWorkerWorkItem(RouteEndpoint Endpoint, int PrecedenceDigit, List<DfaNode> Parents);
}
diff --git a/src/Http/Routing/src/Matching/DfaMatcherFactory.cs b/src/Http/Routing/src/Matching/DfaMatcherFactory.cs
index 9f6587b0c0..0e7298e407 100644
--- a/src/Http/Routing/src/Matching/DfaMatcherFactory.cs
+++ b/src/Http/Routing/src/Matching/DfaMatcherFactory.cs
@@ -4,39 +4,38 @@
using System;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class DfaMatcherFactory : MatcherFactory
{
- internal class DfaMatcherFactory : MatcherFactory
- {
- private readonly IServiceProvider _services;
+ private readonly IServiceProvider _services;
- // Using the service provider here so we can avoid coupling to the dependencies
- // of DfaMatcherBuilder.
- public DfaMatcherFactory(IServiceProvider services)
+ // Using the service provider here so we can avoid coupling to the dependencies
+ // of DfaMatcherBuilder.
+ public DfaMatcherFactory(IServiceProvider services)
+ {
+ if (services == null)
{
- if (services == null)
- {
- throw new ArgumentNullException(nameof(services));
- }
-
- _services = services;
+ throw new ArgumentNullException(nameof(services));
}
- public override Matcher CreateMatcher(EndpointDataSource dataSource)
+ _services = services;
+ }
+
+ public override Matcher CreateMatcher(EndpointDataSource dataSource)
+ {
+ if (dataSource == null)
{
- if (dataSource == null)
- {
- throw new ArgumentNullException(nameof(dataSource));
- }
-
- // Creates a tracking entry in DI to stop listening for change events
- // when the services are disposed.
- var lifetime = _services.GetRequiredService<DataSourceDependentMatcher.Lifetime>();
-
- return new DataSourceDependentMatcher(dataSource, lifetime, () =>
- {
- return _services.GetRequiredService<DfaMatcherBuilder>();
- });
+ throw new ArgumentNullException(nameof(dataSource));
}
+
+ // Creates a tracking entry in DI to stop listening for change events
+ // when the services are disposed.
+ var lifetime = _services.GetRequiredService<DataSourceDependentMatcher.Lifetime>();
+
+ return new DataSourceDependentMatcher(dataSource, lifetime, () =>
+ {
+ return _services.GetRequiredService<DfaMatcherBuilder>();
+ });
}
}
diff --git a/src/Http/Routing/src/Matching/DfaNode.cs b/src/Http/Routing/src/Matching/DfaNode.cs
index eec7170625..286a4f3e2c 100644
--- a/src/Http/Routing/src/Matching/DfaNode.cs
+++ b/src/Http/Routing/src/Matching/DfaNode.cs
@@ -10,128 +10,127 @@ using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Intermediate data structure used to build the DFA. Not used at runtime.
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+internal class DfaNode
{
- // Intermediate data structure used to build the DFA. Not used at runtime.
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- internal class DfaNode
- {
- // The depth of the node. The depth indicates the number of segments
- // that must be processed to arrive at this node.
- //
- // This value is not computed for Policy nodes and will be set to -1.
- public int PathDepth { get; set; } = -1;
+ // The depth of the node. The depth indicates the number of segments
+ // that must be processed to arrive at this node.
+ //
+ // This value is not computed for Policy nodes and will be set to -1.
+ public int PathDepth { get; set; } = -1;
- // Just for diagnostics and debugging
- public string Label { get; set; }
+ // Just for diagnostics and debugging
+ public string Label { get; set; }
- public List<Endpoint> Matches { get; private set; }
+ public List<Endpoint> Matches { get; private set; }
- public Dictionary<string, DfaNode> Literals { get; private set; }
+ public Dictionary<string, DfaNode> Literals { get; private set; }
- public DfaNode Parameters { get; set; }
+ public DfaNode Parameters { get; set; }
- public DfaNode CatchAll { get; set; }
+ public DfaNode CatchAll { get; set; }
- public INodeBuilderPolicy NodeBuilder { get; set; }
+ public INodeBuilderPolicy NodeBuilder { get; set; }
- public Dictionary<object, DfaNode> PolicyEdges { get; private set; }
+ public Dictionary<object, DfaNode> PolicyEdges { get; private set; }
- public void AddPolicyEdge(object state, DfaNode node)
+ public void AddPolicyEdge(object state, DfaNode node)
+ {
+ if (PolicyEdges == null)
{
- if (PolicyEdges == null)
- {
- PolicyEdges = new Dictionary<object, DfaNode>();
- }
-
- PolicyEdges.Add(state, node);
+ PolicyEdges = new Dictionary<object, DfaNode>();
}
- public void AddLiteral(string literal, DfaNode node)
- {
- if (Literals == null)
- {
- Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
- }
+ PolicyEdges.Add(state, node);
+ }
- Literals.Add(literal, node);
+ public void AddLiteral(string literal, DfaNode node)
+ {
+ if (Literals == null)
+ {
+ Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
}
- public void AddMatch(Endpoint endpoint)
+ Literals.Add(literal, node);
+ }
+
+ public void AddMatch(Endpoint endpoint)
+ {
+ if (Matches == null)
{
- if (Matches == null)
- {
- Matches = new List<Endpoint>();
- }
+ Matches = new List<Endpoint>();
+ }
- Matches.Add(endpoint);
+ Matches.Add(endpoint);
+ }
+
+ public void AddMatches(IEnumerable<Endpoint> endpoints)
+ {
+ if (Matches == null)
+ {
+ Matches = new List<Endpoint>(endpoints);
+ }
+ else
+ {
+ Matches.AddRange(endpoints);
}
+ }
- public void AddMatches(IEnumerable<Endpoint> endpoints)
+ public void Visit(Action<DfaNode> visitor)
+ {
+ if (Literals != null)
{
- if (Matches == null)
- {
- Matches = new List<Endpoint>(endpoints);
- }
- else
+ foreach (var kvp in Literals)
{
- Matches.AddRange(endpoints);
+ kvp.Value.Visit(visitor);
}
}
- public void Visit(Action<DfaNode> visitor)
+ // Break cycles
+ if (Parameters != null && !ReferenceEquals(this, Parameters))
{
- if (Literals != null)
- {
- foreach (var kvp in Literals)
- {
- kvp.Value.Visit(visitor);
- }
- }
+ Parameters.Visit(visitor);
+ }
- // Break cycles
- if (Parameters != null && !ReferenceEquals(this, Parameters))
- {
- Parameters.Visit(visitor);
- }
+ // Break cycles
+ if (CatchAll != null && !ReferenceEquals(this, CatchAll))
+ {
+ CatchAll.Visit(visitor);
+ }
- // Break cycles
- if (CatchAll != null && !ReferenceEquals(this, CatchAll))
+ if (PolicyEdges != null)
+ {
+ foreach (var kvp in PolicyEdges)
{
- CatchAll.Visit(visitor);
+ kvp.Value.Visit(visitor);
}
+ }
- if (PolicyEdges != null)
- {
- foreach (var kvp in PolicyEdges)
- {
- kvp.Value.Visit(visitor);
- }
- }
+ visitor(this);
+ }
- visitor(this);
+ private string DebuggerToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append(Label);
+ builder.Append(" d:");
+ builder.Append(PathDepth);
+ builder.Append(" m:");
+ builder.Append(Matches?.Count ?? 0);
+ builder.Append(" c: ");
+ if (Literals != null)
+ {
+ builder.AppendJoin(", ", Literals.Select(kvp => $"{kvp.Key}->({FormatNode(kvp.Value)})"));
}
+ return builder.ToString();
- private string DebuggerToString()
+ // DfaNodes can be self-referential, don't traverse cycles.
+ string FormatNode(DfaNode other)
{
- var builder = new StringBuilder();
- builder.Append(Label);
- builder.Append(" d:");
- builder.Append(PathDepth);
- builder.Append(" m:");
- builder.Append(Matches?.Count ?? 0);
- builder.Append(" c: ");
- if (Literals != null)
- {
- builder.AppendJoin(", ", Literals.Select(kvp => $"{kvp.Key}->({FormatNode(kvp.Value)})"));
- }
- return builder.ToString();
-
- // DfaNodes can be self-referential, don't traverse cycles.
- string FormatNode(DfaNode other)
- {
- return ReferenceEquals(this, other) ? "this" : other.DebuggerToString();
- }
+ return ReferenceEquals(this, other) ? "this" : other.DebuggerToString();
}
}
}
diff --git a/src/Http/Routing/src/Matching/DfaState.cs b/src/Http/Routing/src/Matching/DfaState.cs
index 04ec059c33..a33a47cbc8 100644
--- a/src/Http/Routing/src/Matching/DfaState.cs
+++ b/src/Http/Routing/src/Matching/DfaState.cs
@@ -3,34 +3,33 @@
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+internal readonly struct DfaState
{
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- internal readonly struct DfaState
- {
- public readonly Candidate[] Candidates;
- public readonly IEndpointSelectorPolicy[] Policies;
- public readonly JumpTable PathTransitions;
- public readonly PolicyJumpTable PolicyTransitions;
+ public readonly Candidate[] Candidates;
+ public readonly IEndpointSelectorPolicy[] Policies;
+ public readonly JumpTable PathTransitions;
+ public readonly PolicyJumpTable PolicyTransitions;
- public DfaState(
- Candidate[] candidates,
- IEndpointSelectorPolicy[] policies,
- JumpTable pathTransitions,
- PolicyJumpTable policyTransitions)
- {
- Candidates = candidates;
- Policies = policies;
- PathTransitions = pathTransitions;
- PolicyTransitions = policyTransitions;
- }
+ public DfaState(
+ Candidate[] candidates,
+ IEndpointSelectorPolicy[] policies,
+ JumpTable pathTransitions,
+ PolicyJumpTable policyTransitions)
+ {
+ Candidates = candidates;
+ Policies = policies;
+ PathTransitions = pathTransitions;
+ PolicyTransitions = policyTransitions;
+ }
- public string DebuggerToString()
- {
- return
- $"matches: {Candidates?.Length ?? 0}, " +
- $"path: ({PathTransitions?.DebuggerToString()}), " +
- $"policy: ({PolicyTransitions?.DebuggerToString()})";
- }
+ public string DebuggerToString()
+ {
+ return
+ $"matches: {Candidates?.Length ?? 0}, " +
+ $"path: ({PathTransitions?.DebuggerToString()}), " +
+ $"policy: ({PolicyTransitions?.DebuggerToString()})";
}
}
diff --git a/src/Http/Routing/src/Matching/DictionaryJumpTable.cs b/src/Http/Routing/src/Matching/DictionaryJumpTable.cs
index b4649d18d8..8efe718ea0 100644
--- a/src/Http/Routing/src/Matching/DictionaryJumpTable.cs
+++ b/src/Http/Routing/src/Matching/DictionaryJumpTable.cs
@@ -6,63 +6,62 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class DictionaryJumpTable : JumpTable
{
- internal class DictionaryJumpTable : JumpTable
+ private readonly int _defaultDestination;
+ private readonly int _exitDestination;
+ private readonly Dictionary<string, int> _dictionary;
+
+ public DictionaryJumpTable(
+ int defaultDestination,
+ int exitDestination,
+ (string text, int destination)[] entries)
{
- private readonly int _defaultDestination;
- private readonly int _exitDestination;
- private readonly Dictionary<string, int> _dictionary;
+ _defaultDestination = defaultDestination;
+ _exitDestination = exitDestination;
- public DictionaryJumpTable(
- int defaultDestination,
- int exitDestination,
- (string text, int destination)[] entries)
+ _dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
+ for (var i = 0; i < entries.Length; i++)
{
- _defaultDestination = defaultDestination;
- _exitDestination = exitDestination;
-
- _dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
- for (var i = 0; i < entries.Length; i++)
- {
- _dictionary.Add(entries[i].text, entries[i].destination);
- }
+ _dictionary.Add(entries[i].text, entries[i].destination);
}
+ }
- public override int GetDestination(string path, PathSegment segment)
+ public override int GetDestination(string path, PathSegment segment)
+ {
+ if (segment.Length == 0)
{
- if (segment.Length == 0)
- {
- return _exitDestination;
- }
-
- var text = path.Substring(segment.Start, segment.Length);
- if (_dictionary.TryGetValue(text, out var destination))
- {
- return destination;
- }
-
- return _defaultDestination;
+ return _exitDestination;
}
- public override string DebuggerToString()
+ var text = path.Substring(segment.Start, segment.Length);
+ if (_dictionary.TryGetValue(text, out var destination))
{
- var builder = new StringBuilder();
- builder.Append("{ ");
+ return destination;
+ }
+
+ return _defaultDestination;
+ }
- builder.AppendJoin(", ", _dictionary.Select(kvp => $"{kvp.Key}: {kvp.Value}"));
+ public override string DebuggerToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append("{ ");
- builder.Append("$+: ");
- builder.Append(_defaultDestination);
- builder.Append(", ");
+ builder.AppendJoin(", ", _dictionary.Select(kvp => $"{kvp.Key}: {kvp.Value}"));
- builder.Append("$0: ");
- builder.Append(_defaultDestination);
+ builder.Append("$+: ");
+ builder.Append(_defaultDestination);
+ builder.Append(", ");
- builder.Append(" }");
+ builder.Append("$0: ");
+ builder.Append(_defaultDestination);
+ builder.Append(" }");
- return builder.ToString();
- }
+
+ return builder.ToString();
}
}
diff --git a/src/Http/Routing/src/Matching/EndpointComparer.cs b/src/Http/Routing/src/Matching/EndpointComparer.cs
index 664a0de9c9..5371ae2cac 100644
--- a/src/Http/Routing/src/Matching/EndpointComparer.cs
+++ b/src/Http/Routing/src/Matching/EndpointComparer.cs
@@ -6,54 +6,111 @@ using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Use to sort and group Endpoints. RouteEndpoints are sorted before other implementations.
+//
+// NOTE:
+// When ordering endpoints, we compare the route templates as an absolute last resort.
+// This is used as a factor to ensure that we always have a predictable ordering
+// for tests, errors, etc.
+//
+// When we group endpoints we don't consider the route template, because we're trying
+// to group endpoints not separate them.
+//
+// TLDR:
+// IComparer implementation considers the template string as a tiebreaker.
+// IEqualityComparer implementation does not.
+// This is cool and good.
+internal class EndpointComparer : IComparer<Endpoint>, IEqualityComparer<Endpoint>
{
- // Use to sort and group Endpoints. RouteEndpoints are sorted before other implementations.
- //
- // NOTE:
- // When ordering endpoints, we compare the route templates as an absolute last resort.
- // This is used as a factor to ensure that we always have a predictable ordering
- // for tests, errors, etc.
- //
- // When we group endpoints we don't consider the route template, because we're trying
- // to group endpoints not separate them.
- //
- // TLDR:
- // IComparer implementation considers the template string as a tiebreaker.
- // IEqualityComparer implementation does not.
- // This is cool and good.
- internal class EndpointComparer : IComparer<Endpoint>, IEqualityComparer<Endpoint>
+ private readonly IComparer<Endpoint>[] _comparers;
+
+ public EndpointComparer(IEndpointComparerPolicy[] policies)
+ {
+ // Order, Precedence, (others)...
+ _comparers = new IComparer<Endpoint>[2 + policies.Length];
+ _comparers[0] = OrderComparer.Instance;
+ _comparers[1] = PrecedenceComparer.Instance;
+ for (var i = 0; i < policies.Length; i++)
+ {
+ _comparers[i + 2] = policies[i].Comparer;
+ }
+ }
+
+ public int Compare(Endpoint? x, Endpoint? y)
{
- private readonly IComparer<Endpoint>[] _comparers;
+ // We don't expose this publicly, and we should never call it on
+ // a null endpoint.
+ Debug.Assert(x != null);
+ Debug.Assert(y != null);
+
+ var compare = CompareCore(x, y);
- public EndpointComparer(IEndpointComparerPolicy[] policies)
+ // Since we're sorting, use the route template as a last resort.
+ return compare == 0 ? ComparePattern(x, y) : compare;
+ }
+
+ private static int ComparePattern(Endpoint x, Endpoint y)
+ {
+ // A RouteEndpoint always comes before a non-RouteEndpoint, regardless of its RawText value
+ var routeEndpointX = x as RouteEndpoint;
+ var routeEndpointY = y as RouteEndpoint;
+
+ if (routeEndpointX != null)
{
- // Order, Precedence, (others)...
- _comparers = new IComparer<Endpoint>[2 + policies.Length];
- _comparers[0] = OrderComparer.Instance;
- _comparers[1] = PrecedenceComparer.Instance;
- for (var i = 0; i < policies.Length; i++)
+ if (routeEndpointY != null)
{
- _comparers[i + 2] = policies[i].Comparer;
+ return string.Compare(routeEndpointX.RoutePattern.RawText, routeEndpointY.RoutePattern.RawText, StringComparison.OrdinalIgnoreCase);
}
- }
- public int Compare(Endpoint? x, Endpoint? y)
+ return 1;
+ }
+ else if (routeEndpointY != null)
{
- // We don't expose this publicly, and we should never call it on
- // a null endpoint.
- Debug.Assert(x != null);
- Debug.Assert(y != null);
+ return -1;
+ }
- var compare = CompareCore(x, y);
+ return 0;
+ }
+
+ public bool Equals(Endpoint? x, Endpoint? y)
+ {
+ // We don't expose this publicly, and we should never call it on
+ // a null endpoint.
+ Debug.Assert(x != null);
+ Debug.Assert(y != null);
- // Since we're sorting, use the route template as a last resort.
- return compare == 0 ? ComparePattern(x, y) : compare;
+ return CompareCore(x, y) == 0;
+ }
+
+ public int GetHashCode(Endpoint obj)
+ {
+ // This should not be possible to call publicly.
+ Debug.Fail("We don't expect this to be called.");
+ throw new System.NotImplementedException();
+ }
+
+ private int CompareCore(Endpoint x, Endpoint y)
+ {
+ for (var i = 0; i < _comparers.Length; i++)
+ {
+ var compare = _comparers[i].Compare(x, y);
+ if (compare != 0)
+ {
+ return compare;
+ }
}
- private static int ComparePattern(Endpoint x, Endpoint y)
+ return 0;
+ }
+
+ private class OrderComparer : IComparer<Endpoint>
+ {
+ public static readonly IComparer<Endpoint> Instance = new OrderComparer();
+
+ public int Compare(Endpoint? x, Endpoint? y)
{
- // A RouteEndpoint always comes before a non-RouteEndpoint, regardless of its RawText value
var routeEndpointX = x as RouteEndpoint;
var routeEndpointY = y as RouteEndpoint;
@@ -61,7 +118,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{
if (routeEndpointY != null)
{
- return string.Compare(routeEndpointX.RoutePattern.RawText, routeEndpointY.RoutePattern.RawText, StringComparison.OrdinalIgnoreCase);
+ return routeEndpointX.Order.CompareTo(routeEndpointY.Order);
}
return 1;
@@ -73,91 +130,33 @@ namespace Microsoft.AspNetCore.Routing.Matching
return 0;
}
+ }
- public bool Equals(Endpoint? x, Endpoint? y)
- {
- // We don't expose this publicly, and we should never call it on
- // a null endpoint.
- Debug.Assert(x != null);
- Debug.Assert(y != null);
-
- return CompareCore(x, y) == 0;
- }
-
- public int GetHashCode(Endpoint obj)
- {
- // This should not be possible to call publicly.
- Debug.Fail("We don't expect this to be called.");
- throw new System.NotImplementedException();
- }
-
- private int CompareCore(Endpoint x, Endpoint y)
- {
- for (var i = 0; i < _comparers.Length; i++)
- {
- var compare = _comparers[i].Compare(x, y);
- if (compare != 0)
- {
- return compare;
- }
- }
-
- return 0;
- }
+ private class PrecedenceComparer : IComparer<Endpoint>
+ {
+ public static readonly IComparer<Endpoint> Instance = new PrecedenceComparer();
- private class OrderComparer : IComparer<Endpoint>
+ public int Compare(Endpoint? x, Endpoint? y)
{
- public static readonly IComparer<Endpoint> Instance = new OrderComparer();
+ var routeEndpointX = x as RouteEndpoint;
+ var routeEndpointY = y as RouteEndpoint;
- public int Compare(Endpoint? x, Endpoint? y)
+ if (routeEndpointX != null)
{
- var routeEndpointX = x as RouteEndpoint;
- var routeEndpointY = y as RouteEndpoint;
-
- if (routeEndpointX != null)
- {
- if (routeEndpointY != null)
- {
- return routeEndpointX.Order.CompareTo(routeEndpointY.Order);
- }
-
- return 1;
- }
- else if (routeEndpointY != null)
+ if (routeEndpointY != null)
{
- return -1;
+ return routeEndpointX.RoutePattern.InboundPrecedence
+ .CompareTo(routeEndpointY.RoutePattern.InboundPrecedence);
}
- return 0;
+ return 1;
}
- }
-
- private class PrecedenceComparer : IComparer<Endpoint>
- {
- public static readonly IComparer<Endpoint> Instance = new PrecedenceComparer();
-
- public int Compare(Endpoint? x, Endpoint? y)
+ else if (routeEndpointY != null)
{
- var routeEndpointX = x as RouteEndpoint;
- var routeEndpointY = y as RouteEndpoint;
-
- if (routeEndpointX != null)
- {
- if (routeEndpointY != null)
- {
- return routeEndpointX.RoutePattern.InboundPrecedence
- .CompareTo(routeEndpointY.RoutePattern.InboundPrecedence);
- }
-
- return 1;
- }
- else if (routeEndpointY != null)
- {
- return -1;
- }
-
- return 0;
+ return -1;
}
+
+ return 0;
}
}
}
diff --git a/src/Http/Routing/src/Matching/EndpointMetadataComparer.cs b/src/Http/Routing/src/Matching/EndpointMetadataComparer.cs
index 2380cd8197..ce74508e3f 100644
--- a/src/Http/Routing/src/Matching/EndpointMetadataComparer.cs
+++ b/src/Http/Routing/src/Matching/EndpointMetadataComparer.cs
@@ -9,165 +9,164 @@ using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// A comparer that can order <see cref="Endpoint"/> instances based on implementations of
+/// <see cref="IEndpointComparerPolicy" />. The implementation can be retrieved from the service
+/// provider and provided to <see cref="CandidateSet.ExpandEndpoint(int, IReadOnlyList{Endpoint}, IComparer{Endpoint})"/>.
+/// </summary>
+public sealed class EndpointMetadataComparer : IComparer<Endpoint>
{
- /// <summary>
- /// A comparer that can order <see cref="Endpoint"/> instances based on implementations of
- /// <see cref="IEndpointComparerPolicy" />. The implementation can be retrieved from the service
- /// provider and provided to <see cref="CandidateSet.ExpandEndpoint(int, IReadOnlyList{Endpoint}, IComparer{Endpoint})"/>.
- /// </summary>
- public sealed class EndpointMetadataComparer : IComparer<Endpoint>
+ private readonly IServiceProvider _services;
+ private IComparer<Endpoint>[]? _comparers;
+
+ // This type is **INTENDED** for use in MatcherPolicy instances yet is also needs the MatcherPolicy instances.
+ // using IServiceProvider to break the cycle.
+ internal EndpointMetadataComparer(IServiceProvider services)
{
- private readonly IServiceProvider _services;
- private IComparer<Endpoint>[]? _comparers;
+ if (services == null)
+ {
+ throw new ArgumentNullException(nameof(services));
+ }
+
+ _services = services;
+ }
- // This type is **INTENDED** for use in MatcherPolicy instances yet is also needs the MatcherPolicy instances.
- // using IServiceProvider to break the cycle.
- internal EndpointMetadataComparer(IServiceProvider services)
+ private IComparer<Endpoint>[] Comparers
+ {
+ get
{
- if (services == null)
+ if (_comparers == null)
{
- throw new ArgumentNullException(nameof(services));
+ _comparers = _services.GetServices<MatcherPolicy>()
+ .OrderBy(p => p.Order)
+ .OfType<IEndpointComparerPolicy>()
+ .Select(p => p.Comparer)
+ .ToArray();
}
- _services = services;
+ return _comparers;
}
+ }
- private IComparer<Endpoint>[] Comparers
+ int IComparer<Endpoint>.Compare(Endpoint? x, Endpoint? y)
+ {
+ if (x == null)
{
- get
- {
- if (_comparers == null)
- {
- _comparers = _services.GetServices<MatcherPolicy>()
- .OrderBy(p => p.Order)
- .OfType<IEndpointComparerPolicy>()
- .Select(p => p.Comparer)
- .ToArray();
- }
-
- return _comparers;
- }
+ throw new ArgumentNullException(nameof(x));
}
- int IComparer<Endpoint>.Compare(Endpoint? x, Endpoint? y)
+ if (y == null)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
-
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
+ throw new ArgumentNullException(nameof(y));
+ }
- var comparers = Comparers;
- for (var i = 0; i < comparers.Length; i++)
+ var comparers = Comparers;
+ for (var i = 0; i < comparers.Length; i++)
+ {
+ var compare = comparers[i].Compare(x, y);
+ if (compare != 0)
{
- var compare = comparers[i].Compare(x, y);
- if (compare != 0)
- {
- return compare;
- }
+ return compare;
}
-
- return 0;
}
+
+ return 0;
}
+}
+
+/// <summary>
+/// A base class for <see cref="IComparer{Endpoint}"/> implementations that use
+/// a specific type of metadata from <see cref="Endpoint.Metadata"/> for comparison.
+/// Useful for implementing <see cref="IEndpointComparerPolicy.Comparer"/>.
+/// </summary>
+/// <typeparam name="TMetadata">
+/// The type of metadata to compare. Typically this is a type of metadata related
+/// to the application concern being handled.
+/// </typeparam>
+public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class
+{
+ /// <summary>
+ /// A default instance of the <see cref="EndpointMetadataComparer"/>.
+ /// </summary>
+ public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>();
/// <summary>
- /// A base class for <see cref="IComparer{Endpoint}"/> implementations that use
- /// a specific type of metadata from <see cref="Endpoint.Metadata"/> for comparison.
- /// Useful for implementing <see cref="IEndpointComparerPolicy.Comparer"/>.
+ /// Compares two objects and returns a value indicating whether one is less than, equal to,
+ /// or greater than the other.
/// </summary>
- /// <typeparam name="TMetadata">
- /// The type of metadata to compare. Typically this is a type of metadata related
- /// to the application concern being handled.
- /// </typeparam>
- public abstract class EndpointMetadataComparer<TMetadata> : IComparer<Endpoint> where TMetadata : class
+ /// <param name="x">The first object to compare.</param>
+ /// <param name="y">The second object to compare.</param>
+ /// <returns>
+ /// An implementation of this method must return a value less than zero if
+ /// x is less than y, zero if x is equal to y, or a value greater than zero if x is
+ /// greater than y.
+ /// </returns>
+ public int Compare(Endpoint? x, Endpoint? y)
{
- /// <summary>
- /// A default instance of the <see cref="EndpointMetadataComparer"/>.
- /// </summary>
- public static readonly EndpointMetadataComparer<TMetadata> Default = new DefaultComparer<TMetadata>();
-
- /// <summary>
- /// Compares two objects and returns a value indicating whether one is less than, equal to,
- /// or greater than the other.
- /// </summary>
- /// <param name="x">The first object to compare.</param>
- /// <param name="y">The second object to compare.</param>
- /// <returns>
- /// An implementation of this method must return a value less than zero if
- /// x is less than y, zero if x is equal to y, or a value greater than zero if x is
- /// greater than y.
- /// </returns>
- public int Compare(Endpoint? x, Endpoint? y)
+ if (x == null)
{
- if (x == null)
- {
- throw new ArgumentNullException(nameof(x));
- }
-
- if (y == null)
- {
- throw new ArgumentNullException(nameof(y));
- }
-
- return CompareMetadata(GetMetadata(x), GetMetadata(y));
+ throw new ArgumentNullException(nameof(x));
}
- /// <summary>
- /// Gets the metadata of type <typeparamref name="TMetadata"/> from the provided endpoint.
- /// </summary>
- /// <param name="endpoint">The <see cref="Endpoint"/>.</param>
- /// <returns>The <typeparamref name="TMetadata"/> instance or <c>null</c>.</returns>
- protected virtual TMetadata? GetMetadata(Endpoint endpoint)
+ if (y == null)
{
- return endpoint.Metadata.GetMetadata<TMetadata>();
+ throw new ArgumentNullException(nameof(y));
}
- /// <summary>
- /// Compares two <typeparamref name="TMetadata"/> instances.
- /// </summary>
- /// <param name="x">The first object to compare.</param>
- /// <param name="y">The second object to compare.</param>
- /// <returns>
- /// An implementation of this method must return a value less than zero if
- /// x is less than y, zero if x is equal to y, or a value greater than zero if x is
- /// greater than y.
- /// </returns>
- /// <remarks>
- /// The base-class implementation of this method will compare metadata based on whether
- /// or not they are <c>null</c>. The effect of this is that when endpoints are being
- /// compared, the endpoint that defines an instance of <typeparamref name="TMetadata"/>
- /// will be considered higher priority.
- /// </remarks>
- protected virtual int CompareMetadata(TMetadata? x, TMetadata? y)
- {
- // The default policy is that if x endpoint defines TMetadata, and
- // y endpoint does not, then x is *more specific* than y. We return
- // -1 for this case so that x will come first in the sort order.
+ return CompareMetadata(GetMetadata(x), GetMetadata(y));
+ }
- if (x == null && y != null)
- {
- // y is more specific
- return 1;
- }
- else if (x != null && y == null)
- {
- // x is more specific
- return -1;
- }
+ /// <summary>
+ /// Gets the metadata of type <typeparamref name="TMetadata"/> from the provided endpoint.
+ /// </summary>
+ /// <param name="endpoint">The <see cref="Endpoint"/>.</param>
+ /// <returns>The <typeparamref name="TMetadata"/> instance or <c>null</c>.</returns>
+ protected virtual TMetadata? GetMetadata(Endpoint endpoint)
+ {
+ return endpoint.Metadata.GetMetadata<TMetadata>();
+ }
- // both endpoints have this metadata, or both do not have it, they have
- // the same specificity.
- return 0;
- }
+ /// <summary>
+ /// Compares two <typeparamref name="TMetadata"/> instances.
+ /// </summary>
+ /// <param name="x">The first object to compare.</param>
+ /// <param name="y">The second object to compare.</param>
+ /// <returns>
+ /// An implementation of this method must return a value less than zero if
+ /// x is less than y, zero if x is equal to y, or a value greater than zero if x is
+ /// greater than y.
+ /// </returns>
+ /// <remarks>
+ /// The base-class implementation of this method will compare metadata based on whether
+ /// or not they are <c>null</c>. The effect of this is that when endpoints are being
+ /// compared, the endpoint that defines an instance of <typeparamref name="TMetadata"/>
+ /// will be considered higher priority.
+ /// </remarks>
+ protected virtual int CompareMetadata(TMetadata? x, TMetadata? y)
+ {
+ // The default policy is that if x endpoint defines TMetadata, and
+ // y endpoint does not, then x is *more specific* than y. We return
+ // -1 for this case so that x will come first in the sort order.
- private class DefaultComparer<T> : EndpointMetadataComparer<T> where T : class
+ if (x == null && y != null)
{
+ // y is more specific
+ return 1;
}
+ else if (x != null && y == null)
+ {
+ // x is more specific
+ return -1;
+ }
+
+ // both endpoints have this metadata, or both do not have it, they have
+ // the same specificity.
+ return 0;
+ }
+
+ private class DefaultComparer<T> : EndpointMetadataComparer<T> where T : class
+ {
}
}
diff --git a/src/Http/Routing/src/Matching/EndpointSelector.cs b/src/Http/Routing/src/Matching/EndpointSelector.cs
index a2cd1f7d57..5b520e62bb 100644
--- a/src/Http/Routing/src/Matching/EndpointSelector.cs
+++ b/src/Http/Routing/src/Matching/EndpointSelector.cs
@@ -4,26 +4,25 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// A service that is responsible for the final <see cref="Endpoint"/> selection
+/// decision. To use a custom <see cref="EndpointSelector"/> register an implementation
+/// of <see cref="EndpointSelector"/> in the dependency injection container as a singleton.
+/// </summary>
+public abstract class EndpointSelector
{
/// <summary>
- /// A service that is responsible for the final <see cref="Endpoint"/> selection
- /// decision. To use a custom <see cref="EndpointSelector"/> register an implementation
- /// of <see cref="EndpointSelector"/> in the dependency injection container as a singleton.
+ /// Asynchronously selects an <see cref="Endpoint"/> from the <see cref="CandidateSet"/>.
/// </summary>
- public abstract class EndpointSelector
- {
- /// <summary>
- /// Asynchronously selects an <see cref="Endpoint"/> from the <see cref="CandidateSet"/>.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="candidates">The <see cref="CandidateSet"/>.</param>
- /// <returns>A <see cref="Task"/> that completes asynchronously once endpoint selection is complete.</returns>
- /// <remarks>
- /// An <see cref="EndpointSelector"/> should assign the endpoint by calling
- /// <see cref="EndpointHttpContextExtensions.SetEndpoint(HttpContext, Endpoint)"/>
- /// and setting <see cref="HttpRequest.RouteValues"/> once an endpoint is selected.
- /// </remarks>
- public abstract Task SelectAsync(HttpContext httpContext, CandidateSet candidates);
- }
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="candidates">The <see cref="CandidateSet"/>.</param>
+ /// <returns>A <see cref="Task"/> that completes asynchronously once endpoint selection is complete.</returns>
+ /// <remarks>
+ /// An <see cref="EndpointSelector"/> should assign the endpoint by calling
+ /// <see cref="EndpointHttpContextExtensions.SetEndpoint(HttpContext, Endpoint)"/>
+ /// and setting <see cref="HttpRequest.RouteValues"/> once an endpoint is selected.
+ /// </remarks>
+ public abstract Task SelectAsync(HttpContext httpContext, CandidateSet candidates);
}
diff --git a/src/Http/Routing/src/Matching/FastPathTokenizer.cs b/src/Http/Routing/src/Matching/FastPathTokenizer.cs
index 73f59c77fd..29ba5982b0 100644
--- a/src/Http/Routing/src/Matching/FastPathTokenizer.cs
+++ b/src/Http/Routing/src/Matching/FastPathTokenizer.cs
@@ -3,44 +3,43 @@
using System;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Low level implementation of our path tokenization algorithm. Alternative
+// to PathTokenizer.
+internal static class FastPathTokenizer
{
- // Low level implementation of our path tokenization algorithm. Alternative
- // to PathTokenizer.
- internal static class FastPathTokenizer
+ // This section tokenizes the path by marking the sequence of slashes, and their
+ // and the length of the text between them.
+ //
+ // If there is residue (text after last slash) then the length of the segment will
+ // computed based on the string length.
+ public static int Tokenize(string path, Span<PathSegment> segments)
{
- // This section tokenizes the path by marking the sequence of slashes, and their
- // and the length of the text between them.
- //
- // If there is residue (text after last slash) then the length of the segment will
- // computed based on the string length.
- public static int Tokenize(string path, Span<PathSegment> segments)
+ // This can happen in test scenarios.
+ if (string.IsNullOrEmpty(path))
{
- // This can happen in test scenarios.
- if (string.IsNullOrEmpty(path))
- {
- return 0;
- }
-
- int count = 0;
- int start = 1; // Paths always start with a leading /
- int end;
- var span = path.AsSpan(start);
- while ((end = span.IndexOf('/')) >= 0 && count < segments.Length)
- {
- segments[count++] = new PathSegment(start, end);
- start += end + 1; // resume search after the current character
- span = path.AsSpan(start);
- }
+ return 0;
+ }
- // Residue
- var length = span.Length;
- if (length > 0 && count < segments.Length)
- {
- segments[count++] = new PathSegment(start, length);
- }
+ int count = 0;
+ int start = 1; // Paths always start with a leading /
+ int end;
+ var span = path.AsSpan(start);
+ while ((end = span.IndexOf('/')) >= 0 && count < segments.Length)
+ {
+ segments[count++] = new PathSegment(start, end);
+ start += end + 1; // resume search after the current character
+ span = path.AsSpan(start);
+ }
- return count;
+ // Residue
+ var length = span.Length;
+ if (length > 0 && count < segments.Length)
+ {
+ segments[count++] = new PathSegment(start, length);
}
+
+ return count;
}
}
diff --git a/src/Http/Routing/src/Matching/HostMatcherPolicy.cs b/src/Http/Routing/src/Matching/HostMatcherPolicy.cs
index a41d0a24e5..a9c9b790e9 100644
--- a/src/Http/Routing/src/Matching/HostMatcherPolicy.cs
+++ b/src/Http/Routing/src/Matching/HostMatcherPolicy.cs
@@ -8,472 +8,471 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// A <see cref="MatcherPolicy"/> that implements filtering and selection by
+/// the host header of a request.
+/// </summary>
+public sealed class HostMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy, IEndpointSelectorPolicy
{
- /// <summary>
- /// A <see cref="MatcherPolicy"/> that implements filtering and selection by
- /// the host header of a request.
- /// </summary>
- public sealed class HostMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy, IEndpointSelectorPolicy
- {
- private const string WildcardHost = "*";
- private const string WildcardPrefix = "*.";
+ private const string WildcardHost = "*";
+ private const string WildcardPrefix = "*.";
- // Run after HTTP methods, but before 'default'.
- /// <inheritdoc />
- public override int Order { get; } = -100;
+ // Run after HTTP methods, but before 'default'.
+ /// <inheritdoc />
+ public override int Order { get; } = -100;
- /// <inheritdoc />
- public IComparer<Endpoint> Comparer { get; } = new HostMetadataEndpointComparer();
+ /// <inheritdoc />
+ public IComparer<Endpoint> Comparer { get; } = new HostMetadataEndpointComparer();
- bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ if (endpoints == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- return !ContainsDynamicEndpoints(endpoints) && AppliesToEndpointsCore(endpoints);
+ throw new ArgumentNullException(nameof(endpoints));
}
- bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- // When the node contains dynamic endpoints we can't make any assumptions.
- var applies = ContainsDynamicEndpoints(endpoints);
- if (applies)
- {
- // Run for the side-effect of validating metadata.
- AppliesToEndpointsCore(endpoints);
- }
+ return !ContainsDynamicEndpoints(endpoints) && AppliesToEndpointsCore(endpoints);
+ }
- return applies;
+ bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ // When the node contains dynamic endpoints we can't make any assumptions.
+ var applies = ContainsDynamicEndpoints(endpoints);
+ if (applies)
+ {
+ // Run for the side-effect of validating metadata.
+ AppliesToEndpointsCore(endpoints);
}
- private static bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
+ return applies;
+ }
+
+ private static bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
+ {
+ return endpoints.Any(e =>
{
- return endpoints.Any(e =>
+ var hosts = e.Metadata.GetMetadata<IHostMetadata>()?.Hosts;
+ if (hosts == null || hosts.Count == 0)
{
- var hosts = e.Metadata.GetMetadata<IHostMetadata>()?.Hosts;
- if (hosts == null || hosts.Count == 0)
- {
- return false;
- }
+ return false;
+ }
- foreach (var host in hosts)
- {
+ foreach (var host in hosts)
+ {
// Don't run policy on endpoints that match everything
var key = CreateEdgeKey(host);
- if (!key.MatchesAll)
- {
- return true;
- }
+ if (!key.MatchesAll)
+ {
+ return true;
}
+ }
- return false;
- });
+ return false;
+ });
+ }
+
+ /// <inheritdoc />
+ public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
}
- /// <inheritdoc />
- public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
+ if (candidates == null)
{
- if (httpContext == null)
+ throw new ArgumentNullException(nameof(candidates));
+ }
+
+ for (var i = 0; i < candidates.Count; i++)
+ {
+ if (!candidates.IsValidCandidate(i))
{
- throw new ArgumentNullException(nameof(httpContext));
+ continue;
}
- if (candidates == null)
+ var hosts = candidates[i].Endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts;
+ if (hosts == null || hosts.Count == 0)
{
- throw new ArgumentNullException(nameof(candidates));
+ // Can match any host.
+ continue;
}
- for (var i = 0; i < candidates.Count; i++)
+ var matched = false;
+ var (requestHost, requestPort) = GetHostAndPort(httpContext);
+ for (var j = 0; j < hosts.Count; j++)
{
- if (!candidates.IsValidCandidate(i))
+ var host = hosts[j].AsSpan();
+ var port = ReadOnlySpan<char>.Empty;
+
+ // Split into host and port
+ var pivot = host.IndexOf(':');
+ if (pivot >= 0)
{
- continue;
+ port = host.Slice(pivot + 1);
+ host = host.Slice(0, pivot);
}
- var hosts = candidates[i].Endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts;
- if (hosts == null || hosts.Count == 0)
+ if (host == null || MemoryExtensions.Equals(host, WildcardHost, StringComparison.OrdinalIgnoreCase))
{
- // Can match any host.
- continue;
+ // Can match any host
}
+ else if (
+ host.StartsWith(WildcardPrefix) &&
- var matched = false;
- var (requestHost, requestPort) = GetHostAndPort(httpContext);
- for (var j = 0; j < hosts.Count; j++)
+ // Note that we only slice off the `*`. We want to match the leading `.` also.
+ MemoryExtensions.EndsWith(requestHost, host.Slice(WildcardHost.Length), StringComparison.OrdinalIgnoreCase))
{
- var host = hosts[j].AsSpan();
- var port = ReadOnlySpan<char>.Empty;
-
- // Split into host and port
- var pivot = host.IndexOf(':');
- if (pivot >= 0)
- {
- port = host.Slice(pivot + 1);
- host = host.Slice(0, pivot);
- }
-
- if (host == null || MemoryExtensions.Equals(host, WildcardHost, StringComparison.OrdinalIgnoreCase))
- {
- // Can match any host
- }
- else if (
- host.StartsWith(WildcardPrefix) &&
-
- // Note that we only slice off the `*`. We want to match the leading `.` also.
- MemoryExtensions.EndsWith(requestHost, host.Slice(WildcardHost.Length), StringComparison.OrdinalIgnoreCase))
- {
- // Matches a suffix wildcard.
- }
- else if (MemoryExtensions.Equals(requestHost, host, StringComparison.OrdinalIgnoreCase))
- {
- // Matches exactly
- }
- else
- {
- // If we get here then the host doesn't match.
- continue;
- }
-
- if (MemoryExtensions.Equals(port, WildcardHost, StringComparison.OrdinalIgnoreCase))
- {
- // Port is a wildcard, we allow any port.
- }
- else if (port.Length > 0 && (!int.TryParse(port, out var parsed) || parsed != requestPort))
- {
- // If we get here then the port doesn't match.
- continue;
- }
-
- matched = true;
- break;
+ // Matches a suffix wildcard.
+ }
+ else if (MemoryExtensions.Equals(requestHost, host, StringComparison.OrdinalIgnoreCase))
+ {
+ // Matches exactly
+ }
+ else
+ {
+ // If we get here then the host doesn't match.
+ continue;
}
- if (!matched)
+ if (MemoryExtensions.Equals(port, WildcardHost, StringComparison.OrdinalIgnoreCase))
+ {
+ // Port is a wildcard, we allow any port.
+ }
+ else if (port.Length > 0 && (!int.TryParse(port, out var parsed) || parsed != requestPort))
{
- candidates.SetValidity(i, false);
+ // If we get here then the port doesn't match.
+ continue;
}
+
+ matched = true;
+ break;
}
- return Task.CompletedTask;
+ if (!matched)
+ {
+ candidates.SetValidity(i, false);
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private static EdgeKey CreateEdgeKey(string host)
+ {
+ if (host == null)
+ {
+ return EdgeKey.WildcardEdgeKey;
}
- private static EdgeKey CreateEdgeKey(string host)
+ var hostParts = host.Split(':');
+ if (hostParts.Length == 1)
{
- if (host == null)
+ if (!string.IsNullOrEmpty(hostParts[0]))
{
- return EdgeKey.WildcardEdgeKey;
+ return new EdgeKey(hostParts[0], null);
}
-
- var hostParts = host.Split(':');
- if (hostParts.Length == 1)
+ }
+ if (hostParts.Length == 2)
+ {
+ if (!string.IsNullOrEmpty(hostParts[0]))
{
- if (!string.IsNullOrEmpty(hostParts[0]))
+ if (int.TryParse(hostParts[1], out var port))
{
- return new EdgeKey(hostParts[0], null);
+ return new EdgeKey(hostParts[0], port);
}
- }
- if (hostParts.Length == 2)
- {
- if (!string.IsNullOrEmpty(hostParts[0]))
+ else if (string.Equals(hostParts[1], WildcardHost, StringComparison.Ordinal))
{
- if (int.TryParse(hostParts[1], out var port))
- {
- return new EdgeKey(hostParts[0], port);
- }
- else if (string.Equals(hostParts[1], WildcardHost, StringComparison.Ordinal))
- {
- return new EdgeKey(hostParts[0], null);
- }
+ return new EdgeKey(hostParts[0], null);
}
}
+ }
+
+ throw new InvalidOperationException($"Could not parse host: {host}");
+ }
- throw new InvalidOperationException($"Could not parse host: {host}");
+ /// <inheritdoc />
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
+ {
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
}
- /// <inheritdoc />
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
+ // The algorithm here is designed to be preserve the order of the endpoints
+ // while also being relatively simple. Preserving order is important.
+
+ // First, build a dictionary of all of the hosts that are included
+ // at this node.
+ //
+ // For now we're just building up the set of keys. We don't add any endpoints
+ // to lists now because we don't want ordering problems.
+ var edges = new Dictionary<EdgeKey, List<Endpoint>>();
+ for (var i = 0; i < endpoints.Count; i++)
{
- if (endpoints == null)
+ var endpoint = endpoints[i];
+ var hosts = endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts.Select(h => CreateEdgeKey(h)).ToArray();
+ if (hosts == null || hosts.Length == 0)
{
- throw new ArgumentNullException(nameof(endpoints));
+ hosts = new[] { EdgeKey.WildcardEdgeKey };
}
- // The algorithm here is designed to be preserve the order of the endpoints
- // while also being relatively simple. Preserving order is important.
-
- // First, build a dictionary of all of the hosts that are included
- // at this node.
- //
- // For now we're just building up the set of keys. We don't add any endpoints
- // to lists now because we don't want ordering problems.
- var edges = new Dictionary<EdgeKey, List<Endpoint>>();
- for (var i = 0; i < endpoints.Count; i++)
+ for (var j = 0; j < hosts.Length; j++)
{
- var endpoint = endpoints[i];
- var hosts = endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts.Select(h => CreateEdgeKey(h)).ToArray();
- if (hosts == null || hosts.Length == 0)
+ var host = hosts[j];
+ if (!edges.ContainsKey(host))
{
- hosts = new[] { EdgeKey.WildcardEdgeKey };
- }
-
- for (var j = 0; j < hosts.Length; j++)
- {
- var host = hosts[j];
- if (!edges.ContainsKey(host))
- {
- edges.Add(host, new List<Endpoint>());
- }
+ edges.Add(host, new List<Endpoint>());
}
}
+ }
- // Now in a second loop, add endpoints to these lists. We've enumerated all of
- // the states, so we want to see which states this endpoint matches.
- for (var i = 0; i < endpoints.Count; i++)
- {
- var endpoint = endpoints[i];
+ // Now in a second loop, add endpoints to these lists. We've enumerated all of
+ // the states, so we want to see which states this endpoint matches.
+ for (var i = 0; i < endpoints.Count; i++)
+ {
+ var endpoint = endpoints[i];
- var endpointKeys = endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts.Select(h => CreateEdgeKey(h)).ToArray() ?? Array.Empty<EdgeKey>();
- if (endpointKeys.Length == 0)
+ var endpointKeys = endpoint.Metadata.GetMetadata<IHostMetadata>()?.Hosts.Select(h => CreateEdgeKey(h)).ToArray() ?? Array.Empty<EdgeKey>();
+ if (endpointKeys.Length == 0)
+ {
+ // OK this means that this endpoint matches *all* hosts.
+ // So, loop and add it to all states.
+ foreach (var kvp in edges)
{
- // OK this means that this endpoint matches *all* hosts.
- // So, loop and add it to all states.
- foreach (var kvp in edges)
- {
- kvp.Value.Add(endpoint);
- }
+ kvp.Value.Add(endpoint);
}
- else
+ }
+ else
+ {
+ // OK this endpoint matches specific hosts
+ foreach (var kvp in edges)
{
- // OK this endpoint matches specific hosts
- foreach (var kvp in edges)
+ // The edgeKey maps to a possible request header value
+ var edgeKey = kvp.Key;
+
+ for (var j = 0; j < endpointKeys.Length; j++)
{
- // The edgeKey maps to a possible request header value
- var edgeKey = kvp.Key;
+ var endpointKey = endpointKeys[j];
- for (var j = 0; j < endpointKeys.Length; j++)
+ if (edgeKey.Equals(endpointKey))
+ {
+ kvp.Value.Add(endpoint);
+ break;
+ }
+ else if (edgeKey.HasHostWildcard && endpointKey.HasHostWildcard &&
+ edgeKey.Port == endpointKey.Port && edgeKey.MatchHost(endpointKey.Host))
{
- var endpointKey = endpointKeys[j];
-
- if (edgeKey.Equals(endpointKey))
- {
- kvp.Value.Add(endpoint);
- break;
- }
- else if (edgeKey.HasHostWildcard && endpointKey.HasHostWildcard &&
- edgeKey.Port == endpointKey.Port && edgeKey.MatchHost(endpointKey.Host))
- {
- kvp.Value.Add(endpoint);
- break;
- }
+ kvp.Value.Add(endpoint);
+ break;
}
}
}
}
-
- return edges
- .Select(kvp => new PolicyNodeEdge(kvp.Key, kvp.Value))
- .ToArray();
}
- /// <inheritdoc />
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
+ return edges
+ .Select(kvp => new PolicyNodeEdge(kvp.Key, kvp.Value))
+ .ToArray();
+ }
+
+ /// <inheritdoc />
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
+ {
+ if (edges == null)
{
- if (edges == null)
- {
- throw new ArgumentNullException(nameof(edges));
- }
+ throw new ArgumentNullException(nameof(edges));
+ }
- // Since our 'edges' can have wildcards, we do a sort based on how wildcard-ey they
- // are then then execute them in linear order.
- var ordered = edges
- .Select(e => (host: (EdgeKey)e.State, destination: e.Destination))
- .OrderBy(e => GetScore(e.host))
- .ToArray();
+ // Since our 'edges' can have wildcards, we do a sort based on how wildcard-ey they
+ // are then then execute them in linear order.
+ var ordered = edges
+ .Select(e => (host: (EdgeKey)e.State, destination: e.Destination))
+ .OrderBy(e => GetScore(e.host))
+ .ToArray();
- return new HostPolicyJumpTable(exitDestination, ordered);
- }
+ return new HostPolicyJumpTable(exitDestination, ordered);
+ }
- private static int GetScore(in EdgeKey key)
+ private static int GetScore(in EdgeKey key)
+ {
+ // Higher score == lower priority.
+ if (key.MatchesHost && !key.HasHostWildcard && key.MatchesPort)
{
- // Higher score == lower priority.
- if (key.MatchesHost && !key.HasHostWildcard && key.MatchesPort)
- {
- return 1; // Has host AND port, e.g. www.consoto.com:8080
- }
- else if (key.MatchesHost && !key.HasHostWildcard)
- {
- return 2; // Has host, e.g. www.consoto.com
- }
- else if (key.MatchesHost && key.MatchesPort)
- {
- return 3; // Has wildcard host AND port, e.g. *.consoto.com:8080
- }
- else if (key.MatchesHost)
- {
- return 4; // Has wildcard host, e.g. *.consoto.com
- }
- else if (key.MatchesPort)
- {
- return 5; // Has port, e.g. *:8080
- }
- else
- {
- return 6; // Has neither, e.g. *:* (or no metadata)
- }
+ return 1; // Has host AND port, e.g. www.consoto.com:8080
}
+ else if (key.MatchesHost && !key.HasHostWildcard)
+ {
+ return 2; // Has host, e.g. www.consoto.com
+ }
+ else if (key.MatchesHost && key.MatchesPort)
+ {
+ return 3; // Has wildcard host AND port, e.g. *.consoto.com:8080
+ }
+ else if (key.MatchesHost)
+ {
+ return 4; // Has wildcard host, e.g. *.consoto.com
+ }
+ else if (key.MatchesPort)
+ {
+ return 5; // Has port, e.g. *:8080
+ }
+ else
+ {
+ return 6; // Has neither, e.g. *:* (or no metadata)
+ }
+ }
- private static (string host, int? port) GetHostAndPort(HttpContext httpContext)
+ private static (string host, int? port) GetHostAndPort(HttpContext httpContext)
+ {
+ var hostString = httpContext.Request.Host;
+ if (hostString.Port != null)
{
- var hostString = httpContext.Request.Host;
- if (hostString.Port != null)
- {
- return (hostString.Host, hostString.Port);
- }
- else if (string.Equals("https", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
- {
- return (hostString.Host, 443);
- }
- else if (string.Equals("http", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
- {
- return (hostString.Host, 80);
- }
- else
- {
- return (hostString.Host, null);
- }
+ return (hostString.Host, hostString.Port);
}
+ else if (string.Equals("https", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
+ {
+ return (hostString.Host, 443);
+ }
+ else if (string.Equals("http", httpContext.Request.Scheme, StringComparison.OrdinalIgnoreCase))
+ {
+ return (hostString.Host, 80);
+ }
+ else
+ {
+ return (hostString.Host, null);
+ }
+ }
- private class HostMetadataEndpointComparer : EndpointMetadataComparer<IHostMetadata>
+ private class HostMetadataEndpointComparer : EndpointMetadataComparer<IHostMetadata>
+ {
+ protected override int CompareMetadata(IHostMetadata? x, IHostMetadata? y)
{
- protected override int CompareMetadata(IHostMetadata? x, IHostMetadata? y)
- {
- // Ignore the metadata if it has an empty list of hosts.
- return base.CompareMetadata(
- x?.Hosts.Count > 0 ? x : null,
- y?.Hosts.Count > 0 ? y : null);
- }
+ // Ignore the metadata if it has an empty list of hosts.
+ return base.CompareMetadata(
+ x?.Hosts.Count > 0 ? x : null,
+ y?.Hosts.Count > 0 ? y : null);
}
+ }
+
+ private class HostPolicyJumpTable : PolicyJumpTable
+ {
+ private readonly (EdgeKey host, int destination)[] _destinations;
+ private readonly int _exitDestination;
- private class HostPolicyJumpTable : PolicyJumpTable
+ public HostPolicyJumpTable(int exitDestination, (EdgeKey host, int destination)[] destinations)
{
- private readonly (EdgeKey host, int destination)[] _destinations;
- private readonly int _exitDestination;
+ _exitDestination = exitDestination;
+ _destinations = destinations;
+ }
- public HostPolicyJumpTable(int exitDestination, (EdgeKey host, int destination)[] destinations)
- {
- _exitDestination = exitDestination;
- _destinations = destinations;
- }
+ public override int GetDestination(HttpContext httpContext)
+ {
+ // HostString can allocate when accessing the host or port
+ // Store host and port locally and reuse
+ var (host, port) = GetHostAndPort(httpContext);
- public override int GetDestination(HttpContext httpContext)
+ var destinations = _destinations;
+ for (var i = 0; i < destinations.Length; i++)
{
- // HostString can allocate when accessing the host or port
- // Store host and port locally and reuse
- var (host, port) = GetHostAndPort(httpContext);
+ var destination = destinations[i];
- var destinations = _destinations;
- for (var i = 0; i < destinations.Length; i++)
+ if ((!destination.host.MatchesPort || destination.host.Port == port) &&
+ destination.host.MatchHost(host))
{
- var destination = destinations[i];
-
- if ((!destination.host.MatchesPort || destination.host.Port == port) &&
- destination.host.MatchHost(host))
- {
- return destination.destination;
- }
+ return destination.destination;
}
-
- return _exitDestination;
}
+
+ return _exitDestination;
}
+ }
- private readonly struct EdgeKey : IEquatable<EdgeKey>, IComparable<EdgeKey>, IComparable
- {
- internal static readonly EdgeKey WildcardEdgeKey = new EdgeKey(null, null);
+ private readonly struct EdgeKey : IEquatable<EdgeKey>, IComparable<EdgeKey>, IComparable
+ {
+ internal static readonly EdgeKey WildcardEdgeKey = new EdgeKey(null, null);
- public readonly int? Port;
- public readonly string Host;
+ public readonly int? Port;
+ public readonly string Host;
- private readonly string? _wildcardEndsWith;
+ private readonly string? _wildcardEndsWith;
- public EdgeKey(string? host, int? port)
- {
- Host = host ?? WildcardHost;
- Port = port;
+ public EdgeKey(string? host, int? port)
+ {
+ Host = host ?? WildcardHost;
+ Port = port;
- HasHostWildcard = Host.StartsWith(WildcardPrefix, StringComparison.Ordinal);
- _wildcardEndsWith = HasHostWildcard ? Host.Substring(1) : null;
- }
+ HasHostWildcard = Host.StartsWith(WildcardPrefix, StringComparison.Ordinal);
+ _wildcardEndsWith = HasHostWildcard ? Host.Substring(1) : null;
+ }
- public bool HasHostWildcard { get; }
+ public bool HasHostWildcard { get; }
- public bool MatchesHost => !string.Equals(Host, WildcardHost, StringComparison.Ordinal);
+ public bool MatchesHost => !string.Equals(Host, WildcardHost, StringComparison.Ordinal);
- public bool MatchesPort => Port != null;
+ public bool MatchesPort => Port != null;
- public bool MatchesAll => !MatchesHost && !MatchesPort;
+ public bool MatchesAll => !MatchesHost && !MatchesPort;
- public int CompareTo(EdgeKey other)
+ public int CompareTo(EdgeKey other)
+ {
+ var result = Comparer<string>.Default.Compare(Host, other.Host);
+ if (result != 0)
{
- var result = Comparer<string>.Default.Compare(Host, other.Host);
- if (result != 0)
- {
- return result;
- }
-
- return Comparer<int?>.Default.Compare(Port, other.Port);
+ return result;
}
- public int CompareTo(object? obj)
- {
- return CompareTo((EdgeKey)obj!);
- }
+ return Comparer<int?>.Default.Compare(Port, other.Port);
+ }
- public bool Equals(EdgeKey other)
- {
- return string.Equals(Host, other.Host, StringComparison.Ordinal) && Port == other.Port;
- }
+ public int CompareTo(object? obj)
+ {
+ return CompareTo((EdgeKey)obj!);
+ }
- public bool MatchHost(string host)
+ public bool Equals(EdgeKey other)
+ {
+ return string.Equals(Host, other.Host, StringComparison.Ordinal) && Port == other.Port;
+ }
+
+ public bool MatchHost(string host)
+ {
+ if (MatchesHost)
{
- if (MatchesHost)
+ if (HasHostWildcard)
{
- if (HasHostWildcard)
- {
- return host.EndsWith(_wildcardEndsWith!, StringComparison.OrdinalIgnoreCase);
- }
- else
- {
- return string.Equals(host, Host, StringComparison.OrdinalIgnoreCase);
- }
+ return host.EndsWith(_wildcardEndsWith!, StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ return string.Equals(host, Host, StringComparison.OrdinalIgnoreCase);
}
-
- return true;
}
+ return true;
+ }
- public override int GetHashCode()
- {
- return (Host?.GetHashCode() ?? 0) ^ (Port?.GetHashCode() ?? 0);
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is EdgeKey key)
- {
- return Equals(key);
- }
- return false;
- }
+ public override int GetHashCode()
+ {
+ return (Host?.GetHashCode() ?? 0) ^ (Port?.GetHashCode() ?? 0);
+ }
- public override string ToString()
+ public override bool Equals(object? obj)
+ {
+ if (obj is EdgeKey key)
{
- return $"{Host}:{Port?.ToString(CultureInfo.InvariantCulture) ?? WildcardHost}";
+ return Equals(key);
}
+
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return $"{Host}:{Port?.ToString(CultureInfo.InvariantCulture) ?? WildcardHost}";
}
}
}
diff --git a/src/Http/Routing/src/Matching/HttpMethodDictionaryPolicyJumpTable.cs b/src/Http/Routing/src/Matching/HttpMethodDictionaryPolicyJumpTable.cs
index 31b4e58f7c..52d969f1c9 100644
--- a/src/Http/Routing/src/Matching/HttpMethodDictionaryPolicyJumpTable.cs
+++ b/src/Http/Routing/src/Matching/HttpMethodDictionaryPolicyJumpTable.cs
@@ -4,45 +4,44 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal sealed class HttpMethodDictionaryPolicyJumpTable : PolicyJumpTable
{
- internal sealed class HttpMethodDictionaryPolicyJumpTable : PolicyJumpTable
+ private readonly int _exitDestination;
+ private readonly Dictionary<string, int>? _destinations;
+ private readonly int _corsPreflightExitDestination;
+ private readonly Dictionary<string, int>? _corsPreflightDestinations;
+
+ private readonly bool _supportsCorsPreflight;
+
+ public HttpMethodDictionaryPolicyJumpTable(
+ int exitDestination,
+ Dictionary<string, int>? destinations,
+ int corsPreflightExitDestination,
+ Dictionary<string, int>? corsPreflightDestinations)
{
- private readonly int _exitDestination;
- private readonly Dictionary<string, int>? _destinations;
- private readonly int _corsPreflightExitDestination;
- private readonly Dictionary<string, int>? _corsPreflightDestinations;
-
- private readonly bool _supportsCorsPreflight;
-
- public HttpMethodDictionaryPolicyJumpTable(
- int exitDestination,
- Dictionary<string, int>? destinations,
- int corsPreflightExitDestination,
- Dictionary<string, int>? corsPreflightDestinations)
- {
- _exitDestination = exitDestination;
- _destinations = destinations;
- _corsPreflightExitDestination = corsPreflightExitDestination;
- _corsPreflightDestinations = corsPreflightDestinations;
+ _exitDestination = exitDestination;
+ _destinations = destinations;
+ _corsPreflightExitDestination = corsPreflightExitDestination;
+ _corsPreflightDestinations = corsPreflightDestinations;
- _supportsCorsPreflight = _corsPreflightDestinations != null && _corsPreflightDestinations.Count > 0;
- }
+ _supportsCorsPreflight = _corsPreflightDestinations != null && _corsPreflightDestinations.Count > 0;
+ }
- public override int GetDestination(HttpContext httpContext)
+ public override int GetDestination(HttpContext httpContext)
+ {
+ int destination;
+
+ var httpMethod = httpContext.Request.Method;
+ if (_supportsCorsPreflight && HttpMethodMatcherPolicy.IsCorsPreflightRequest(httpContext, httpMethod, out var accessControlRequestMethod))
{
- int destination;
-
- var httpMethod = httpContext.Request.Method;
- if (_supportsCorsPreflight && HttpMethodMatcherPolicy.IsCorsPreflightRequest(httpContext, httpMethod, out var accessControlRequestMethod))
- {
- return _corsPreflightDestinations!.TryGetValue(accessControlRequestMethod.ToString(), out destination)
- ? destination
- : _corsPreflightExitDestination;
- }
-
- return _destinations != null &&
- _destinations.TryGetValue(httpMethod, out destination) ? destination : _exitDestination;
+ return _corsPreflightDestinations!.TryGetValue(accessControlRequestMethod.ToString(), out destination)
+ ? destination
+ : _corsPreflightExitDestination;
}
+
+ return _destinations != null &&
+ _destinations.TryGetValue(httpMethod, out destination) ? destination : _exitDestination;
}
}
diff --git a/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs b/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs
index 199a9a9bb2..6bee324925 100644
--- a/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs
+++ b/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs
@@ -11,517 +11,516 @@ using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// An <see cref="MatcherPolicy"/> that implements filtering and selection by
+/// the HTTP method of a request.
+/// </summary>
+public sealed class HttpMethodMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy, IEndpointSelectorPolicy
{
+ // Used in tests
+ internal static readonly string PreflightHttpMethod = HttpMethods.Options;
+
+ // Used in tests
+ internal const string Http405EndpointDisplayName = "405 HTTP Method Not Supported";
+
+ // Used in tests
+ internal const string AnyMethod = "*";
+
+ /// <summary>
+ /// For framework use only.
+ /// </summary>
+ public IComparer<Endpoint> Comparer => new HttpMethodMetadataEndpointComparer();
+
+ // The order value is chosen to be less than 0, so that it comes before naively
+ // written policies.
/// <summary>
- /// An <see cref="MatcherPolicy"/> that implements filtering and selection by
- /// the HTTP method of a request.
+ /// For framework use only.
/// </summary>
- public sealed class HttpMethodMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy, IEndpointSelectorPolicy
+ public override int Order => -1000;
+
+ bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
- // Used in tests
- internal static readonly string PreflightHttpMethod = HttpMethods.Options;
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- // Used in tests
- internal const string Http405EndpointDisplayName = "405 HTTP Method Not Supported";
+ if (ContainsDynamicEndpoints(endpoints))
+ {
+ return false;
+ }
- // Used in tests
- internal const string AnyMethod = "*";
+ return AppliesToEndpointsCore(endpoints);
+ }
- /// <summary>
- /// For framework use only.
- /// </summary>
- public IComparer<Endpoint> Comparer => new HttpMethodMetadataEndpointComparer();
+ bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- // The order value is chosen to be less than 0, so that it comes before naively
- // written policies.
- /// <summary>
- /// For framework use only.
- /// </summary>
- public override int Order => -1000;
+ // When the node contains dynamic endpoints we can't make any assumptions.
+ return ContainsDynamicEndpoints(endpoints);
+ }
- bool INodeBuilderPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ private static bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
+ {
+ for (var i = 0; i < endpoints.Count; i++)
{
- if (endpoints == null)
+ if (endpoints[i].Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods.Count > 0)
{
- throw new ArgumentNullException(nameof(endpoints));
+ return true;
}
+ }
- if (ContainsDynamicEndpoints(endpoints))
- {
- return false;
- }
+ return false;
+ }
- return AppliesToEndpointsCore(endpoints);
+ /// <summary>
+ /// For framework use only.
+ /// </summary>
+ /// <param name="httpContext"></param>
+ /// <param name="candidates"></param>
+ /// <returns></returns>
+ public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
}
- bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ if (candidates == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
-
- // When the node contains dynamic endpoints we can't make any assumptions.
- return ContainsDynamicEndpoints(endpoints);
+ throw new ArgumentNullException(nameof(candidates));
}
- private static bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
+ // Returning a 405 here requires us to return keep track of all 'seen' HTTP methods. We allocate to
+ // keep track of this because we either need to keep track of the HTTP methods or keep track of the
+ // endpoints - both allocate.
+ //
+ // Those code only runs in the presence of dynamic endpoints anyway.
+ //
+ // We want to return a 405 iff we eliminated ALL of the currently valid endpoints due to HTTP method
+ // mismatch.
+ bool? needs405Endpoint = null;
+ HashSet<string>? methods = null;
+
+ for (var i = 0; i < candidates.Count; i++)
{
- for (var i = 0; i < endpoints.Count; i++)
+ // We do this check first for consistency with how 405 is implemented for the graph version
+ // of this code. We still want to know if any endpoints in this set require an HTTP method
+ // even if those endpoints are already invalid - hence the null-check.
+ var metadata = candidates[i].Endpoint?.Metadata.GetMetadata<IHttpMethodMetadata>();
+ if (metadata == null || metadata.HttpMethods.Count == 0)
{
- if (endpoints[i].Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods.Count > 0)
- {
- return true;
- }
+ // Can match any method.
+ needs405Endpoint = false;
+ continue;
}
- return false;
- }
+ // Saw a valid endpoint.
+ needs405Endpoint = needs405Endpoint ?? true;
- /// <summary>
- /// For framework use only.
- /// </summary>
- /// <param name="httpContext"></param>
- /// <param name="candidates"></param>
- /// <returns></returns>
- public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
- {
- if (httpContext == null)
+ if (!candidates.IsValidCandidate(i))
{
- throw new ArgumentNullException(nameof(httpContext));
+ continue;
}
- if (candidates == null)
+ var httpMethod = httpContext.Request.Method;
+ var headers = httpContext.Request.Headers;
+ if (metadata.AcceptCorsPreflight &&
+ HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
+ headers.ContainsKey(HeaderNames.Origin) &&
+ headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out var accessControlRequestMethod) &&
+ !StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
- throw new ArgumentNullException(nameof(candidates));
+ needs405Endpoint = false; // We don't return a 405 for a CORS preflight request when the endpoints accept CORS preflight.
+ httpMethod = accessControlRequestMethod.ToString();
}
- // Returning a 405 here requires us to return keep track of all 'seen' HTTP methods. We allocate to
- // keep track of this because we either need to keep track of the HTTP methods or keep track of the
- // endpoints - both allocate.
- //
- // Those code only runs in the presence of dynamic endpoints anyway.
- //
- // We want to return a 405 iff we eliminated ALL of the currently valid endpoints due to HTTP method
- // mismatch.
- bool? needs405Endpoint = null;
- HashSet<string>? methods = null;
-
- for (var i = 0; i < candidates.Count; i++)
+ var matched = false;
+ for (var j = 0; j < metadata.HttpMethods.Count; j++)
{
- // We do this check first for consistency with how 405 is implemented for the graph version
- // of this code. We still want to know if any endpoints in this set require an HTTP method
- // even if those endpoints are already invalid - hence the null-check.
- var metadata = candidates[i].Endpoint?.Metadata.GetMetadata<IHttpMethodMetadata>();
- if (metadata == null || metadata.HttpMethods.Count == 0)
+ var candidateMethod = metadata.HttpMethods[j];
+ if (!HttpMethods.Equals(httpMethod, candidateMethod))
{
- // Can match any method.
- needs405Endpoint = false;
+ methods = methods ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ methods.Add(candidateMethod);
continue;
}
- // Saw a valid endpoint.
- needs405Endpoint = needs405Endpoint ?? true;
-
- if (!candidates.IsValidCandidate(i))
- {
- continue;
- }
-
- var httpMethod = httpContext.Request.Method;
- var headers = httpContext.Request.Headers;
- if (metadata.AcceptCorsPreflight &&
- HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
- headers.ContainsKey(HeaderNames.Origin) &&
- headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out var accessControlRequestMethod) &&
- !StringValues.IsNullOrEmpty(accessControlRequestMethod))
- {
- needs405Endpoint = false; // We don't return a 405 for a CORS preflight request when the endpoints accept CORS preflight.
- httpMethod = accessControlRequestMethod.ToString();
- }
-
- var matched = false;
- for (var j = 0; j < metadata.HttpMethods.Count; j++)
- {
- var candidateMethod = metadata.HttpMethods[j];
- if (!HttpMethods.Equals(httpMethod, candidateMethod))
- {
- methods = methods ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- methods.Add(candidateMethod);
- continue;
- }
-
- matched = true;
- needs405Endpoint = false;
- break;
- }
-
- if (!matched)
- {
- candidates.SetValidity(i, false);
- }
+ matched = true;
+ needs405Endpoint = false;
+ break;
}
- if (needs405Endpoint == true)
+ if (!matched)
{
- // We saw some endpoints coming in, and we eliminated them all.
- httpContext.SetEndpoint(CreateRejectionEndpoint(methods!.OrderBy(m => m, StringComparer.OrdinalIgnoreCase)));
- httpContext.Request.RouteValues = null!;
+ candidates.SetValidity(i, false);
}
+ }
- return Task.CompletedTask;
+ if (needs405Endpoint == true)
+ {
+ // We saw some endpoints coming in, and we eliminated them all.
+ httpContext.SetEndpoint(CreateRejectionEndpoint(methods!.OrderBy(m => m, StringComparer.OrdinalIgnoreCase)));
+ httpContext.Request.RouteValues = null!;
}
- /// <summary>
- /// For framework use only.
- /// </summary>
- /// <param name="endpoints"></param>
- /// <returns></returns>
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
+ return Task.CompletedTask;
+ }
+
+ /// <summary>
+ /// For framework use only.
+ /// </summary>
+ /// <param name="endpoints"></param>
+ /// <returns></returns>
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
+ {
+ // The algorithm here is designed to be preserve the order of the endpoints
+ // while also being relatively simple. Preserving order is important.
+
+ // First, build a dictionary of all possible HTTP method/CORS combinations
+ // that exist in this list of endpoints.
+ //
+ // For now we're just building up the set of keys. We don't add any endpoints
+ // to lists now because we don't want ordering problems.
+ var allHttpMethods = new List<string>();
+ var edges = new Dictionary<EdgeKey, List<Endpoint>>();
+ for (var i = 0; i < endpoints.Count; i++)
{
- // The algorithm here is designed to be preserve the order of the endpoints
- // while also being relatively simple. Preserving order is important.
-
- // First, build a dictionary of all possible HTTP method/CORS combinations
- // that exist in this list of endpoints.
- //
- // For now we're just building up the set of keys. We don't add any endpoints
- // to lists now because we don't want ordering problems.
- var allHttpMethods = new List<string>();
- var edges = new Dictionary<EdgeKey, List<Endpoint>>();
- for (var i = 0; i < endpoints.Count; i++)
+ var endpoint = endpoints[i];
+ var (httpMethods, acceptCorsPreFlight) = GetHttpMethods(endpoint);
+
+ // If the action doesn't list HTTP methods then it supports all methods.
+ // In this phase we use a sentinel value to represent the *other* HTTP method
+ // a state that represents any HTTP method that doesn't have a match.
+ if (httpMethods.Count == 0)
{
- var endpoint = endpoints[i];
- var (httpMethods, acceptCorsPreFlight) = GetHttpMethods(endpoint);
+ httpMethods = new[] { AnyMethod, };
+ }
- // If the action doesn't list HTTP methods then it supports all methods.
- // In this phase we use a sentinel value to represent the *other* HTTP method
- // a state that represents any HTTP method that doesn't have a match.
- if (httpMethods.Count == 0)
+ for (var j = 0; j < httpMethods.Count; j++)
+ {
+ // An endpoint that allows CORS reqests will match both CORS and non-CORS
+ // so we model it as both.
+ var httpMethod = httpMethods[j];
+ var key = new EdgeKey(httpMethod, acceptCorsPreFlight);
+ if (!edges.ContainsKey(key))
{
- httpMethods = new[] { AnyMethod, };
+ edges.Add(key, new List<Endpoint>());
}
- for (var j = 0; j < httpMethods.Count; j++)
+ // An endpoint that allows CORS reqests will match both CORS and non-CORS
+ // so we model it as both.
+ if (acceptCorsPreFlight)
{
- // An endpoint that allows CORS reqests will match both CORS and non-CORS
- // so we model it as both.
- var httpMethod = httpMethods[j];
- var key = new EdgeKey(httpMethod, acceptCorsPreFlight);
+ key = new EdgeKey(httpMethod, false);
if (!edges.ContainsKey(key))
{
edges.Add(key, new List<Endpoint>());
}
+ }
- // An endpoint that allows CORS reqests will match both CORS and non-CORS
- // so we model it as both.
- if (acceptCorsPreFlight)
- {
- key = new EdgeKey(httpMethod, false);
- if (!edges.ContainsKey(key))
- {
- edges.Add(key, new List<Endpoint>());
- }
- }
-
- // Also if it's not the *any* method key, then track it.
- if (!string.Equals(AnyMethod, httpMethod, StringComparison.OrdinalIgnoreCase))
+ // Also if it's not the *any* method key, then track it.
+ if (!string.Equals(AnyMethod, httpMethod, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!ContainsHttpMethod(allHttpMethods, httpMethod))
{
- if (!ContainsHttpMethod(allHttpMethods, httpMethod))
- {
- allHttpMethods.Add(httpMethod);
- }
+ allHttpMethods.Add(httpMethod);
}
}
}
+ }
- allHttpMethods.Sort(StringComparer.OrdinalIgnoreCase);
+ allHttpMethods.Sort(StringComparer.OrdinalIgnoreCase);
- // Now in a second loop, add endpoints to these lists. We've enumerated all of
- // the states, so we want to see which states this endpoint matches.
- for (var i = 0; i < endpoints.Count; i++)
- {
- var endpoint = endpoints[i];
- var (httpMethods, acceptCorsPreFlight) = GetHttpMethods(endpoint);
+ // Now in a second loop, add endpoints to these lists. We've enumerated all of
+ // the states, so we want to see which states this endpoint matches.
+ for (var i = 0; i < endpoints.Count; i++)
+ {
+ var endpoint = endpoints[i];
+ var (httpMethods, acceptCorsPreFlight) = GetHttpMethods(endpoint);
- if (httpMethods.Count == 0)
+ if (httpMethods.Count == 0)
+ {
+ // OK this means that this endpoint matches *all* HTTP methods.
+ // So, loop and add it to all states.
+ foreach (var kvp in edges)
{
- // OK this means that this endpoint matches *all* HTTP methods.
- // So, loop and add it to all states.
- foreach (var kvp in edges)
+ if (acceptCorsPreFlight || !kvp.Key.IsCorsPreflightRequest)
{
- if (acceptCorsPreFlight || !kvp.Key.IsCorsPreflightRequest)
- {
- kvp.Value.Add(endpoint);
- }
+ kvp.Value.Add(endpoint);
}
}
- else
+ }
+ else
+ {
+ // OK this endpoint matches specific methods.
+ for (var j = 0; j < httpMethods.Count; j++)
{
- // OK this endpoint matches specific methods.
- for (var j = 0; j < httpMethods.Count; j++)
- {
- var httpMethod = httpMethods[j];
- var key = new EdgeKey(httpMethod, acceptCorsPreFlight);
+ var httpMethod = httpMethods[j];
+ var key = new EdgeKey(httpMethod, acceptCorsPreFlight);
- edges[key].Add(endpoint);
+ edges[key].Add(endpoint);
- // An endpoint that allows CORS reqests will match both CORS and non-CORS
- // so we model it as both.
- if (acceptCorsPreFlight)
- {
- key = new EdgeKey(httpMethod, false);
- edges[key].Add(endpoint);
- }
+ // An endpoint that allows CORS reqests will match both CORS and non-CORS
+ // so we model it as both.
+ if (acceptCorsPreFlight)
+ {
+ key = new EdgeKey(httpMethod, false);
+ edges[key].Add(endpoint);
}
}
}
+ }
- // Adds a very low priority endpoint that will reject the request with
- // a 405 if nothing else can handle this verb. This is only done if
- // no other actions exist that handle the 'all verbs'.
- //
- // The rationale for this is that we want to report a 405 if none of
- // the supported methods match, but we don't want to report a 405 in a
- // case where an application defines an endpoint that handles all verbs, but
- // a constraint rejects the request, or a complex segment fails to parse. We
- // consider a case like that a 'user input validation' failure rather than
- // a semantic violation of HTTP.
- //
- // This will make 405 much more likely in API-focused applications, and somewhat
- // unlikely in a traditional MVC application. That's good.
- //
- // We don't bother returning a 405 when the CORS preflight method doesn't exist.
- // The developer calling the API will see it as a CORS error, which is fine because
- // there isn't an endpoint to check for a CORS policy.
- if (!edges.TryGetValue(new EdgeKey(AnyMethod, false), out var matches))
- {
- // Methods sorted for testability.
- var endpoint = CreateRejectionEndpoint(allHttpMethods);
- matches = new List<Endpoint>() { endpoint, };
- edges[new EdgeKey(AnyMethod, false)] = matches;
- }
+ // Adds a very low priority endpoint that will reject the request with
+ // a 405 if nothing else can handle this verb. This is only done if
+ // no other actions exist that handle the 'all verbs'.
+ //
+ // The rationale for this is that we want to report a 405 if none of
+ // the supported methods match, but we don't want to report a 405 in a
+ // case where an application defines an endpoint that handles all verbs, but
+ // a constraint rejects the request, or a complex segment fails to parse. We
+ // consider a case like that a 'user input validation' failure rather than
+ // a semantic violation of HTTP.
+ //
+ // This will make 405 much more likely in API-focused applications, and somewhat
+ // unlikely in a traditional MVC application. That's good.
+ //
+ // We don't bother returning a 405 when the CORS preflight method doesn't exist.
+ // The developer calling the API will see it as a CORS error, which is fine because
+ // there isn't an endpoint to check for a CORS policy.
+ if (!edges.TryGetValue(new EdgeKey(AnyMethod, false), out var matches))
+ {
+ // Methods sorted for testability.
+ var endpoint = CreateRejectionEndpoint(allHttpMethods);
+ matches = new List<Endpoint>() { endpoint, };
+ edges[new EdgeKey(AnyMethod, false)] = matches;
+ }
- var policyNodeEdges = new PolicyNodeEdge[edges.Count];
- var index = 0;
- foreach (var kvp in edges)
- {
- policyNodeEdges[index++] = new PolicyNodeEdge(kvp.Key, kvp.Value);
- }
+ var policyNodeEdges = new PolicyNodeEdge[edges.Count];
+ var index = 0;
+ foreach (var kvp in edges)
+ {
+ policyNodeEdges[index++] = new PolicyNodeEdge(kvp.Key, kvp.Value);
+ }
- return policyNodeEdges;
+ return policyNodeEdges;
- (IReadOnlyList<string> httpMethods, bool acceptCorsPreflight) GetHttpMethods(Endpoint e)
- {
- var metadata = e.Metadata.GetMetadata<IHttpMethodMetadata>();
- return metadata == null ? (Array.Empty<string>(), false) : (metadata.HttpMethods, metadata.AcceptCorsPreflight);
- }
+ (IReadOnlyList<string> httpMethods, bool acceptCorsPreflight) GetHttpMethods(Endpoint e)
+ {
+ var metadata = e.Metadata.GetMetadata<IHttpMethodMetadata>();
+ return metadata == null ? (Array.Empty<string>(), false) : (metadata.HttpMethods, metadata.AcceptCorsPreflight);
}
+ }
- /// <summary>
- /// For framework use only.
- /// </summary>
- /// <param name="exitDestination"></param>
- /// <param name="edges"></param>
- /// <returns></returns>
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
+ /// <summary>
+ /// For framework use only.
+ /// </summary>
+ /// <param name="exitDestination"></param>
+ /// <param name="edges"></param>
+ /// <returns></returns>
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
+ {
+ Dictionary<string, int>? destinations = null;
+ Dictionary<string, int>? corsPreflightDestinations = null;
+ for (var i = 0; i < edges.Count; i++)
{
- Dictionary<string, int>? destinations = null;
- Dictionary<string, int>? corsPreflightDestinations = null;
- for (var i = 0; i < edges.Count; i++)
+ // We create this data, so it's safe to cast it.
+ var key = (EdgeKey)edges[i].State;
+ if (key.IsCorsPreflightRequest)
{
- // We create this data, so it's safe to cast it.
- var key = (EdgeKey)edges[i].State;
- if (key.IsCorsPreflightRequest)
+ if (corsPreflightDestinations == null)
{
- if (corsPreflightDestinations == null)
- {
- corsPreflightDestinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
- }
-
- corsPreflightDestinations.Add(key.HttpMethod, edges[i].Destination);
- }
- else
- {
- if (destinations == null)
- {
- destinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
- }
-
- destinations.Add(key.HttpMethod, edges[i].Destination);
+ corsPreflightDestinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
}
- }
- int corsPreflightExitDestination = exitDestination;
- if (corsPreflightDestinations != null && corsPreflightDestinations.TryGetValue(AnyMethod, out var matchesAnyVerb))
- {
- // If we have endpoints that match any HTTP method, use that as the exit.
- corsPreflightExitDestination = matchesAnyVerb;
- corsPreflightDestinations.Remove(AnyMethod);
+ corsPreflightDestinations.Add(key.HttpMethod, edges[i].Destination);
}
-
- if (destinations != null && destinations.TryGetValue(AnyMethod, out matchesAnyVerb))
- {
- // If we have endpoints that match any HTTP method, use that as the exit.
- exitDestination = matchesAnyVerb;
- destinations.Remove(AnyMethod);
- }
-
- if (destinations?.Count == 1)
+ else
{
- // If there is only a single valid HTTP method then use an optimized jump table.
- // It avoids unnecessary dictionary lookups with the method name.
- var httpMethodDestination = destinations.Single();
- var method = httpMethodDestination.Key;
- var destination = httpMethodDestination.Value;
- var supportsCorsPreflight = false;
- var corsPreflightDestination = 0;
-
- if (corsPreflightDestinations?.Count > 0)
+ if (destinations == null)
{
- supportsCorsPreflight = true;
- corsPreflightDestination = corsPreflightDestinations.Single().Value;
+ destinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
}
- return new HttpMethodSingleEntryPolicyJumpTable(
- exitDestination,
- method,
- destination,
- supportsCorsPreflight,
- corsPreflightExitDestination,
- corsPreflightDestination);
+ destinations.Add(key.HttpMethod, edges[i].Destination);
}
- else
+ }
+
+ int corsPreflightExitDestination = exitDestination;
+ if (corsPreflightDestinations != null && corsPreflightDestinations.TryGetValue(AnyMethod, out var matchesAnyVerb))
+ {
+ // If we have endpoints that match any HTTP method, use that as the exit.
+ corsPreflightExitDestination = matchesAnyVerb;
+ corsPreflightDestinations.Remove(AnyMethod);
+ }
+
+ if (destinations != null && destinations.TryGetValue(AnyMethod, out matchesAnyVerb))
+ {
+ // If we have endpoints that match any HTTP method, use that as the exit.
+ exitDestination = matchesAnyVerb;
+ destinations.Remove(AnyMethod);
+ }
+
+ if (destinations?.Count == 1)
+ {
+ // If there is only a single valid HTTP method then use an optimized jump table.
+ // It avoids unnecessary dictionary lookups with the method name.
+ var httpMethodDestination = destinations.Single();
+ var method = httpMethodDestination.Key;
+ var destination = httpMethodDestination.Value;
+ var supportsCorsPreflight = false;
+ var corsPreflightDestination = 0;
+
+ if (corsPreflightDestinations?.Count > 0)
{
- return new HttpMethodDictionaryPolicyJumpTable(
- exitDestination,
- destinations,
- corsPreflightExitDestination,
- corsPreflightDestinations);
+ supportsCorsPreflight = true;
+ corsPreflightDestination = corsPreflightDestinations.Single().Value;
}
- }
- private static Endpoint CreateRejectionEndpoint(IEnumerable<string> httpMethods)
+ return new HttpMethodSingleEntryPolicyJumpTable(
+ exitDestination,
+ method,
+ destination,
+ supportsCorsPreflight,
+ corsPreflightExitDestination,
+ corsPreflightDestination);
+ }
+ else
{
- var allow = string.Join(", ", httpMethods);
- return new Endpoint(
- (context) =>
- {
- context.Response.StatusCode = 405;
+ return new HttpMethodDictionaryPolicyJumpTable(
+ exitDestination,
+ destinations,
+ corsPreflightExitDestination,
+ corsPreflightDestinations);
+ }
+ }
+
+ private static Endpoint CreateRejectionEndpoint(IEnumerable<string> httpMethods)
+ {
+ var allow = string.Join(", ", httpMethods);
+ return new Endpoint(
+ (context) =>
+ {
+ context.Response.StatusCode = 405;
// Prevent ArgumentException from duplicate key if header already added, such as when the
// request is re-executed by an error handler (see https://github.com/dotnet/aspnetcore/issues/6415)
context.Response.Headers.Allow = allow;
- return Task.CompletedTask;
- },
- EndpointMetadataCollection.Empty,
- Http405EndpointDisplayName);
- }
+ return Task.CompletedTask;
+ },
+ EndpointMetadataCollection.Empty,
+ Http405EndpointDisplayName);
+ }
- private static bool ContainsHttpMethod(List<string> httpMethods, string httpMethod)
+ private static bool ContainsHttpMethod(List<string> httpMethods, string httpMethod)
+ {
+ var methods = CollectionsMarshal.AsSpan(httpMethods);
+ for (var i = 0; i < methods.Length; i++)
{
- var methods = CollectionsMarshal.AsSpan(httpMethods);
- for (var i = 0; i < methods.Length; i++)
+ // This is a fast path for when everything is using static HttpMethods instances.
+ if (object.ReferenceEquals(methods[i], httpMethod))
{
- // This is a fast path for when everything is using static HttpMethods instances.
- if (object.ReferenceEquals(methods[i], httpMethod))
- {
- return true;
- }
+ return true;
}
+ }
- for (var i = 0; i < methods.Length; i++)
+ for (var i = 0; i < methods.Length; i++)
+ {
+ if (HttpMethods.Equals(methods[i], httpMethod))
{
- if (HttpMethods.Equals(methods[i], httpMethod))
- {
- return true;
- }
+ return true;
}
-
- return false;
}
- internal static bool IsCorsPreflightRequest(HttpContext httpContext, string httpMethod, out StringValues accessControlRequestMethod)
- {
- accessControlRequestMethod = default;
- var headers = httpContext.Request.Headers;
+ return false;
+ }
- return HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
- headers.ContainsKey(HeaderNames.Origin) &&
- headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out accessControlRequestMethod) &&
- !StringValues.IsNullOrEmpty(accessControlRequestMethod);
- }
+ internal static bool IsCorsPreflightRequest(HttpContext httpContext, string httpMethod, out StringValues accessControlRequestMethod)
+ {
+ accessControlRequestMethod = default;
+ var headers = httpContext.Request.Headers;
+
+ return HttpMethods.Equals(httpMethod, PreflightHttpMethod) &&
+ headers.ContainsKey(HeaderNames.Origin) &&
+ headers.TryGetValue(HeaderNames.AccessControlRequestMethod, out accessControlRequestMethod) &&
+ !StringValues.IsNullOrEmpty(accessControlRequestMethod);
+ }
- private class HttpMethodMetadataEndpointComparer : EndpointMetadataComparer<IHttpMethodMetadata>
+ private class HttpMethodMetadataEndpointComparer : EndpointMetadataComparer<IHttpMethodMetadata>
+ {
+ protected override int CompareMetadata(IHttpMethodMetadata? x, IHttpMethodMetadata? y)
{
- protected override int CompareMetadata(IHttpMethodMetadata? x, IHttpMethodMetadata? y)
- {
- // Ignore the metadata if it has an empty list of HTTP methods.
- return base.CompareMetadata(
- x?.HttpMethods.Count > 0 ? x : null,
- y?.HttpMethods.Count > 0 ? y : null);
- }
+ // Ignore the metadata if it has an empty list of HTTP methods.
+ return base.CompareMetadata(
+ x?.HttpMethods.Count > 0 ? x : null,
+ y?.HttpMethods.Count > 0 ? y : null);
}
+ }
+
+ internal readonly struct EdgeKey : IEquatable<EdgeKey>, IComparable<EdgeKey>, IComparable
+ {
+ // Note that in contrast with the metadata, the edge represents a possible state change
+ // rather than a list of what's allowed. We represent CORS and non-CORS requests as separate
+ // states.
+ public readonly bool IsCorsPreflightRequest;
+ public readonly string HttpMethod;
- internal readonly struct EdgeKey : IEquatable<EdgeKey>, IComparable<EdgeKey>, IComparable
+ public EdgeKey(string httpMethod, bool isCorsPreflightRequest)
{
- // Note that in contrast with the metadata, the edge represents a possible state change
- // rather than a list of what's allowed. We represent CORS and non-CORS requests as separate
- // states.
- public readonly bool IsCorsPreflightRequest;
- public readonly string HttpMethod;
+ HttpMethod = httpMethod;
+ IsCorsPreflightRequest = isCorsPreflightRequest;
+ }
- public EdgeKey(string httpMethod, bool isCorsPreflightRequest)
+ // These are comparable so they can be sorted in tests.
+ public int CompareTo(EdgeKey other)
+ {
+ var compare = string.Compare(HttpMethod, other.HttpMethod, StringComparison.Ordinal);
+ if (compare != 0)
{
- HttpMethod = httpMethod;
- IsCorsPreflightRequest = isCorsPreflightRequest;
+ return compare;
}
- // These are comparable so they can be sorted in tests.
- public int CompareTo(EdgeKey other)
- {
- var compare = string.Compare(HttpMethod, other.HttpMethod, StringComparison.Ordinal);
- if (compare != 0)
- {
- return compare;
- }
-
- return IsCorsPreflightRequest.CompareTo(other.IsCorsPreflightRequest);
- }
+ return IsCorsPreflightRequest.CompareTo(other.IsCorsPreflightRequest);
+ }
- public int CompareTo(object? obj)
- {
- return CompareTo((EdgeKey)obj!);
- }
+ public int CompareTo(object? obj)
+ {
+ return CompareTo((EdgeKey)obj!);
+ }
- public bool Equals(EdgeKey other)
- {
- return
- IsCorsPreflightRequest == other.IsCorsPreflightRequest &&
- HttpMethods.Equals(HttpMethod, other.HttpMethod);
- }
+ public bool Equals(EdgeKey other)
+ {
+ return
+ IsCorsPreflightRequest == other.IsCorsPreflightRequest &&
+ HttpMethods.Equals(HttpMethod, other.HttpMethod);
+ }
- public override bool Equals(object? obj)
- {
- var other = obj as EdgeKey?;
- return other == null ? false : Equals(other.Value);
- }
+ public override bool Equals(object? obj)
+ {
+ var other = obj as EdgeKey?;
+ return other == null ? false : Equals(other.Value);
+ }
- public override int GetHashCode()
- {
- var hash = new HashCode();
- hash.Add(IsCorsPreflightRequest ? 1 : 0);
- hash.Add(HttpMethod, StringComparer.Ordinal);
- return hash.ToHashCode();
- }
+ public override int GetHashCode()
+ {
+ var hash = new HashCode();
+ hash.Add(IsCorsPreflightRequest ? 1 : 0);
+ hash.Add(HttpMethod, StringComparer.Ordinal);
+ return hash.ToHashCode();
+ }
- // Used in GraphViz output.
- public override string ToString()
- {
- return IsCorsPreflightRequest ? $"CORS: {HttpMethod}" : $"HTTP: {HttpMethod}";
- }
+ // Used in GraphViz output.
+ public override string ToString()
+ {
+ return IsCorsPreflightRequest ? $"CORS: {HttpMethod}" : $"HTTP: {HttpMethod}";
}
}
}
diff --git a/src/Http/Routing/src/Matching/HttpMethodSingleEntryPolicyJumpTable.cs b/src/Http/Routing/src/Matching/HttpMethodSingleEntryPolicyJumpTable.cs
index 52b1607d0d..75e03b76bd 100644
--- a/src/Http/Routing/src/Matching/HttpMethodSingleEntryPolicyJumpTable.cs
+++ b/src/Http/Routing/src/Matching/HttpMethodSingleEntryPolicyJumpTable.cs
@@ -3,43 +3,42 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal sealed class HttpMethodSingleEntryPolicyJumpTable : PolicyJumpTable
{
- internal sealed class HttpMethodSingleEntryPolicyJumpTable : PolicyJumpTable
- {
- private readonly int _exitDestination;
- private readonly string _method;
- private readonly int _destination;
- private readonly int _corsPreflightExitDestination;
- private readonly int _corsPreflightDestination;
+ private readonly int _exitDestination;
+ private readonly string _method;
+ private readonly int _destination;
+ private readonly int _corsPreflightExitDestination;
+ private readonly int _corsPreflightDestination;
- private readonly bool _supportsCorsPreflight;
+ private readonly bool _supportsCorsPreflight;
- public HttpMethodSingleEntryPolicyJumpTable(
- int exitDestination,
- string method,
- int destination,
- bool supportsCorsPreflight,
- int corsPreflightExitDestination,
- int corsPreflightDestination)
- {
- _exitDestination = exitDestination;
- _method = method;
- _destination = destination;
- _supportsCorsPreflight = supportsCorsPreflight;
- _corsPreflightExitDestination = corsPreflightExitDestination;
- _corsPreflightDestination = corsPreflightDestination;
- }
+ public HttpMethodSingleEntryPolicyJumpTable(
+ int exitDestination,
+ string method,
+ int destination,
+ bool supportsCorsPreflight,
+ int corsPreflightExitDestination,
+ int corsPreflightDestination)
+ {
+ _exitDestination = exitDestination;
+ _method = method;
+ _destination = destination;
+ _supportsCorsPreflight = supportsCorsPreflight;
+ _corsPreflightExitDestination = corsPreflightExitDestination;
+ _corsPreflightDestination = corsPreflightDestination;
+ }
- public override int GetDestination(HttpContext httpContext)
+ public override int GetDestination(HttpContext httpContext)
+ {
+ var httpMethod = httpContext.Request.Method;
+ if (_supportsCorsPreflight && HttpMethodMatcherPolicy.IsCorsPreflightRequest(httpContext, httpMethod, out var accessControlRequestMethod))
{
- var httpMethod = httpContext.Request.Method;
- if (_supportsCorsPreflight && HttpMethodMatcherPolicy.IsCorsPreflightRequest(httpContext, httpMethod, out var accessControlRequestMethod))
- {
- return HttpMethods.Equals(accessControlRequestMethod.ToString(), _method) ? _corsPreflightDestination : _corsPreflightExitDestination;
- }
-
- return HttpMethods.Equals(httpMethod, _method) ? _destination : _exitDestination;
+ return HttpMethods.Equals(accessControlRequestMethod.ToString(), _method) ? _corsPreflightDestination : _corsPreflightExitDestination;
}
+
+ return HttpMethods.Equals(httpMethod, _method) ? _destination : _exitDestination;
}
}
diff --git a/src/Http/Routing/src/Matching/IEndpointComparerPolicy.cs b/src/Http/Routing/src/Matching/IEndpointComparerPolicy.cs
index 9aec53af23..a6bb7c0848 100644
--- a/src/Http/Routing/src/Matching/IEndpointComparerPolicy.cs
+++ b/src/Http/Routing/src/Matching/IEndpointComparerPolicy.cs
@@ -4,32 +4,31 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// A <see cref="MatcherPolicy"/> interface that can be implemented to sort
+/// endpoints. Implementations of <see cref="IEndpointComparerPolicy"/> must
+/// inherit from <see cref="MatcherPolicy"/> and should be registered in
+/// the dependency injection container as singleton services of type <see cref="MatcherPolicy"/>.
+/// </summary>
+/// <remarks>
+/// <para>
+/// Candidates in a <see cref="CandidateSet"/> are sorted based on their priority. Defining
+/// a <see cref="IEndpointComparerPolicy"/> adds an additional criterion to the sorting
+/// operation used to order candidates.
+/// </para>
+/// <para>
+/// As an example, the implementation of <see cref="HttpMethodMatcherPolicy"/> implements
+/// <see cref="IEndpointComparerPolicy"/> to ensure that endpoints matching specific HTTP
+/// methods are sorted with a higher priority than endpoints without a specific HTTP method
+/// requirement.
+/// </para>
+/// </remarks>
+public interface IEndpointComparerPolicy
{
/// <summary>
- /// A <see cref="MatcherPolicy"/> interface that can be implemented to sort
- /// endpoints. Implementations of <see cref="IEndpointComparerPolicy"/> must
- /// inherit from <see cref="MatcherPolicy"/> and should be registered in
- /// the dependency injection container as singleton services of type <see cref="MatcherPolicy"/>.
+ /// Gets an <see cref="IComparer{Endpoint}"/> that will be used to sort the endpoints.
/// </summary>
- /// <remarks>
- /// <para>
- /// Candidates in a <see cref="CandidateSet"/> are sorted based on their priority. Defining
- /// a <see cref="IEndpointComparerPolicy"/> adds an additional criterion to the sorting
- /// operation used to order candidates.
- /// </para>
- /// <para>
- /// As an example, the implementation of <see cref="HttpMethodMatcherPolicy"/> implements
- /// <see cref="IEndpointComparerPolicy"/> to ensure that endpoints matching specific HTTP
- /// methods are sorted with a higher priority than endpoints without a specific HTTP method
- /// requirement.
- /// </para>
- /// </remarks>
- public interface IEndpointComparerPolicy
- {
- /// <summary>
- /// Gets an <see cref="IComparer{Endpoint}"/> that will be used to sort the endpoints.
- /// </summary>
- IComparer<Endpoint> Comparer { get; }
- }
+ IComparer<Endpoint> Comparer { get; }
}
diff --git a/src/Http/Routing/src/Matching/IEndpointSelectorPolicy.cs b/src/Http/Routing/src/Matching/IEndpointSelectorPolicy.cs
index 2698280e2d..006e5d5108 100644
--- a/src/Http/Routing/src/Matching/IEndpointSelectorPolicy.cs
+++ b/src/Http/Routing/src/Matching/IEndpointSelectorPolicy.cs
@@ -1,50 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Matching
+/// <summary>
+/// A <see cref="MatcherPolicy"/> interface that can implemented to filter endpoints
+/// in a <see cref="CandidateSet"/>. Implementations of <see cref="IEndpointSelectorPolicy"/> must
+/// inherit from <see cref="MatcherPolicy"/> and should be registered in
+/// the dependency injection container as singleton services of type <see cref="MatcherPolicy"/>.
+/// </summary>
+public interface IEndpointSelectorPolicy
{
/// <summary>
- /// A <see cref="MatcherPolicy"/> interface that can implemented to filter endpoints
- /// in a <see cref="CandidateSet"/>. Implementations of <see cref="IEndpointSelectorPolicy"/> must
- /// inherit from <see cref="MatcherPolicy"/> and should be registered in
- /// the dependency injection container as singleton services of type <see cref="MatcherPolicy"/>.
+ /// Returns a value that indicates whether the <see cref="IEndpointSelectorPolicy"/> applies
+ /// to any endpoint in <paramref name="endpoints"/>.
/// </summary>
- public interface IEndpointSelectorPolicy
- {
- /// <summary>
- /// Returns a value that indicates whether the <see cref="IEndpointSelectorPolicy"/> applies
- /// to any endpoint in <paramref name="endpoints"/>.
- /// </summary>
- /// <param name="endpoints">The set of candidate <see cref="Endpoint"/> values.</param>
- /// <returns>
- /// <c>true</c> if the policy applies to any endpoint in <paramref name="endpoints"/>, otherwise <c>false</c>.
- /// </returns>
- bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints);
+ /// <param name="endpoints">The set of candidate <see cref="Endpoint"/> values.</param>
+ /// <returns>
+ /// <c>true</c> if the policy applies to any endpoint in <paramref name="endpoints"/>, otherwise <c>false</c>.
+ /// </returns>
+ bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints);
- /// <summary>
- /// Applies the policy to the <see cref="CandidateSet"/>.
- /// </summary>
- /// <param name="httpContext">
- /// The <see cref="HttpContext"/> associated with the current request.
- /// </param>
- /// <param name="candidates">The <see cref="CandidateSet"/>.</param>
- /// <remarks>
- /// <para>
- /// Implementations of <see cref="IEndpointSelectorPolicy"/> should implement this method
- /// and filter the set of candidates in the <paramref name="candidates"/> by setting
- /// <see cref="CandidateSet.SetValidity(int, bool)"/> to <c>false</c> where desired.
- /// </para>
- /// <para>
- /// To signal an error condition, the <see cref="IEndpointSelectorPolicy"/> should assign the endpoint by
- /// calling <see cref="EndpointHttpContextExtensions.SetEndpoint(HttpContext, Endpoint)"/>
- /// and setting <see cref="HttpRequest.RouteValues"/> to an
- /// <see cref="Endpoint"/> value that will produce the desired error when executed.
- /// </para>
- /// </remarks>
- Task ApplyAsync(HttpContext httpContext, CandidateSet candidates);
- }
+ /// <summary>
+ /// Applies the policy to the <see cref="CandidateSet"/>.
+ /// </summary>
+ /// <param name="httpContext">
+ /// The <see cref="HttpContext"/> associated with the current request.
+ /// </param>
+ /// <param name="candidates">The <see cref="CandidateSet"/>.</param>
+ /// <remarks>
+ /// <para>
+ /// Implementations of <see cref="IEndpointSelectorPolicy"/> should implement this method
+ /// and filter the set of candidates in the <paramref name="candidates"/> by setting
+ /// <see cref="CandidateSet.SetValidity(int, bool)"/> to <c>false</c> where desired.
+ /// </para>
+ /// <para>
+ /// To signal an error condition, the <see cref="IEndpointSelectorPolicy"/> should assign the endpoint by
+ /// calling <see cref="EndpointHttpContextExtensions.SetEndpoint(HttpContext, Endpoint)"/>
+ /// and setting <see cref="HttpRequest.RouteValues"/> to an
+ /// <see cref="Endpoint"/> value that will produce the desired error when executed.
+ /// </para>
+ /// </remarks>
+ Task ApplyAsync(HttpContext httpContext, CandidateSet candidates);
}
diff --git a/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs b/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs
index 5f17adbe71..2334256dfe 100644
--- a/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs
+++ b/src/Http/Routing/src/Matching/ILEmitTrieFactory.cs
@@ -11,388 +11,388 @@ using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal static class ILEmitTrieFactory
{
- internal static class ILEmitTrieFactory
+ // The algorthm we use only works for ASCII text. If we find non-ASCII text in the input
+ // we need to reject it and let is be processed with a fallback technique.
+ public const int NotAscii = int.MinValue;
+
+ // Creates a Func of (string path, int start, int length) => destination
+ // Not using PathSegment here because we don't want to mess with visibility checks and
+ // generating IL without it is easier.
+ public static Func<string, int, int, int> Create(
+ int defaultDestination,
+ int exitDestination,
+ (string text, int destination)[] entries,
+ bool? vectorize)
{
- // The algorthm we use only works for ASCII text. If we find non-ASCII text in the input
- // we need to reject it and let is be processed with a fallback technique.
- public const int NotAscii = int.MinValue;
-
- // Creates a Func of (string path, int start, int length) => destination
- // Not using PathSegment here because we don't want to mess with visibility checks and
- // generating IL without it is easier.
- public static Func<string, int, int, int> Create(
- int defaultDestination,
- int exitDestination,
- (string text, int destination)[] entries,
- bool? vectorize)
- {
- var method = new DynamicMethod(
- "GetDestination",
- typeof(int),
- new[] { typeof(string), typeof(int), typeof(int), });
+ var method = new DynamicMethod(
+ "GetDestination",
+ typeof(int),
+ new[] { typeof(string), typeof(int), typeof(int), });
- GenerateMethodBody(method.GetILGenerator(), defaultDestination, exitDestination, entries, vectorize);
+ GenerateMethodBody(method.GetILGenerator(), defaultDestination, exitDestination, entries, vectorize);
#if IL_EMIT_SAVE_ASSEMBLY
SaveAssembly(method.GetILGenerator(), defaultDestination, exitDestination, entries, vectorize);
#endif
- return (Func<string, int, int, int>)method.CreateDelegate(typeof(Func<string, int, int, int>));
- }
-
- // Internal for testing
- internal static bool ShouldVectorize((string text, int destination)[] entries)
- {
- // There's no value in vectorizing the computation if we're on 32bit or
- // if no string is long enough. We do the vectorized comparison with uint64 ulongs
- // which isn't beneficial if they don't map to the native size of the CPU. The
- // vectorized algorithm introduces additional overhead for casing.
-
- // Vectorize by default on 64bit (allow override for testing)
- return (IntPtr.Size == 8) &&
-
- // Don't vectorize if all of the strings are small (prevents allocating unused locals)
- entries.Any(e => e.text.Length >= 4);
- }
+ return (Func<string, int, int, int>)method.CreateDelegate(typeof(Func<string, int, int, int>));
+ }
- private static void GenerateMethodBody(
- ILGenerator il,
- int defaultDestination,
- int exitDestination,
- (string text, int destination)[] entries,
- bool? vectorize)
- {
+ // Internal for testing
+ internal static bool ShouldVectorize((string text, int destination)[] entries)
+ {
+ // There's no value in vectorizing the computation if we're on 32bit or
+ // if no string is long enough. We do the vectorized comparison with uint64 ulongs
+ // which isn't beneficial if they don't map to the native size of the CPU. The
+ // vectorized algorithm introduces additional overhead for casing.
- vectorize = vectorize ?? ShouldVectorize(entries);
+ // Vectorize by default on 64bit (allow override for testing)
+ return (IntPtr.Size == 8) &&
- // See comments on Locals for details
- var locals = new Locals(il, vectorize.Value);
+ // Don't vectorize if all of the strings are small (prevents allocating unused locals)
+ entries.Any(e => e.text.Length >= 4);
+ }
- // See comments on Labels for details
- var labels = new Labels()
- {
- ReturnDefault = il.DefineLabel(),
- ReturnNotAscii = il.DefineLabel(),
- };
+ private static void GenerateMethodBody(
+ ILGenerator il,
+ int defaultDestination,
+ int exitDestination,
+ (string text, int destination)[] entries,
+ bool? vectorize)
+ {
- // See comments on Methods for details
- var methods = Methods.Instance;
+ vectorize = vectorize ?? ShouldVectorize(entries);
- // Initializing top-level locals - this is similar to...
- // ReadOnlySpan<char> span = arg0.AsSpan(arg1, arg2);
- // ref byte p = ref Unsafe.As<char, byte>(MemoryMarshal.GetReference<char>(span))
+ // See comments on Locals for details
+ var locals = new Locals(il, vectorize.Value);
- // arg0.AsSpan(arg1, arg2)
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldarg_1);
- il.Emit(OpCodes.Ldarg_2);
- il.Emit(OpCodes.Call, methods.AsSpan);
+ // See comments on Labels for details
+ var labels = new Labels()
+ {
+ ReturnDefault = il.DefineLabel(),
+ ReturnNotAscii = il.DefineLabel(),
+ };
- // ReadOnlySpan<char> = ...
- il.Emit(OpCodes.Stloc, locals.Span);
+ // See comments on Methods for details
+ var methods = Methods.Instance;
- // MemoryMarshal.GetReference<char>(span)
- il.Emit(OpCodes.Ldloc, locals.Span);
- il.Emit(OpCodes.Call, methods.GetReference);
+ // Initializing top-level locals - this is similar to...
+ // ReadOnlySpan<char> span = arg0.AsSpan(arg1, arg2);
+ // ref byte p = ref Unsafe.As<char, byte>(MemoryMarshal.GetReference<char>(span))
- // Unsafe.As<char, byte>(...)
- il.Emit(OpCodes.Call, methods.As);
+ // arg0.AsSpan(arg1, arg2)
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, methods.AsSpan);
- // ref byte p = ...
- il.Emit(OpCodes.Stloc_0, locals.P);
+ // ReadOnlySpan<char> = ...
+ il.Emit(OpCodes.Stloc, locals.Span);
- var groups = entries.GroupBy(e => e.text.Length).ToArray();
- for (var i = 0; i < groups.Length; i++)
- {
- var group = groups[i];
+ // MemoryMarshal.GetReference<char>(span)
+ il.Emit(OpCodes.Ldloc, locals.Span);
+ il.Emit(OpCodes.Call, methods.GetReference);
- // Similar to 'if (length != X) { ... }
- var inside = il.DefineLabel();
- var next = il.DefineLabel();
- il.Emit(OpCodes.Ldarg_2);
- il.Emit(OpCodes.Ldc_I4, group.Key);
- il.Emit(OpCodes.Beq, inside);
- il.Emit(OpCodes.Br, next);
+ // Unsafe.As<char, byte>(...)
+ il.Emit(OpCodes.Call, methods.As);
- // Process the group
- il.MarkLabel(inside);
- EmitTable(il, group.ToArray(), 0, group.Key, locals, labels, methods);
- il.MarkLabel(next);
- }
+ // ref byte p = ...
+ il.Emit(OpCodes.Stloc_0, locals.P);
- // Exit point - we end up here when the text doesn't match
- il.MarkLabel(labels.ReturnDefault);
- il.Emit(OpCodes.Ldc_I4, defaultDestination);
- il.Emit(OpCodes.Ret);
+ var groups = entries.GroupBy(e => e.text.Length).ToArray();
+ for (var i = 0; i < groups.Length; i++)
+ {
+ var group = groups[i];
- // Exit point - we end up here with the text contains non-ASCII text
- il.MarkLabel(labels.ReturnNotAscii);
- il.Emit(OpCodes.Ldc_I4, NotAscii);
- il.Emit(OpCodes.Ret);
+ // Similar to 'if (length != X) { ... }
+ var inside = il.DefineLabel();
+ var next = il.DefineLabel();
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Ldc_I4, group.Key);
+ il.Emit(OpCodes.Beq, inside);
+ il.Emit(OpCodes.Br, next);
+
+ // Process the group
+ il.MarkLabel(inside);
+ EmitTable(il, group.ToArray(), 0, group.Key, locals, labels, methods);
+ il.MarkLabel(next);
}
- private static void EmitTable(
- ILGenerator il,
- (string text, int destination)[] entries,
- int index,
- int length,
- Locals locals,
- Labels labels,
- Methods methods)
- {
- // We've reached the end of the string.
- if (index == length)
- {
- EmitReturnDestination(il, entries);
- return;
- }
+ // Exit point - we end up here when the text doesn't match
+ il.MarkLabel(labels.ReturnDefault);
+ il.Emit(OpCodes.Ldc_I4, defaultDestination);
+ il.Emit(OpCodes.Ret);
- // If 4 or more characters remain, and we're vectorizing, we should process 4 characters at a time.
- if (length - index >= 4 && locals.UInt64Value != null)
- {
- EmitVectorizedTable(il, entries, index, length, locals, labels, methods);
- return;
- }
+ // Exit point - we end up here with the text contains non-ASCII text
+ il.MarkLabel(labels.ReturnNotAscii);
+ il.Emit(OpCodes.Ldc_I4, NotAscii);
+ il.Emit(OpCodes.Ret);
+ }
- // Fall back to processing a character at a time.
- EmitSingleCharacterTable(il, entries, index, length, locals, labels, methods);
+ private static void EmitTable(
+ ILGenerator il,
+ (string text, int destination)[] entries,
+ int index,
+ int length,
+ Locals locals,
+ Labels labels,
+ Methods methods)
+ {
+ // We've reached the end of the string.
+ if (index == length)
+ {
+ EmitReturnDestination(il, entries);
+ return;
}
- private static void EmitVectorizedTable(
- ILGenerator il,
- (string text, int destination)[] entries,
- int index,
- int length,
- Locals locals,
- Labels labels,
- Methods methods)
+ // If 4 or more characters remain, and we're vectorizing, we should process 4 characters at a time.
+ if (length - index >= 4 && locals.UInt64Value != null)
{
- // Emits code similar to:
- //
- // uint64Value = Unsafe.ReadUnaligned<ulong>(ref p);
- // p = ref Unsafe.Add(ref p, 8);
- //
- // if ((uint64Value & ~0x007F007F007F007FUL) == 0)
- // {
- // return NotAscii;
- // }
- // uint64LowerIndicator = value + (0x0080008000800080UL - 0x0041004100410041UL);
- // uint64UpperIndicator = value + (0x0080008000800080UL - 0x005B005B005B005BUL);
- // ulong temp1 = uint64LowerIndicator ^ uint64UpperIndicator
- // ulong temp2 = temp1 & 0x0080008000800080UL;
- // ulong temp3 = (temp2) >> 2;
- // uint64Value = uint64Value ^ temp3;
- //
- // This is a vectorized non-branching technique for processing 4 utf16 characters
- // at a time inside a single uint64.
- //
- // Similar to:
- // https://github.com/GrabYourPitchforks/coreclr/commit/a3c1df25c4225995ffd6b18fd0fc39d6b81fd6a5#diff-d89b6ca07ea349899e45eed5f688a7ebR81
- //
- // Basically we need to check if the text is non-ASCII first and bail if it is.
- // The rest of the steps will convert the text to lowercase by checking all characters
- // at a time to see if they are in the A-Z range, that's where 0x0041 and 0x005B come in.
-
- // IMPORTANT
- //
- // If you are modifying this code, be aware that the easiest way to make a mistake is by
- // getting the set of casts wrong doing something like:
- //
- // il.Emit(OpCodes.Ldc_I8, ~0x007F007F007F007FUL);
- //
- // The IL Emit apis don't have overloads that accept ulong or ushort, and will resolve
- // an overload that does an undesirable conversion (for instance converting ulong to float).
- //
- // IMPORTANT
-
- // Unsafe.ReadUnaligned<ulong>(ref p)
- il.Emit(OpCodes.Ldloc, locals.P);
- il.Emit(OpCodes.Call, methods.ReadUnalignedUInt64);
-
- // uint64Value = ...
- il.Emit(OpCodes.Stloc, locals.UInt64Value);
-
- // Unsafe.Add(ref p, 8)
- il.Emit(OpCodes.Ldloc, locals.P);
- il.Emit(OpCodes.Ldc_I4, 8); // 8 bytes were read
- il.Emit(OpCodes.Call, methods.Add);
-
- // p = ref ...
- il.Emit(OpCodes.Stloc, locals.P);
-
- // if ((uint64Value & ~0x007F007F007F007FUL) == 0)
- // {
- // goto: NotAscii;
- // }
- il.Emit(OpCodes.Ldloc, locals.UInt64Value);
- il.Emit(OpCodes.Ldc_I8, unchecked((long)~0x007F007F007F007FUL));
- il.Emit(OpCodes.And);
- il.Emit(OpCodes.Brtrue, labels.ReturnNotAscii);
-
- // uint64Value + (0x0080008000800080UL - 0x0041004100410041UL)
- il.Emit(OpCodes.Ldloc, locals.UInt64Value);
- il.Emit(OpCodes.Ldc_I8, unchecked((long)(0x0080008000800080UL - 0x0041004100410041UL)));
- il.Emit(OpCodes.Add);
-
- // uint64LowerIndicator = ...
- il.Emit(OpCodes.Stloc, locals.UInt64LowerIndicator);
-
- // value + (0x0080008000800080UL - 0x005B005B005B005BUL)
- il.Emit(OpCodes.Ldloc, locals.UInt64Value);
- il.Emit(OpCodes.Ldc_I8, unchecked((long)(0x0080008000800080UL - 0x005B005B005B005BUL)));
- il.Emit(OpCodes.Add);
-
- // uint64UpperIndicator = ...
- il.Emit(OpCodes.Stloc, locals.UInt64UpperIndicator);
-
- // ulongLowerIndicator ^ ulongUpperIndicator
- il.Emit(OpCodes.Ldloc, locals.UInt64LowerIndicator);
- il.Emit(OpCodes.Ldloc, locals.UInt64UpperIndicator);
- il.Emit(OpCodes.Xor);
-
- // ... & 0x0080008000800080UL
- il.Emit(OpCodes.Ldc_I8, unchecked((long)0x0080008000800080UL));
- il.Emit(OpCodes.And);
+ EmitVectorizedTable(il, entries, index, length, locals, labels, methods);
+ return;
+ }
- // ... >> 2;
- il.Emit(OpCodes.Ldc_I4, 2);
- il.Emit(OpCodes.Shr_Un);
+ // Fall back to processing a character at a time.
+ EmitSingleCharacterTable(il, entries, index, length, locals, labels, methods);
+ }
- // ... ^ uint64Value
+ private static void EmitVectorizedTable(
+ ILGenerator il,
+ (string text, int destination)[] entries,
+ int index,
+ int length,
+ Locals locals,
+ Labels labels,
+ Methods methods)
+ {
+ // Emits code similar to:
+ //
+ // uint64Value = Unsafe.ReadUnaligned<ulong>(ref p);
+ // p = ref Unsafe.Add(ref p, 8);
+ //
+ // if ((uint64Value & ~0x007F007F007F007FUL) == 0)
+ // {
+ // return NotAscii;
+ // }
+ // uint64LowerIndicator = value + (0x0080008000800080UL - 0x0041004100410041UL);
+ // uint64UpperIndicator = value + (0x0080008000800080UL - 0x005B005B005B005BUL);
+ // ulong temp1 = uint64LowerIndicator ^ uint64UpperIndicator
+ // ulong temp2 = temp1 & 0x0080008000800080UL;
+ // ulong temp3 = (temp2) >> 2;
+ // uint64Value = uint64Value ^ temp3;
+ //
+ // This is a vectorized non-branching technique for processing 4 utf16 characters
+ // at a time inside a single uint64.
+ //
+ // Similar to:
+ // https://github.com/GrabYourPitchforks/coreclr/commit/a3c1df25c4225995ffd6b18fd0fc39d6b81fd6a5#diff-d89b6ca07ea349899e45eed5f688a7ebR81
+ //
+ // Basically we need to check if the text is non-ASCII first and bail if it is.
+ // The rest of the steps will convert the text to lowercase by checking all characters
+ // at a time to see if they are in the A-Z range, that's where 0x0041 and 0x005B come in.
+
+ // IMPORTANT
+ //
+ // If you are modifying this code, be aware that the easiest way to make a mistake is by
+ // getting the set of casts wrong doing something like:
+ //
+ // il.Emit(OpCodes.Ldc_I8, ~0x007F007F007F007FUL);
+ //
+ // The IL Emit apis don't have overloads that accept ulong or ushort, and will resolve
+ // an overload that does an undesirable conversion (for instance converting ulong to float).
+ //
+ // IMPORTANT
+
+ // Unsafe.ReadUnaligned<ulong>(ref p)
+ il.Emit(OpCodes.Ldloc, locals.P);
+ il.Emit(OpCodes.Call, methods.ReadUnalignedUInt64);
+
+ // uint64Value = ...
+ il.Emit(OpCodes.Stloc, locals.UInt64Value);
+
+ // Unsafe.Add(ref p, 8)
+ il.Emit(OpCodes.Ldloc, locals.P);
+ il.Emit(OpCodes.Ldc_I4, 8); // 8 bytes were read
+ il.Emit(OpCodes.Call, methods.Add);
+
+ // p = ref ...
+ il.Emit(OpCodes.Stloc, locals.P);
+
+ // if ((uint64Value & ~0x007F007F007F007FUL) == 0)
+ // {
+ // goto: NotAscii;
+ // }
+ il.Emit(OpCodes.Ldloc, locals.UInt64Value);
+ il.Emit(OpCodes.Ldc_I8, unchecked((long)~0x007F007F007F007FUL));
+ il.Emit(OpCodes.And);
+ il.Emit(OpCodes.Brtrue, labels.ReturnNotAscii);
+
+ // uint64Value + (0x0080008000800080UL - 0x0041004100410041UL)
+ il.Emit(OpCodes.Ldloc, locals.UInt64Value);
+ il.Emit(OpCodes.Ldc_I8, unchecked((long)(0x0080008000800080UL - 0x0041004100410041UL)));
+ il.Emit(OpCodes.Add);
+
+ // uint64LowerIndicator = ...
+ il.Emit(OpCodes.Stloc, locals.UInt64LowerIndicator);
+
+ // value + (0x0080008000800080UL - 0x005B005B005B005BUL)
+ il.Emit(OpCodes.Ldloc, locals.UInt64Value);
+ il.Emit(OpCodes.Ldc_I8, unchecked((long)(0x0080008000800080UL - 0x005B005B005B005BUL)));
+ il.Emit(OpCodes.Add);
+
+ // uint64UpperIndicator = ...
+ il.Emit(OpCodes.Stloc, locals.UInt64UpperIndicator);
+
+ // ulongLowerIndicator ^ ulongUpperIndicator
+ il.Emit(OpCodes.Ldloc, locals.UInt64LowerIndicator);
+ il.Emit(OpCodes.Ldloc, locals.UInt64UpperIndicator);
+ il.Emit(OpCodes.Xor);
+
+ // ... & 0x0080008000800080UL
+ il.Emit(OpCodes.Ldc_I8, unchecked((long)0x0080008000800080UL));
+ il.Emit(OpCodes.And);
+
+ // ... >> 2;
+ il.Emit(OpCodes.Ldc_I4, 2);
+ il.Emit(OpCodes.Shr_Un);
+
+ // ... ^ uint64Value
+ il.Emit(OpCodes.Ldloc, locals.UInt64Value);
+ il.Emit(OpCodes.Xor);
+
+ // uint64Value = ...
+ il.Emit(OpCodes.Stloc, locals.UInt64Value);
+
+ // Now we generate an 'if' ladder with an entry for each of the unique 64 bit sections
+ // of the text.
+ var groups = entries.GroupBy(e => GetUInt64Key(e.text, index));
+ foreach (var group in groups)
+ {
+ // if (uint64Value == 0x.....) { ... }
+ var next = il.DefineLabel();
il.Emit(OpCodes.Ldloc, locals.UInt64Value);
- il.Emit(OpCodes.Xor);
-
- // uint64Value = ...
- il.Emit(OpCodes.Stloc, locals.UInt64Value);
+ il.Emit(OpCodes.Ldc_I8, unchecked((long)group.Key));
+ il.Emit(OpCodes.Bne_Un, next);
- // Now we generate an 'if' ladder with an entry for each of the unique 64 bit sections
- // of the text.
- var groups = entries.GroupBy(e => GetUInt64Key(e.text, index));
- foreach (var group in groups)
- {
- // if (uint64Value == 0x.....) { ... }
- var next = il.DefineLabel();
- il.Emit(OpCodes.Ldloc, locals.UInt64Value);
- il.Emit(OpCodes.Ldc_I8, unchecked((long)group.Key));
- il.Emit(OpCodes.Bne_Un, next);
-
- // Process the group
- EmitTable(il, group.ToArray(), index + 4, length, locals, labels, methods);
- il.MarkLabel(next);
- }
-
- // goto: defaultDestination
- il.Emit(OpCodes.Br, labels.ReturnDefault);
+ // Process the group
+ EmitTable(il, group.ToArray(), index + 4, length, locals, labels, methods);
+ il.MarkLabel(next);
}
- private static void EmitSingleCharacterTable(
- ILGenerator il,
- (string text, int destination)[] entries,
- int index,
- int length,
- Locals locals,
- Labels labels,
- Methods methods)
+ // goto: defaultDestination
+ il.Emit(OpCodes.Br, labels.ReturnDefault);
+ }
+
+ private static void EmitSingleCharacterTable(
+ ILGenerator il,
+ (string text, int destination)[] entries,
+ int index,
+ int length,
+ Locals locals,
+ Labels labels,
+ Methods methods)
+ {
+ // See the vectorized code path for a much more thorough explanation.
+
+ // IMPORTANT
+ //
+ // If you are modifying this code, be aware that the easiest way to make a mistake is by
+ // getting the set of casts wrong doing something like:
+ //
+ // il.Emit(OpCodes.Ldc_I4, ~0x007F);
+ //
+ // The IL Emit apis don't have overloads that accept ulong or ushort, and will resolve
+ // an overload that does an undesirable conversion (for instance convering ulong to float).
+ //
+ // IMPORTANT
+
+ // Unsafe.ReadUnaligned<ushort>(ref p)
+ il.Emit(OpCodes.Ldloc, locals.P);
+ il.Emit(OpCodes.Call, methods.ReadUnalignedUInt16);
+
+ // uint16Value = ...
+ il.Emit(OpCodes.Stloc, locals.UInt16Value);
+
+ // Unsafe.Add(ref p, 2)
+ il.Emit(OpCodes.Ldloc, locals.P);
+ il.Emit(OpCodes.Ldc_I4, 2); // 2 bytes were read
+ il.Emit(OpCodes.Call, methods.Add);
+
+ // p = ref ...
+ il.Emit(OpCodes.Stloc, locals.P);
+
+ // if ((uInt16Value & ~0x007FUL) == 0)
+ // {
+ // goto: NotAscii;
+ // }
+ il.Emit(OpCodes.Ldloc, locals.UInt16Value);
+ il.Emit(OpCodes.Ldc_I4, unchecked((int)((uint)~0x007F)));
+ il.Emit(OpCodes.And);
+ il.Emit(OpCodes.Brtrue, labels.ReturnNotAscii);
+
+ // Since we're handling a single character at a time, it's easier to just
+ // generate an 'if' with two comparisons instead of doing complicated conversion
+ // logic.
+
+ // Now we generate an 'if' ladder with an entry for each of the unique
+ // characters in the group.
+ var groups = entries.GroupBy(e => GetUInt16Key(e.text, index));
+ foreach (var group in groups)
{
- // See the vectorized code path for a much more thorough explanation.
-
- // IMPORTANT
- //
- // If you are modifying this code, be aware that the easiest way to make a mistake is by
- // getting the set of casts wrong doing something like:
- //
- // il.Emit(OpCodes.Ldc_I4, ~0x007F);
- //
- // The IL Emit apis don't have overloads that accept ulong or ushort, and will resolve
- // an overload that does an undesirable conversion (for instance convering ulong to float).
- //
- // IMPORTANT
-
- // Unsafe.ReadUnaligned<ushort>(ref p)
- il.Emit(OpCodes.Ldloc, locals.P);
- il.Emit(OpCodes.Call, methods.ReadUnalignedUInt16);
-
- // uint16Value = ...
- il.Emit(OpCodes.Stloc, locals.UInt16Value);
-
- // Unsafe.Add(ref p, 2)
- il.Emit(OpCodes.Ldloc, locals.P);
- il.Emit(OpCodes.Ldc_I4, 2); // 2 bytes were read
- il.Emit(OpCodes.Call, methods.Add);
-
- // p = ref ...
- il.Emit(OpCodes.Stloc, locals.P);
-
- // if ((uInt16Value & ~0x007FUL) == 0)
- // {
- // goto: NotAscii;
- // }
+ // if (uInt16Value == 'A' || uint16Value == 'a') { ... }
+ var next = il.DefineLabel();
+ var inside = il.DefineLabel();
il.Emit(OpCodes.Ldloc, locals.UInt16Value);
- il.Emit(OpCodes.Ldc_I4, unchecked((int)((uint)~0x007F)));
- il.Emit(OpCodes.And);
- il.Emit(OpCodes.Brtrue, labels.ReturnNotAscii);
-
- // Since we're handling a single character at a time, it's easier to just
- // generate an 'if' with two comparisons instead of doing complicated conversion
- // logic.
-
- // Now we generate an 'if' ladder with an entry for each of the unique
- // characters in the group.
- var groups = entries.GroupBy(e => GetUInt16Key(e.text, index));
- foreach (var group in groups)
+ il.Emit(OpCodes.Ldc_I4, unchecked((int)((uint)group.Key)));
+ il.Emit(OpCodes.Beq, inside);
+
+ var upper = (ushort)char.ToUpperInvariant((char)group.Key);
+ if (upper != group.Key)
{
- // if (uInt16Value == 'A' || uint16Value == 'a') { ... }
- var next = il.DefineLabel();
- var inside = il.DefineLabel();
il.Emit(OpCodes.Ldloc, locals.UInt16Value);
- il.Emit(OpCodes.Ldc_I4, unchecked((int)((uint)group.Key)));
+ il.Emit(OpCodes.Ldc_I4, unchecked((int)((uint)upper)));
il.Emit(OpCodes.Beq, inside);
-
- var upper = (ushort)char.ToUpperInvariant((char)group.Key);
- if (upper != group.Key)
- {
- il.Emit(OpCodes.Ldloc, locals.UInt16Value);
- il.Emit(OpCodes.Ldc_I4, unchecked((int)((uint)upper)));
- il.Emit(OpCodes.Beq, inside);
- }
-
- il.Emit(OpCodes.Br, next);
-
- // Process the group
- il.MarkLabel(inside);
- EmitTable(il, group.ToArray(), index + 1, length, locals, labels, methods);
- il.MarkLabel(next);
}
- // goto: defaultDestination
- il.Emit(OpCodes.Br, labels.ReturnDefault);
- }
+ il.Emit(OpCodes.Br, next);
- public static void EmitReturnDestination(ILGenerator il, (string text, int destination)[] entries)
- {
- Debug.Assert(entries.Length == 1, "We should have a single entry");
- il.Emit(OpCodes.Ldc_I4, entries[0].destination);
- il.Emit(OpCodes.Ret);
+ // Process the group
+ il.MarkLabel(inside);
+ EmitTable(il, group.ToArray(), index + 1, length, locals, labels, methods);
+ il.MarkLabel(next);
}
- private static ulong GetUInt64Key(string text, int index)
- {
- Debug.Assert(index + 4 <= text.Length);
- var span = text.ToLowerInvariant().AsSpan(index);
- ref var p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
- return Unsafe.ReadUnaligned<ulong>(ref p);
- }
+ // goto: defaultDestination
+ il.Emit(OpCodes.Br, labels.ReturnDefault);
+ }
- private static ushort GetUInt16Key(string text, int index)
- {
- Debug.Assert(index + 1 <= text.Length);
- return (ushort)char.ToLowerInvariant(text[index]);
- }
+ public static void EmitReturnDestination(ILGenerator il, (string text, int destination)[] entries)
+ {
+ Debug.Assert(entries.Length == 1, "We should have a single entry");
+ il.Emit(OpCodes.Ldc_I4, entries[0].destination);
+ il.Emit(OpCodes.Ret);
+ }
+
+ private static ulong GetUInt64Key(string text, int index)
+ {
+ Debug.Assert(index + 4 <= text.Length);
+ var span = text.ToLowerInvariant().AsSpan(index);
+ ref var p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
+ return Unsafe.ReadUnaligned<ulong>(ref p);
+ }
+
+ private static ushort GetUInt16Key(string text, int index)
+ {
+ Debug.Assert(index + 1 <= text.Length);
+ return (ushort)char.ToLowerInvariant(text[index]);
+ }
- // We require a special build-time define since this is a testing/debugging
- // feature that will litter the app directory with assemblies.
+ // We require a special build-time define since this is a testing/debugging
+ // feature that will litter the app directory with assemblies.
#if IL_EMIT_SAVE_ASSEMBLY
private static void SaveAssembly(
int defaultDestination,
@@ -419,181 +419,180 @@ namespace Microsoft.AspNetCore.Routing.Matching
}
#endif
- private class Locals
+ private class Locals
+ {
+ public Locals(ILGenerator il, bool vectorize)
{
- public Locals(ILGenerator il, bool vectorize)
- {
- P = il.DeclareLocal(typeof(byte).MakeByRefType());
- Span = il.DeclareLocal(typeof(ReadOnlySpan<char>));
+ P = il.DeclareLocal(typeof(byte).MakeByRefType());
+ Span = il.DeclareLocal(typeof(ReadOnlySpan<char>));
- UInt16Value = il.DeclareLocal(typeof(ushort));
+ UInt16Value = il.DeclareLocal(typeof(ushort));
- if (vectorize)
- {
- UInt64Value = il.DeclareLocal(typeof(ulong));
- UInt64LowerIndicator = il.DeclareLocal(typeof(ulong));
- UInt64UpperIndicator = il.DeclareLocal(typeof(ulong));
- }
+ if (vectorize)
+ {
+ UInt64Value = il.DeclareLocal(typeof(ulong));
+ UInt64LowerIndicator = il.DeclareLocal(typeof(ulong));
+ UInt64UpperIndicator = il.DeclareLocal(typeof(ulong));
}
-
- /// <summary>
- /// Holds current character when processing a character at a time.
- /// </summary>
- public LocalBuilder UInt16Value { get; }
-
- /// <summary>
- /// Holds current character when processing 4 characters at a time.
- /// </summary>
- public LocalBuilder UInt64Value { get; }
-
- /// <summary>
- /// Used to covert casing. See comments where it's used.
- /// </summary>
- public LocalBuilder UInt64LowerIndicator { get; }
-
- /// <summary>
- /// Used to covert casing. See comments where it's used.
- /// </summary>
- public LocalBuilder UInt64UpperIndicator { get; }
-
- /// <summary>
- /// Holds a 'ref byte' reference to the current character (in bytes).
- /// </summary>
- public LocalBuilder P { get; }
-
- /// <summary>
- /// Holds the relevant portion of the path as a Span[byte].
- /// </summary>
- public LocalBuilder Span { get; }
}
- private class Labels
- {
- /// <summary>
- /// Label to goto that will return the default destination (not a match).
- /// </summary>
- public Label ReturnDefault { get; set; }
-
- /// <summary>
- /// Label to goto that will return a sentinel value for non-ascii text.
- /// </summary>
- public Label ReturnNotAscii { get; set; }
- }
+ /// <summary>
+ /// Holds current character when processing a character at a time.
+ /// </summary>
+ public LocalBuilder UInt16Value { get; }
+
+ /// <summary>
+ /// Holds current character when processing 4 characters at a time.
+ /// </summary>
+ public LocalBuilder UInt64Value { get; }
+
+ /// <summary>
+ /// Used to covert casing. See comments where it's used.
+ /// </summary>
+ public LocalBuilder UInt64LowerIndicator { get; }
+
+ /// <summary>
+ /// Used to covert casing. See comments where it's used.
+ /// </summary>
+ public LocalBuilder UInt64UpperIndicator { get; }
+
+ /// <summary>
+ /// Holds a 'ref byte' reference to the current character (in bytes).
+ /// </summary>
+ public LocalBuilder P { get; }
+
+ /// <summary>
+ /// Holds the relevant portion of the path as a Span[byte].
+ /// </summary>
+ public LocalBuilder Span { get; }
+ }
+
+ private class Labels
+ {
+ /// <summary>
+ /// Label to goto that will return the default destination (not a match).
+ /// </summary>
+ public Label ReturnDefault { get; set; }
+
+ /// <summary>
+ /// Label to goto that will return a sentinel value for non-ascii text.
+ /// </summary>
+ public Label ReturnNotAscii { get; set; }
+ }
- private class Methods
+ private class Methods
+ {
+ // Caching because the methods won't change, if we're being called once we're likely to
+ // be called again.
+ public static readonly Methods Instance = new Methods();
+
+ private Methods()
{
- // Caching because the methods won't change, if we're being called once we're likely to
- // be called again.
- public static readonly Methods Instance = new Methods();
+ // Can't use GetMethod because the parameter is a generic method parameters.
+ Add = typeof(Unsafe)
+ .GetMethods(BindingFlags.Public | BindingFlags.Static)
+ .Where(m => m.Name == nameof(Unsafe.Add))
+ .Where(m => m.GetGenericArguments().Length == 1)
+ .Where(m => m.GetParameters().Length == 2)
+ .FirstOrDefault()
+ ?.MakeGenericMethod(typeof(byte));
+ if (Add == null)
+ {
+ throw new InvalidOperationException("Failed to find Unsafe.Add{T}(ref T, int)");
+ }
+
+ // Can't use GetMethod because the parameter is a generic method parameters.
+ As = typeof(Unsafe)
+ .GetMethods(BindingFlags.Public | BindingFlags.Static)
+ .Where(m => m.Name == nameof(Unsafe.As))
+ .Where(m => m.GetGenericArguments().Length == 2)
+ .Where(m => m.GetParameters().Length == 1)
+ .FirstOrDefault()
+ ?.MakeGenericMethod(typeof(char), typeof(byte));
+ if (Add == null)
+ {
+ throw new InvalidOperationException("Failed to find Unsafe.As{TFrom, TTo}(ref TFrom)");
+ }
+
+ AsSpan = typeof(MemoryExtensions).GetMethod(
+ nameof(MemoryExtensions.AsSpan),
+ BindingFlags.Public | BindingFlags.Static,
+ binder: null,
+ new[] { typeof(string), typeof(int), typeof(int), },
+ modifiers: null);
+ if (AsSpan == null)
+ {
+ throw new InvalidOperationException("Failed to find MemoryExtensions.AsSpan(string, int, int)");
+ }
- private Methods()
+ // Can't use GetMethod because the parameter is a generic method parameters.
+ GetReference = typeof(MemoryMarshal)
+ .GetMethods(BindingFlags.Public | BindingFlags.Static)
+ .Where(m => m.Name == nameof(MemoryMarshal.GetReference))
+ .Where(m => m.GetGenericArguments().Length == 1)
+ .Where(m => m.GetParameters().Length == 1)
+ // Disambiguate between ReadOnlySpan<> and Span<> - this method is overloaded.
+ .Where(m => m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(ReadOnlySpan<>))
+ .FirstOrDefault()
+ ?.MakeGenericMethod(typeof(char));
+ if (GetReference == null)
{
- // Can't use GetMethod because the parameter is a generic method parameters.
- Add = typeof(Unsafe)
- .GetMethods(BindingFlags.Public | BindingFlags.Static)
- .Where(m => m.Name == nameof(Unsafe.Add))
- .Where(m => m.GetGenericArguments().Length == 1)
- .Where(m => m.GetParameters().Length == 2)
- .FirstOrDefault()
- ?.MakeGenericMethod(typeof(byte));
- if (Add == null)
- {
- throw new InvalidOperationException("Failed to find Unsafe.Add{T}(ref T, int)");
- }
-
- // Can't use GetMethod because the parameter is a generic method parameters.
- As = typeof(Unsafe)
- .GetMethods(BindingFlags.Public | BindingFlags.Static)
- .Where(m => m.Name == nameof(Unsafe.As))
- .Where(m => m.GetGenericArguments().Length == 2)
- .Where(m => m.GetParameters().Length == 1)
- .FirstOrDefault()
- ?.MakeGenericMethod(typeof(char), typeof(byte));
- if (Add == null)
- {
- throw new InvalidOperationException("Failed to find Unsafe.As{TFrom, TTo}(ref TFrom)");
- }
-
- AsSpan = typeof(MemoryExtensions).GetMethod(
- nameof(MemoryExtensions.AsSpan),
- BindingFlags.Public | BindingFlags.Static,
- binder: null,
- new[] { typeof(string), typeof(int), typeof(int), },
- modifiers: null);
- if (AsSpan == null)
- {
- throw new InvalidOperationException("Failed to find MemoryExtensions.AsSpan(string, int, int)");
- }
-
- // Can't use GetMethod because the parameter is a generic method parameters.
- GetReference = typeof(MemoryMarshal)
- .GetMethods(BindingFlags.Public | BindingFlags.Static)
- .Where(m => m.Name == nameof(MemoryMarshal.GetReference))
- .Where(m => m.GetGenericArguments().Length == 1)
- .Where(m => m.GetParameters().Length == 1)
- // Disambiguate between ReadOnlySpan<> and Span<> - this method is overloaded.
- .Where(m => m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(ReadOnlySpan<>))
- .FirstOrDefault()
- ?.MakeGenericMethod(typeof(char));
- if (GetReference == null)
- {
- throw new InvalidOperationException("Failed to find MemoryMarshal.GetReference{T}(ReadOnlySpan{T})");
- }
-
- ReadUnalignedUInt64 = typeof(Unsafe).GetMethod(
- nameof(Unsafe.ReadUnaligned),
- BindingFlags.Public | BindingFlags.Static,
- binder: null,
- new[] { typeof(byte).MakeByRefType(), },
- modifiers: null)
- .MakeGenericMethod(typeof(ulong));
- if (ReadUnalignedUInt64 == null)
- {
- throw new InvalidOperationException("Failed to find Unsafe.ReadUnaligned{T}(ref byte)");
- }
-
- ReadUnalignedUInt16 = typeof(Unsafe).GetMethod(
- nameof(Unsafe.ReadUnaligned),
- BindingFlags.Public | BindingFlags.Static,
- binder: null,
- new[] { typeof(byte).MakeByRefType(), },
- modifiers: null)
- .MakeGenericMethod(typeof(ushort));
- if (ReadUnalignedUInt16 == null)
- {
- throw new InvalidOperationException("Failed to find Unsafe.ReadUnaligned{T}(ref byte)");
- }
+ throw new InvalidOperationException("Failed to find MemoryMarshal.GetReference{T}(ReadOnlySpan{T})");
}
- /// <summary>
- /// <see cref="Unsafe.Add{T}(ref T, int)"/> - Add[ref byte]
- /// </summary>
- public MethodInfo Add { get; }
-
- /// <summary>
- /// <see cref="Unsafe.As{TFrom, TTo}(ref TFrom)"/> - As[char, byte]
- /// </summary>
- public MethodInfo As { get; }
-
- /// <summary>
- /// <see cref="MemoryExtensions.AsSpan(string, int, int)"/>
- /// </summary>
- public MethodInfo AsSpan { get; }
-
- /// <summary>
- /// <see cref="MemoryMarshal.GetReference{T}(ReadOnlySpan{T})"/> - GetReference[char]
- /// </summary>
- public MethodInfo GetReference { get; }
-
- /// <summary>
- /// <see cref="Unsafe.ReadUnaligned{T}(ref byte)"/> - ReadUnaligned[ulong]
- /// </summary>
- public MethodInfo ReadUnalignedUInt64 { get; }
-
- /// <summary>
- /// <see cref="Unsafe.ReadUnaligned{T}(ref byte)"/> - ReadUnaligned[ushort]
- /// </summary>
- public MethodInfo ReadUnalignedUInt16 { get; }
+ ReadUnalignedUInt64 = typeof(Unsafe).GetMethod(
+ nameof(Unsafe.ReadUnaligned),
+ BindingFlags.Public | BindingFlags.Static,
+ binder: null,
+ new[] { typeof(byte).MakeByRefType(), },
+ modifiers: null)
+ .MakeGenericMethod(typeof(ulong));
+ if (ReadUnalignedUInt64 == null)
+ {
+ throw new InvalidOperationException("Failed to find Unsafe.ReadUnaligned{T}(ref byte)");
+ }
+
+ ReadUnalignedUInt16 = typeof(Unsafe).GetMethod(
+ nameof(Unsafe.ReadUnaligned),
+ BindingFlags.Public | BindingFlags.Static,
+ binder: null,
+ new[] { typeof(byte).MakeByRefType(), },
+ modifiers: null)
+ .MakeGenericMethod(typeof(ushort));
+ if (ReadUnalignedUInt16 == null)
+ {
+ throw new InvalidOperationException("Failed to find Unsafe.ReadUnaligned{T}(ref byte)");
+ }
}
+
+ /// <summary>
+ /// <see cref="Unsafe.Add{T}(ref T, int)"/> - Add[ref byte]
+ /// </summary>
+ public MethodInfo Add { get; }
+
+ /// <summary>
+ /// <see cref="Unsafe.As{TFrom, TTo}(ref TFrom)"/> - As[char, byte]
+ /// </summary>
+ public MethodInfo As { get; }
+
+ /// <summary>
+ /// <see cref="MemoryExtensions.AsSpan(string, int, int)"/>
+ /// </summary>
+ public MethodInfo AsSpan { get; }
+
+ /// <summary>
+ /// <see cref="MemoryMarshal.GetReference{T}(ReadOnlySpan{T})"/> - GetReference[char]
+ /// </summary>
+ public MethodInfo GetReference { get; }
+
+ /// <summary>
+ /// <see cref="Unsafe.ReadUnaligned{T}(ref byte)"/> - ReadUnaligned[ulong]
+ /// </summary>
+ public MethodInfo ReadUnalignedUInt64 { get; }
+
+ /// <summary>
+ /// <see cref="Unsafe.ReadUnaligned{T}(ref byte)"/> - ReadUnaligned[ushort]
+ /// </summary>
+ public MethodInfo ReadUnalignedUInt16 { get; }
}
}
diff --git a/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs b/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs
index e742795995..8f0128bb18 100644
--- a/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs
+++ b/src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs
@@ -7,96 +7,95 @@ using System;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Uses generated IL to implement the JumpTable contract. This approach requires
+// a fallback jump table for two reasons:
+// 1. We compute the IL lazily to avoid taking up significant time when processing a request
+// 2. The generated IL only supports ASCII in the URL path
+internal class ILEmitTrieJumpTable : JumpTable
{
- // Uses generated IL to implement the JumpTable contract. This approach requires
- // a fallback jump table for two reasons:
- // 1. We compute the IL lazily to avoid taking up significant time when processing a request
- // 2. The generated IL only supports ASCII in the URL path
- internal class ILEmitTrieJumpTable : JumpTable
+ private readonly int _defaultDestination;
+ private readonly int _exitDestination;
+ private readonly (string text, int destination)[] _entries;
+
+ private readonly bool? _vectorize;
+ private readonly JumpTable _fallback;
+
+ // Used to protect the initialization of the compiled delegate
+ private object _lock;
+ private bool _initializing;
+ private Task _task;
+
+ // Will be replaced at runtime by the generated code.
+ //
+ // Internal for testing
+ internal Func<string, PathSegment, int> _getDestination;
+
+ public ILEmitTrieJumpTable(
+ int defaultDestination,
+ int exitDestination,
+ (string text, int destination)[] entries,
+ bool? vectorize,
+ JumpTable fallback)
{
- private readonly int _defaultDestination;
- private readonly int _exitDestination;
- private readonly (string text, int destination)[] _entries;
-
- private readonly bool? _vectorize;
- private readonly JumpTable _fallback;
-
- // Used to protect the initialization of the compiled delegate
- private object _lock;
- private bool _initializing;
- private Task _task;
-
- // Will be replaced at runtime by the generated code.
- //
- // Internal for testing
- internal Func<string, PathSegment, int> _getDestination;
-
- public ILEmitTrieJumpTable(
- int defaultDestination,
- int exitDestination,
- (string text, int destination)[] entries,
- bool? vectorize,
- JumpTable fallback)
- {
- _defaultDestination = defaultDestination;
- _exitDestination = exitDestination;
- _entries = entries;
- _vectorize = vectorize;
- _fallback = fallback;
+ _defaultDestination = defaultDestination;
+ _exitDestination = exitDestination;
+ _entries = entries;
+ _vectorize = vectorize;
+ _fallback = fallback;
- _getDestination = FallbackGetDestination;
- }
+ _getDestination = FallbackGetDestination;
+ }
+
+ public override int GetDestination(string path, PathSegment segment)
+ {
+ return _getDestination(path, segment);
+ }
- public override int GetDestination(string path, PathSegment segment)
+ // Used when we haven't yet initialized the IL trie. We defer compilation of the IL for startup
+ // performance.
+ private int FallbackGetDestination(string path, PathSegment segment)
+ {
+ if (path.Length == 0)
{
- return _getDestination(path, segment);
+ return _exitDestination;
}
- // Used when we haven't yet initialized the IL trie. We defer compilation of the IL for startup
- // performance.
- private int FallbackGetDestination(string path, PathSegment segment)
- {
- if (path.Length == 0)
- {
- return _exitDestination;
- }
+ // We only hit this code path if the IL delegate is still initializing.
+ LazyInitializer.EnsureInitialized(ref _task, ref _initializing, ref _lock, InitializeILDelegateAsync);
- // We only hit this code path if the IL delegate is still initializing.
- LazyInitializer.EnsureInitialized(ref _task, ref _initializing, ref _lock, InitializeILDelegateAsync);
+ return _fallback.GetDestination(path, segment);
+ }
- return _fallback.GetDestination(path, segment);
- }
+ // Internal for testing
+ internal async Task InitializeILDelegateAsync()
+ {
+ // Offload the creation of the IL delegate to the thread pool.
+ await Task.Run(() =>
+ {
+ InitializeILDelegate();
+ });
+ }
- // Internal for testing
- internal async Task InitializeILDelegateAsync()
+ // Internal for testing
+ internal void InitializeILDelegate()
+ {
+ var generated = ILEmitTrieFactory.Create(_defaultDestination, _exitDestination, _entries, _vectorize);
+ _getDestination = (string path, PathSegment segment) =>
{
- // Offload the creation of the IL delegate to the thread pool.
- await Task.Run(() =>
+ if (segment.Length == 0)
{
- InitializeILDelegate();
- });
- }
+ return _exitDestination;
+ }
- // Internal for testing
- internal void InitializeILDelegate()
- {
- var generated = ILEmitTrieFactory.Create(_defaultDestination, _exitDestination, _entries, _vectorize);
- _getDestination = (string path, PathSegment segment) =>
+ var result = generated(path, segment.Start, segment.Length);
+ if (result == ILEmitTrieFactory.NotAscii)
{
- if (segment.Length == 0)
- {
- return _exitDestination;
- }
-
- var result = generated(path, segment.Start, segment.Length);
- if (result == ILEmitTrieFactory.NotAscii)
- {
- result = _fallback.GetDestination(path, segment);
- }
-
- return result;
- };
- }
+ result = _fallback.GetDestination(path, segment);
+ }
+
+ return result;
+ };
}
}
diff --git a/src/Http/Routing/src/Matching/INodeBuilderPolicy.cs b/src/Http/Routing/src/Matching/INodeBuilderPolicy.cs
index f25c64fa57..c8fd4abf31 100644
--- a/src/Http/Routing/src/Matching/INodeBuilderPolicy.cs
+++ b/src/Http/Routing/src/Matching/INodeBuilderPolicy.cs
@@ -4,33 +4,32 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// Implements an interface for a matcher policy with support for generating graph representations of the endpoints.
+/// </summary>
+public interface INodeBuilderPolicy
{
/// <summary>
- /// Implements an interface for a matcher policy with support for generating graph representations of the endpoints.
+ /// Evaluates if the policy matches any of the endpoints provided in <paramref name="endpoints"/>.
/// </summary>
- public interface INodeBuilderPolicy
- {
- /// <summary>
- /// Evaluates if the policy matches any of the endpoints provided in <paramref name="endpoints"/>.
- /// </summary>
- /// <param name="endpoints">A list of <see cref="Endpoint"/>.</param>
- /// <returns><see langword="true"/> if the policy applies to any of the provided <paramref name="endpoints"/>.</returns>
- bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints);
+ /// <param name="endpoints">A list of <see cref="Endpoint"/>.</param>
+ /// <returns><see langword="true"/> if the policy applies to any of the provided <paramref name="endpoints"/>.</returns>
+ bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints);
- /// <summary>
- /// Generates a graph that representations the relationship between endpoints and hosts.
- /// </summary>
- /// <param name="endpoints">A list of <see cref="Endpoint"/>.</param>
- /// <returns>A graph representing the relationship between endpoints and hosts.</returns>
- IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints);
+ /// <summary>
+ /// Generates a graph that representations the relationship between endpoints and hosts.
+ /// </summary>
+ /// <param name="endpoints">A list of <see cref="Endpoint"/>.</param>
+ /// <returns>A graph representing the relationship between endpoints and hosts.</returns>
+ IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints);
- /// <summary>
- /// Constructs a jump table given the a set of <paramref name="edges"/>.
- /// </summary>
- /// <param name="exitDestination">The default destination for lookups.</param>
- /// <param name="edges">A list of <see cref="PolicyJumpTableEdge"/>.</param>
- /// <returns>A <see cref="PolicyJumpTable"/> instance.</returns>
- PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges);
- }
+ /// <summary>
+ /// Constructs a jump table given the a set of <paramref name="edges"/>.
+ /// </summary>
+ /// <param name="exitDestination">The default destination for lookups.</param>
+ /// <param name="edges">A list of <see cref="PolicyJumpTableEdge"/>.</param>
+ /// <returns>A <see cref="PolicyJumpTable"/> instance.</returns>
+ PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges);
}
diff --git a/src/Http/Routing/src/Matching/IParameterLiteralNodeMatchingPolicy.cs b/src/Http/Routing/src/Matching/IParameterLiteralNodeMatchingPolicy.cs
index 805527ed21..c7b5f833d5 100644
--- a/src/Http/Routing/src/Matching/IParameterLiteralNodeMatchingPolicy.cs
+++ b/src/Http/Routing/src/Matching/IParameterLiteralNodeMatchingPolicy.cs
@@ -1,22 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// Defines the contract that a class must implement in order to check if a literal value is valid for a given constraint.
+/// <remarks>
+/// When a parameter implements this interface, the router is able to optimize away some paths from the route table that don't match this constraint.
+/// </remarks>
+/// </summary>
+public interface IParameterLiteralNodeMatchingPolicy : IParameterPolicy
{
/// <summary>
- /// Defines the contract that a class must implement in order to check if a literal value is valid for a given constraint.
- /// <remarks>
- /// When a parameter implements this interface, the router is able to optimize away some paths from the route table that don't match this constraint.
- /// </remarks>
+ /// Determines whether the given <paramref name="literal"/> can match the constraint.
/// </summary>
- public interface IParameterLiteralNodeMatchingPolicy : IParameterPolicy
- {
- /// <summary>
- /// Determines whether the given <paramref name="literal"/> can match the constraint.
- /// </summary>
- /// <param name="parameterName">The parameter name we are currently evaluating.</param>
- /// <param name="literal">The literal to test the constraint against.</param>
- /// <returns><c>true</c> if the literal contains a valid value; otherwise, <c>false</c>.</returns>
- bool MatchesLiteral(string parameterName, string literal);
- }
+ /// <param name="parameterName">The parameter name we are currently evaluating.</param>
+ /// <param name="literal">The literal to test the constraint against.</param>
+ /// <returns><c>true</c> if the literal contains a valid value; otherwise, <c>false</c>.</returns>
+ bool MatchesLiteral(string parameterName, string literal);
}
diff --git a/src/Http/Routing/src/Matching/JumpTable.cs b/src/Http/Routing/src/Matching/JumpTable.cs
index 1cad22dc56..db3a4fa4c9 100644
--- a/src/Http/Routing/src/Matching/JumpTable.cs
+++ b/src/Http/Routing/src/Matching/JumpTable.cs
@@ -3,16 +3,15 @@
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+internal abstract class JumpTable
{
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- internal abstract class JumpTable
- {
- public abstract int GetDestination(string path, PathSegment segment);
+ public abstract int GetDestination(string path, PathSegment segment);
- public virtual string DebuggerToString()
- {
- return GetType().Name;
- }
+ public virtual string DebuggerToString()
+ {
+ return GetType().Name;
}
}
diff --git a/src/Http/Routing/src/Matching/JumpTableBuilder.cs b/src/Http/Routing/src/Matching/JumpTableBuilder.cs
index 3f7bb52e60..62e4d49c7a 100644
--- a/src/Http/Routing/src/Matching/JumpTableBuilder.cs
+++ b/src/Http/Routing/src/Matching/JumpTableBuilder.cs
@@ -4,94 +4,93 @@
using System;
using System.Runtime.CompilerServices;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal static class JumpTableBuilder
{
- internal static class JumpTableBuilder
- {
- public const int InvalidDestination = -1;
+ public const int InvalidDestination = -1;
- public static JumpTable Build(int defaultDestination, int exitDestination, (string text, int destination)[] pathEntries)
+ public static JumpTable Build(int defaultDestination, int exitDestination, (string text, int destination)[] pathEntries)
+ {
+ if (defaultDestination == InvalidDestination)
{
- if (defaultDestination == InvalidDestination)
- {
- var message = $"{nameof(defaultDestination)} is not set. Please report this as a bug.";
- throw new InvalidOperationException(message);
- }
-
- if (exitDestination == InvalidDestination)
- {
- var message = $"{nameof(exitDestination)} is not set. Please report this as a bug.";
- throw new InvalidOperationException(message);
- }
+ var message = $"{nameof(defaultDestination)} is not set. Please report this as a bug.";
+ throw new InvalidOperationException(message);
+ }
- // The JumpTable implementation is chosen based on the number of entries.
- //
- // Basically the concerns that we're juggling here are that different implementations
- // make sense depending on the characteristics of the entries.
- //
- // On netcoreapp we support IL generation of optimized tries that is much faster
- // than anything we can do with string.Compare or dictionaries. However the IL emit
- // strategy requires us to produce a fallback jump table - see comments on the class.
+ if (exitDestination == InvalidDestination)
+ {
+ var message = $"{nameof(exitDestination)} is not set. Please report this as a bug.";
+ throw new InvalidOperationException(message);
+ }
- // We have an optimized fast path for zero entries since we don't have to
- // do any string comparisons.
- if (pathEntries == null || pathEntries.Length == 0)
- {
- return new ZeroEntryJumpTable(defaultDestination, exitDestination);
- }
+ // The JumpTable implementation is chosen based on the number of entries.
+ //
+ // Basically the concerns that we're juggling here are that different implementations
+ // make sense depending on the characteristics of the entries.
+ //
+ // On netcoreapp we support IL generation of optimized tries that is much faster
+ // than anything we can do with string.Compare or dictionaries. However the IL emit
+ // strategy requires us to produce a fallback jump table - see comments on the class.
- // The IL Emit jump table is not faster for a single entry - but we have an optimized version when all text
- // is ASCII
- if (pathEntries.Length == 1 && Ascii.IsAscii(pathEntries[0].text))
- {
- var entry = pathEntries[0];
- return new SingleEntryAsciiJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
- }
+ // We have an optimized fast path for zero entries since we don't have to
+ // do any string comparisons.
+ if (pathEntries == null || pathEntries.Length == 0)
+ {
+ return new ZeroEntryJumpTable(defaultDestination, exitDestination);
+ }
- // We have a fallback that works for non-ASCII
- if (pathEntries.Length == 1)
- {
- var entry = pathEntries[0];
- return new SingleEntryJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
- }
+ // The IL Emit jump table is not faster for a single entry - but we have an optimized version when all text
+ // is ASCII
+ if (pathEntries.Length == 1 && Ascii.IsAscii(pathEntries[0].text))
+ {
+ var entry = pathEntries[0];
+ return new SingleEntryAsciiJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
+ }
- // We choose a hard upper bound of 100 as the limit for when we switch to a dictionary
- // over a trie. The reason is that while the dictionary has a bigger constant factor,
- // it is O(1) vs a trie which is O(M * log(N)). Our perf testing shows that the trie
- // is better for ~90 entries based on all of Azure's route table. Anything above 100 edges
- // we'd consider to be a very very large node, and so while we don't think anyone will
- // have a node this large in practice, we want to make sure the performance is reasonable
- // for any size.
- //
- // Additionally if we're on 32bit, the scalability is worse, so switch to the dictionary at 50
- // entries.
- var threshold = IntPtr.Size == 8 ? 100 : 50;
- if (pathEntries.Length >= threshold)
- {
- return new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
- }
+ // We have a fallback that works for non-ASCII
+ if (pathEntries.Length == 1)
+ {
+ var entry = pathEntries[0];
+ return new SingleEntryJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
+ }
- // If we have more than a single string, the IL emit strategy is the fastest - but we need to decide
- // what do for the fallback case.
- JumpTable fallback;
+ // We choose a hard upper bound of 100 as the limit for when we switch to a dictionary
+ // over a trie. The reason is that while the dictionary has a bigger constant factor,
+ // it is O(1) vs a trie which is O(M * log(N)). Our perf testing shows that the trie
+ // is better for ~90 entries based on all of Azure's route table. Anything above 100 edges
+ // we'd consider to be a very very large node, and so while we don't think anyone will
+ // have a node this large in practice, we want to make sure the performance is reasonable
+ // for any size.
+ //
+ // Additionally if we're on 32bit, the scalability is worse, so switch to the dictionary at 50
+ // entries.
+ var threshold = IntPtr.Size == 8 ? 100 : 50;
+ if (pathEntries.Length >= threshold)
+ {
+ return new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
+ }
- // Based on our testing a linear search is still faster than a dictionary at ten entries.
- if (pathEntries.Length <= 10)
- {
- fallback = new LinearSearchJumpTable(defaultDestination, exitDestination, pathEntries);
- }
- else
- {
- fallback = new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
- }
+ // If we have more than a single string, the IL emit strategy is the fastest - but we need to decide
+ // what do for the fallback case.
+ JumpTable fallback;
- // Use the ILEmitTrieJumpTable if the IL is going to be compiled (not interpreted)
- if (RuntimeFeature.IsDynamicCodeCompiled)
- {
- return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
- }
+ // Based on our testing a linear search is still faster than a dictionary at ten entries.
+ if (pathEntries.Length <= 10)
+ {
+ fallback = new LinearSearchJumpTable(defaultDestination, exitDestination, pathEntries);
+ }
+ else
+ {
+ fallback = new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
+ }
- return fallback;
+ // Use the ILEmitTrieJumpTable if the IL is going to be compiled (not interpreted)
+ if (RuntimeFeature.IsDynamicCodeCompiled)
+ {
+ return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
}
+
+ return fallback;
}
}
diff --git a/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs b/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs
index af4d2c12b3..0673a1bcb6 100644
--- a/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs
+++ b/src/Http/Routing/src/Matching/LinearSearchJumpTable.cs
@@ -5,68 +5,67 @@ using System;
using System.Linq;
using System.Text;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class LinearSearchJumpTable : JumpTable
{
- internal class LinearSearchJumpTable : JumpTable
+ private readonly int _defaultDestination;
+ private readonly int _exitDestination;
+ private readonly (string text, int destination)[] _entries;
+
+ public LinearSearchJumpTable(
+ int defaultDestination,
+ int exitDestination,
+ (string text, int destination)[] entries)
{
- private readonly int _defaultDestination;
- private readonly int _exitDestination;
- private readonly (string text, int destination)[] _entries;
+ _defaultDestination = defaultDestination;
+ _exitDestination = exitDestination;
+ _entries = entries;
+ }
- public LinearSearchJumpTable(
- int defaultDestination,
- int exitDestination,
- (string text, int destination)[] entries)
+ public override int GetDestination(string path, PathSegment segment)
+ {
+ if (segment.Length == 0)
{
- _defaultDestination = defaultDestination;
- _exitDestination = exitDestination;
- _entries = entries;
+ return _exitDestination;
}
- public override int GetDestination(string path, PathSegment segment)
+ var entries = _entries;
+ for (var i = 0; i < entries.Length; i++)
{
- if (segment.Length == 0)
- {
- return _exitDestination;
- }
-
- var entries = _entries;
- for (var i = 0; i < entries.Length; i++)
+ var text = entries[i].text;
+ if (segment.Length == text.Length &&
+ string.Compare(
+ path,
+ segment.Start,
+ text,
+ 0,
+ segment.Length,
+ StringComparison.OrdinalIgnoreCase) == 0)
{
- var text = entries[i].text;
- if (segment.Length == text.Length &&
- string.Compare(
- path,
- segment.Start,
- text,
- 0,
- segment.Length,
- StringComparison.OrdinalIgnoreCase) == 0)
- {
- return entries[i].destination;
- }
+ return entries[i].destination;
}
-
- return _defaultDestination;
}
- public override string DebuggerToString()
- {
- var builder = new StringBuilder();
- builder.Append("{ ");
+ return _defaultDestination;
+ }
- builder.AppendJoin(", ", _entries.Select(e => $"{e.text}: {e.destination}"));
+ public override string DebuggerToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append("{ ");
- builder.Append("$+: ");
- builder.Append(_defaultDestination);
- builder.Append(", ");
+ builder.AppendJoin(", ", _entries.Select(e => $"{e.text}: {e.destination}"));
- builder.Append("$0: ");
- builder.Append(_defaultDestination);
+ builder.Append("$+: ");
+ builder.Append(_defaultDestination);
+ builder.Append(", ");
- builder.Append(" }");
+ builder.Append("$0: ");
+ builder.Append(_defaultDestination);
- return builder.ToString();
- }
+ builder.Append(" }");
+
+ return builder.ToString();
}
}
diff --git a/src/Http/Routing/src/Matching/Matcher.cs b/src/Http/Routing/src/Matching/Matcher.cs
index 040ea74df9..34d06fe9e7 100644
--- a/src/Http/Routing/src/Matching/Matcher.cs
+++ b/src/Http/Routing/src/Matching/Matcher.cs
@@ -5,19 +5,18 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// An interface for components that can select an <see cref="Endpoint"/> given the current request, as part
+/// of the execution of <see cref="EndpointRoutingMiddleware"/>.
+/// </summary>
+internal abstract class Matcher
{
/// <summary>
- /// An interface for components that can select an <see cref="Endpoint"/> given the current request, as part
- /// of the execution of <see cref="EndpointRoutingMiddleware"/>.
+ /// Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
/// </summary>
- internal abstract class Matcher
- {
- /// <summary>
- /// Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <returns>A <see cref="Task"/> which represents the asynchronous completion of the operation.</returns>
- public abstract Task MatchAsync(HttpContext httpContext);
- }
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <returns>A <see cref="Task"/> which represents the asynchronous completion of the operation.</returns>
+ public abstract Task MatchAsync(HttpContext httpContext);
}
diff --git a/src/Http/Routing/src/Matching/MatcherBuilder.cs b/src/Http/Routing/src/Matching/MatcherBuilder.cs
index 65514ea9df..77771e2a4e 100644
--- a/src/Http/Routing/src/Matching/MatcherBuilder.cs
+++ b/src/Http/Routing/src/Matching/MatcherBuilder.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal abstract class MatcherBuilder
{
- internal abstract class MatcherBuilder
- {
- public abstract void AddEndpoint(RouteEndpoint endpoint);
+ public abstract void AddEndpoint(RouteEndpoint endpoint);
- public abstract Matcher Build();
- }
+ public abstract Matcher Build();
}
diff --git a/src/Http/Routing/src/Matching/MatcherFactory.cs b/src/Http/Routing/src/Matching/MatcherFactory.cs
index 3289d3a382..1ac712fcc2 100644
--- a/src/Http/Routing/src/Matching/MatcherFactory.cs
+++ b/src/Http/Routing/src/Matching/MatcherFactory.cs
@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal abstract class MatcherFactory
{
- internal abstract class MatcherFactory
- {
- public abstract Matcher CreateMatcher(EndpointDataSource dataSource);
- }
+ public abstract Matcher CreateMatcher(EndpointDataSource dataSource);
}
diff --git a/src/Http/Routing/src/Matching/MatcherPolicy.cs b/src/Http/Routing/src/Matching/MatcherPolicy.cs
index 109f087e0c..3beef63b71 100644
--- a/src/Http/Routing/src/Matching/MatcherPolicy.cs
+++ b/src/Http/Routing/src/Matching/MatcherPolicy.cs
@@ -6,63 +6,62 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines a policy that applies behaviors to the URL matcher. Implementations
+/// of <see cref="MatcherPolicy"/> and related interfaces must be registered
+/// in the dependency injection container as singleton services of type
+/// <see cref="MatcherPolicy"/>.
+/// </summary>
+/// <remarks>
+/// <see cref="MatcherPolicy"/> implementations can implement the following
+/// interfaces <see cref="IEndpointComparerPolicy"/>, <see cref="IEndpointSelectorPolicy"/>,
+/// and <see cref="INodeBuilderPolicy"/>.
+/// </remarks>
+public abstract class MatcherPolicy
{
/// <summary>
- /// Defines a policy that applies behaviors to the URL matcher. Implementations
- /// of <see cref="MatcherPolicy"/> and related interfaces must be registered
- /// in the dependency injection container as singleton services of type
- /// <see cref="MatcherPolicy"/>.
+ /// Gets a value that determines the order the <see cref="MatcherPolicy"/> should
+ /// be applied. Policies are applied in ascending numeric value of the <see cref="Order"/>
+ /// property.
+ /// </summary>
+ public abstract int Order { get; }
+
+ /// <summary>
+ /// Returns a value that indicates whether the provided <paramref name="endpoints"/> contains
+ /// one or more dynamic endpoints.
/// </summary>
+ /// <param name="endpoints">The set of endpoints.</param>
+ /// <returns><c>true</c> if a dynamic endpoint is found; otherwise returns <c>false</c>.</returns>
/// <remarks>
- /// <see cref="MatcherPolicy"/> implementations can implement the following
- /// interfaces <see cref="IEndpointComparerPolicy"/>, <see cref="IEndpointSelectorPolicy"/>,
- /// and <see cref="INodeBuilderPolicy"/>.
+ /// <para>
+ /// The presence of <see cref="IDynamicEndpointMetadata"/> signifies that an endpoint that may be replaced
+ /// during processing by an <see cref="IEndpointSelectorPolicy"/>.
+ /// </para>
+ /// <para>
+ /// An implementation of <see cref="INodeBuilderPolicy"/> should also implement <see cref="IEndpointSelectorPolicy"/>
+ /// and use its <see cref="IEndpointSelectorPolicy"/> implementation when a node contains a dynamic endpoint.
+ /// <see cref="INodeBuilderPolicy"/> implementations rely on caching of data based on a static set of endpoints. This
+ /// is not possible when endpoints are replaced dynamically.
+ /// </para>
/// </remarks>
- public abstract class MatcherPolicy
+ protected static bool ContainsDynamicEndpoints(IReadOnlyList<Endpoint> endpoints)
{
- /// <summary>
- /// Gets a value that determines the order the <see cref="MatcherPolicy"/> should
- /// be applied. Policies are applied in ascending numeric value of the <see cref="Order"/>
- /// property.
- /// </summary>
- public abstract int Order { get; }
-
- /// <summary>
- /// Returns a value that indicates whether the provided <paramref name="endpoints"/> contains
- /// one or more dynamic endpoints.
- /// </summary>
- /// <param name="endpoints">The set of endpoints.</param>
- /// <returns><c>true</c> if a dynamic endpoint is found; otherwise returns <c>false</c>.</returns>
- /// <remarks>
- /// <para>
- /// The presence of <see cref="IDynamicEndpointMetadata"/> signifies that an endpoint that may be replaced
- /// during processing by an <see cref="IEndpointSelectorPolicy"/>.
- /// </para>
- /// <para>
- /// An implementation of <see cref="INodeBuilderPolicy"/> should also implement <see cref="IEndpointSelectorPolicy"/>
- /// and use its <see cref="IEndpointSelectorPolicy"/> implementation when a node contains a dynamic endpoint.
- /// <see cref="INodeBuilderPolicy"/> implementations rely on caching of data based on a static set of endpoints. This
- /// is not possible when endpoints are replaced dynamically.
- /// </para>
- /// </remarks>
- protected static bool ContainsDynamicEndpoints(IReadOnlyList<Endpoint> endpoints)
+ if (endpoints == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- for (var i = 0; i < endpoints.Count; i++)
+ for (var i = 0; i < endpoints.Count; i++)
+ {
+ var metadata = endpoints[i].Metadata.GetMetadata<IDynamicEndpointMetadata>();
+ if (metadata?.IsDynamic == true)
{
- var metadata = endpoints[i].Metadata.GetMetadata<IDynamicEndpointMetadata>();
- if (metadata?.IsDynamic == true)
- {
- return true;
- }
+ return true;
}
-
- return false;
}
+
+ return false;
}
}
diff --git a/src/Http/Routing/src/Matching/PathSegment.cs b/src/Http/Routing/src/Matching/PathSegment.cs
index a9c598ccfd..485db311a7 100644
--- a/src/Http/Routing/src/Matching/PathSegment.cs
+++ b/src/Http/Routing/src/Matching/PathSegment.cs
@@ -3,37 +3,36 @@
using System;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal readonly struct PathSegment : IEquatable<PathSegment>
{
- internal readonly struct PathSegment : IEquatable<PathSegment>
+ public readonly int Start;
+ public readonly int Length;
+
+ public PathSegment(int start, int length)
+ {
+ Start = start;
+ Length = length;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is PathSegment segment ? Equals(segment) : false;
+ }
+
+ public bool Equals(PathSegment other)
+ {
+ return Start == other.Start && Length == other.Length;
+ }
+
+ public override int GetHashCode()
+ {
+ return Start;
+ }
+
+ public override string ToString()
{
- public readonly int Start;
- public readonly int Length;
-
- public PathSegment(int start, int length)
- {
- Start = start;
- Length = length;
- }
-
- public override bool Equals(object? obj)
- {
- return obj is PathSegment segment ? Equals(segment) : false;
- }
-
- public bool Equals(PathSegment other)
- {
- return Start == other.Start && Length == other.Length;
- }
-
- public override int GetHashCode()
- {
- return Start;
- }
-
- public override string ToString()
- {
- return $"Segment({Start}:{Length})";
- }
+ return $"Segment({Start}:{Length})";
}
}
diff --git a/src/Http/Routing/src/Matching/PolicyJumpTable.cs b/src/Http/Routing/src/Matching/PolicyJumpTable.cs
index 7420a6fe75..ba98dfb039 100644
--- a/src/Http/Routing/src/Matching/PolicyJumpTable.cs
+++ b/src/Http/Routing/src/Matching/PolicyJumpTable.cs
@@ -3,22 +3,21 @@
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// Supports retrieving endpoints that fulfill a certain matcher policy.
+/// </summary>
+public abstract class PolicyJumpTable
{
/// <summary>
- /// Supports retrieving endpoints that fulfill a certain matcher policy.
+ /// Returns the destination for a given <paramref name="httpContext"/> in the current jump table.
/// </summary>
- public abstract class PolicyJumpTable
- {
- /// <summary>
- /// Returns the destination for a given <paramref name="httpContext"/> in the current jump table.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- public abstract int GetDestination(HttpContext httpContext);
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ public abstract int GetDestination(HttpContext httpContext);
- internal virtual string DebuggerToString()
- {
- return GetType().Name;
- }
+ internal virtual string DebuggerToString()
+ {
+ return GetType().Name;
}
}
diff --git a/src/Http/Routing/src/Matching/PolicyJumpTableEdge.cs b/src/Http/Routing/src/Matching/PolicyJumpTableEdge.cs
index 30d85a1232..19ba2cb4d4 100644
--- a/src/Http/Routing/src/Matching/PolicyJumpTableEdge.cs
+++ b/src/Http/Routing/src/Matching/PolicyJumpTableEdge.cs
@@ -1,33 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// Represents an entry in a <see cref="PolicyJumpTable"/>.
+/// </summary>
+public readonly struct PolicyJumpTableEdge
{
/// <summary>
- /// Represents an entry in a <see cref="PolicyJumpTable"/>.
+ /// Constructs a new <see cref="PolicyJumpTableEdge"/> instance.
/// </summary>
- public readonly struct PolicyJumpTableEdge
+ /// <param name="state">Represents the match heuristic of the policy.</param>
+ /// <param name="destination"></param>
+ public PolicyJumpTableEdge(object state, int destination)
{
- /// <summary>
- /// Constructs a new <see cref="PolicyJumpTableEdge"/> instance.
- /// </summary>
- /// <param name="state">Represents the match heuristic of the policy.</param>
- /// <param name="destination"></param>
- public PolicyJumpTableEdge(object state, int destination)
- {
- State = state ?? throw new System.ArgumentNullException(nameof(state));
- Destination = destination;
- }
+ State = state ?? throw new System.ArgumentNullException(nameof(state));
+ Destination = destination;
+ }
- /// <summary>
- /// Gets the object used to represent the match heuristic. Can be a host, HTTP method, etc.
- /// depending on the matcher policy.
- /// </summary>
- public object State { get; }
+ /// <summary>
+ /// Gets the object used to represent the match heuristic. Can be a host, HTTP method, etc.
+ /// depending on the matcher policy.
+ /// </summary>
+ public object State { get; }
- /// <summary>
- /// Gets the destination of the current entry.
- /// </summary>
- public int Destination { get; }
- }
+ /// <summary>
+ /// Gets the destination of the current entry.
+ /// </summary>
+ public int Destination { get; }
}
diff --git a/src/Http/Routing/src/Matching/PolicyNodeEdge.cs b/src/Http/Routing/src/Matching/PolicyNodeEdge.cs
index a845cdd92f..c13376064b 100644
--- a/src/Http/Routing/src/Matching/PolicyNodeEdge.cs
+++ b/src/Http/Routing/src/Matching/PolicyNodeEdge.cs
@@ -4,33 +4,32 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+/// <summary>
+/// Represents an edge in a matcher policy graph.
+/// </summary>
+public readonly struct PolicyNodeEdge
{
/// <summary>
- /// Represents an edge in a matcher policy graph.
+ /// Constructs a new <see cref="PolicyNodeEdge"/> instance.
/// </summary>
- public readonly struct PolicyNodeEdge
+ /// <param name="state">Represents the match heuristic of the policy.</param>
+ /// <param name="endpoints">Represents the endpoints that match the policy</param>
+ public PolicyNodeEdge(object state, IReadOnlyList<Endpoint> endpoints)
{
- /// <summary>
- /// Constructs a new <see cref="PolicyNodeEdge"/> instance.
- /// </summary>
- /// <param name="state">Represents the match heuristic of the policy.</param>
- /// <param name="endpoints">Represents the endpoints that match the policy</param>
- public PolicyNodeEdge(object state, IReadOnlyList<Endpoint> endpoints)
- {
- State = state ?? throw new System.ArgumentNullException(nameof(state));
- Endpoints = endpoints ?? throw new System.ArgumentNullException(nameof(endpoints));
- }
+ State = state ?? throw new System.ArgumentNullException(nameof(state));
+ Endpoints = endpoints ?? throw new System.ArgumentNullException(nameof(endpoints));
+ }
- /// <summary>
- /// Gets the endpoints that match the policy defined by <see cref="State"/>.
- /// </summary>
- public IReadOnlyList<Endpoint> Endpoints { get; }
+ /// <summary>
+ /// Gets the endpoints that match the policy defined by <see cref="State"/>.
+ /// </summary>
+ public IReadOnlyList<Endpoint> Endpoints { get; }
- /// <summary>
- /// Gets the object used to represent the match heuristic. Can be a host, HTTP method, etc.
- /// depending on the matcher policy.
- /// </summary>
- public object State { get; }
- }
+ /// <summary>
+ /// Gets the object used to represent the match heuristic. Can be a host, HTTP method, etc.
+ /// depending on the matcher policy.
+ /// </summary>
+ public object State { get; }
}
diff --git a/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs b/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs
index ccbcffed74..274f29a730 100644
--- a/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs
+++ b/src/Http/Routing/src/Matching/SingleEntryAsciiJumpTable.cs
@@ -4,52 +4,51 @@
using System;
using System.Runtime.CompilerServices;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Optimized implementation for cases where we know that we're
+// comparing to ASCII.
+internal class SingleEntryAsciiJumpTable : JumpTable
{
- // Optimized implementation for cases where we know that we're
- // comparing to ASCII.
- internal class SingleEntryAsciiJumpTable : JumpTable
+ private readonly int _defaultDestination;
+ private readonly int _exitDestination;
+ private readonly string _text;
+ private readonly int _destination;
+
+ public SingleEntryAsciiJumpTable(
+ int defaultDestination,
+ int exitDestination,
+ string text,
+ int destination)
{
- private readonly int _defaultDestination;
- private readonly int _exitDestination;
- private readonly string _text;
- private readonly int _destination;
-
- public SingleEntryAsciiJumpTable(
- int defaultDestination,
- int exitDestination,
- string text,
- int destination)
- {
- _defaultDestination = defaultDestination;
- _exitDestination = exitDestination;
- _text = text;
- _destination = destination;
- }
+ _defaultDestination = defaultDestination;
+ _exitDestination = exitDestination;
+ _text = text;
+ _destination = destination;
+ }
- public override int GetDestination(string path, PathSegment segment)
+ public override int GetDestination(string path, PathSegment segment)
+ {
+ var length = segment.Length;
+ if (length == 0)
{
- var length = segment.Length;
- if (length == 0)
- {
- return _exitDestination;
- }
-
- var text = _text;
- if (length != text.Length)
- {
- return _defaultDestination;
- }
-
- var a = path.AsSpan(segment.Start, length);
- var b = text.AsSpan();
-
- return Ascii.AsciiIgnoreCaseEquals(a, b, length) ? _destination : _defaultDestination;
+ return _exitDestination;
}
- public override string DebuggerToString()
+ var text = _text;
+ if (length != text.Length)
{
- return $"{{ {_text}: {_destination}, $+: {_defaultDestination}, $0: {_exitDestination} }}";
+ return _defaultDestination;
}
+
+ var a = path.AsSpan(segment.Start, length);
+ var b = text.AsSpan();
+
+ return Ascii.AsciiIgnoreCaseEquals(a, b, length) ? _destination : _defaultDestination;
+ }
+
+ public override string DebuggerToString()
+ {
+ return $"{{ {_text}: {_destination}, $+: {_defaultDestination}, $0: {_exitDestination} }}";
}
}
diff --git a/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs b/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs
index 1c6a255858..cf1a25ebfb 100644
--- a/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs
+++ b/src/Http/Routing/src/Matching/SingleEntryJumpTable.cs
@@ -3,52 +3,51 @@
using System;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class SingleEntryJumpTable : JumpTable
{
- internal class SingleEntryJumpTable : JumpTable
+ private readonly int _defaultDestination;
+ private readonly int _exitDestination;
+ private readonly string _text;
+ private readonly int _destination;
+
+ public SingleEntryJumpTable(
+ int defaultDestination,
+ int exitDestination,
+ string text,
+ int destination)
{
- private readonly int _defaultDestination;
- private readonly int _exitDestination;
- private readonly string _text;
- private readonly int _destination;
-
- public SingleEntryJumpTable(
- int defaultDestination,
- int exitDestination,
- string text,
- int destination)
- {
- _defaultDestination = defaultDestination;
- _exitDestination = exitDestination;
- _text = text;
- _destination = destination;
- }
+ _defaultDestination = defaultDestination;
+ _exitDestination = exitDestination;
+ _text = text;
+ _destination = destination;
+ }
- public override int GetDestination(string path, PathSegment segment)
+ public override int GetDestination(string path, PathSegment segment)
+ {
+ if (segment.Length == 0)
{
- if (segment.Length == 0)
- {
- return _exitDestination;
- }
-
- if (segment.Length == _text.Length &&
- string.Compare(
- path,
- segment.Start,
- _text,
- 0,
- segment.Length,
- StringComparison.OrdinalIgnoreCase) == 0)
- {
- return _destination;
- }
-
- return _defaultDestination;
+ return _exitDestination;
}
- public override string DebuggerToString()
+ if (segment.Length == _text.Length &&
+ string.Compare(
+ path,
+ segment.Start,
+ _text,
+ 0,
+ segment.Length,
+ StringComparison.OrdinalIgnoreCase) == 0)
{
- return $"{{ {_text}: {_destination}, $+: {_defaultDestination}, $0: {_exitDestination} }}";
+ return _destination;
}
+
+ return _defaultDestination;
+ }
+
+ public override string DebuggerToString()
+ {
+ return $"{{ {_text}: {_destination}, $+: {_defaultDestination}, $0: {_exitDestination} }}";
}
}
diff --git a/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs b/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs
index 70c4d90d7e..ba4243008c 100644
--- a/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs
+++ b/src/Http/Routing/src/Matching/ZeroEntryJumpTable.cs
@@ -1,27 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class ZeroEntryJumpTable : JumpTable
{
- internal class ZeroEntryJumpTable : JumpTable
- {
- private readonly int _defaultDestination;
- private readonly int _exitDestination;
+ private readonly int _defaultDestination;
+ private readonly int _exitDestination;
- public ZeroEntryJumpTable(int defaultDestination, int exitDestination)
- {
- _defaultDestination = defaultDestination;
- _exitDestination = exitDestination;
- }
+ public ZeroEntryJumpTable(int defaultDestination, int exitDestination)
+ {
+ _defaultDestination = defaultDestination;
+ _exitDestination = exitDestination;
+ }
- public override int GetDestination(string path, PathSegment segment)
- {
- return segment.Length == 0 ? _exitDestination : _defaultDestination;
- }
+ public override int GetDestination(string path, PathSegment segment)
+ {
+ return segment.Length == 0 ? _exitDestination : _defaultDestination;
+ }
- public override string DebuggerToString()
- {
- return $"{{ $+: {_defaultDestination}, $0: {_exitDestination} }}";
- }
+ public override string DebuggerToString()
+ {
+ return $"{{ $+: {_defaultDestination}, $0: {_exitDestination} }}";
}
}
diff --git a/src/Http/Routing/src/ModelEndpointDataSource.cs b/src/Http/Routing/src/ModelEndpointDataSource.cs
index e97f41ce90..0f9e7703b9 100644
--- a/src/Http/Routing/src/ModelEndpointDataSource.cs
+++ b/src/Http/Routing/src/ModelEndpointDataSource.cs
@@ -8,33 +8,32 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class ModelEndpointDataSource : EndpointDataSource
{
- internal class ModelEndpointDataSource : EndpointDataSource
- {
- private readonly List<DefaultEndpointConventionBuilder> _endpointConventionBuilders;
+ private readonly List<DefaultEndpointConventionBuilder> _endpointConventionBuilders;
- public ModelEndpointDataSource()
- {
- _endpointConventionBuilders = new List<DefaultEndpointConventionBuilder>();
- }
+ public ModelEndpointDataSource()
+ {
+ _endpointConventionBuilders = new List<DefaultEndpointConventionBuilder>();
+ }
- public IEndpointConventionBuilder AddEndpointBuilder(EndpointBuilder endpointBuilder)
- {
- var builder = new DefaultEndpointConventionBuilder(endpointBuilder);
- _endpointConventionBuilders.Add(builder);
+ public IEndpointConventionBuilder AddEndpointBuilder(EndpointBuilder endpointBuilder)
+ {
+ var builder = new DefaultEndpointConventionBuilder(endpointBuilder);
+ _endpointConventionBuilders.Add(builder);
- return builder;
- }
+ return builder;
+ }
- public override IChangeToken GetChangeToken()
- {
- return NullChangeToken.Singleton;
- }
+ public override IChangeToken GetChangeToken()
+ {
+ return NullChangeToken.Singleton;
+ }
- public override IReadOnlyList<Endpoint> Endpoints => _endpointConventionBuilders.Select(e => e.Build()).ToArray();
+ public override IReadOnlyList<Endpoint> Endpoints => _endpointConventionBuilders.Select(e => e.Build()).ToArray();
- // for testing
- internal IEnumerable<EndpointBuilder> EndpointBuilders => _endpointConventionBuilders.Select(b => b.EndpointBuilder);
- }
+ // for testing
+ internal IEnumerable<EndpointBuilder> EndpointBuilders => _endpointConventionBuilders.Select(b => b.EndpointBuilder);
}
diff --git a/src/Http/Routing/src/NullRouter.cs b/src/Http/Routing/src/NullRouter.cs
index 5a653a4803..e55e9a3687 100644
--- a/src/Http/Routing/src/NullRouter.cs
+++ b/src/Http/Routing/src/NullRouter.cs
@@ -3,24 +3,23 @@
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class NullRouter : IRouter
{
- internal class NullRouter : IRouter
- {
- public static readonly NullRouter Instance = new NullRouter();
+ public static readonly NullRouter Instance = new NullRouter();
- private NullRouter()
- {
- }
+ private NullRouter()
+ {
+ }
- public VirtualPathData? GetVirtualPath(VirtualPathContext context)
- {
- return null;
- }
+ public VirtualPathData? GetVirtualPath(VirtualPathContext context)
+ {
+ return null;
+ }
- public Task RouteAsync(RouteContext context)
- {
- return Task.CompletedTask;
- }
+ public Task RouteAsync(RouteContext context)
+ {
+ return Task.CompletedTask;
}
}
diff --git a/src/Http/Routing/src/ParameterPolicyActivator.cs b/src/Http/Routing/src/ParameterPolicyActivator.cs
index 7db87a8d10..b3eec68457 100644
--- a/src/Http/Routing/src/ParameterPolicyActivator.cs
+++ b/src/Http/Routing/src/ParameterPolicyActivator.cs
@@ -11,178 +11,177 @@ using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal static class ParameterPolicyActivator
{
- internal static class ParameterPolicyActivator
+ public static T ResolveParameterPolicy<T>(
+ IDictionary<string, Type> inlineParameterPolicyMap,
+ IServiceProvider serviceProvider,
+ string inlineParameterPolicy,
+ out string parameterPolicyKey)
+ where T : IParameterPolicy
{
- public static T ResolveParameterPolicy<T>(
- IDictionary<string, Type> inlineParameterPolicyMap,
- IServiceProvider serviceProvider,
- string inlineParameterPolicy,
- out string parameterPolicyKey)
- where T : IParameterPolicy
- {
- // IServiceProvider could be null
- // DefaultInlineConstraintResolver can be created without an IServiceProvider and then call this method
-
- if (inlineParameterPolicyMap == null)
- {
- throw new ArgumentNullException(nameof(inlineParameterPolicyMap));
- }
+ // IServiceProvider could be null
+ // DefaultInlineConstraintResolver can be created without an IServiceProvider and then call this method
- if (inlineParameterPolicy == null)
- {
- throw new ArgumentNullException(nameof(inlineParameterPolicy));
- }
-
- string argumentString;
- var indexOfFirstOpenParens = inlineParameterPolicy.IndexOf('(');
- if (indexOfFirstOpenParens >= 0 && inlineParameterPolicy.EndsWith(")", StringComparison.Ordinal))
- {
- parameterPolicyKey = inlineParameterPolicy.Substring(0, indexOfFirstOpenParens);
- argumentString = inlineParameterPolicy.Substring(
- indexOfFirstOpenParens + 1,
- inlineParameterPolicy.Length - indexOfFirstOpenParens - 2);
- }
- else
- {
- parameterPolicyKey = inlineParameterPolicy;
- argumentString = null;
- }
+ if (inlineParameterPolicyMap == null)
+ {
+ throw new ArgumentNullException(nameof(inlineParameterPolicyMap));
+ }
- if (!inlineParameterPolicyMap.TryGetValue(parameterPolicyKey, out var parameterPolicyType))
- {
- return default;
- }
+ if (inlineParameterPolicy == null)
+ {
+ throw new ArgumentNullException(nameof(inlineParameterPolicy));
+ }
- if (!typeof(T).IsAssignableFrom(parameterPolicyType))
- {
- if (!typeof(IParameterPolicy).IsAssignableFrom(parameterPolicyType))
- {
- // Error if type is not a parameter policy
- throw new RouteCreationException(
- Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
- parameterPolicyType, parameterPolicyKey, typeof(T).Name));
- }
+ string argumentString;
+ var indexOfFirstOpenParens = inlineParameterPolicy.IndexOf('(');
+ if (indexOfFirstOpenParens >= 0 && inlineParameterPolicy.EndsWith(")", StringComparison.Ordinal))
+ {
+ parameterPolicyKey = inlineParameterPolicy.Substring(0, indexOfFirstOpenParens);
+ argumentString = inlineParameterPolicy.Substring(
+ indexOfFirstOpenParens + 1,
+ inlineParameterPolicy.Length - indexOfFirstOpenParens - 2);
+ }
+ else
+ {
+ parameterPolicyKey = inlineParameterPolicy;
+ argumentString = null;
+ }
- // Return null if type is parameter policy but is not the exact type
- // This is used by IInlineConstraintResolver for backwards compatibility
- // e.g. looking for an IRouteConstraint but get a different IParameterPolicy type
- return default;
- }
+ if (!inlineParameterPolicyMap.TryGetValue(parameterPolicyKey, out var parameterPolicyType))
+ {
+ return default;
+ }
- try
- {
- return (T)CreateParameterPolicy(serviceProvider, parameterPolicyType, argumentString);
- }
- catch (RouteCreationException)
- {
- throw;
- }
- catch (Exception exception)
+ if (!typeof(T).IsAssignableFrom(parameterPolicyType))
+ {
+ if (!typeof(IParameterPolicy).IsAssignableFrom(parameterPolicyType))
{
+ // Error if type is not a parameter policy
throw new RouteCreationException(
- $"An error occurred while trying to create an instance of '{parameterPolicyType.FullName}'.",
- exception);
+ Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint(
+ parameterPolicyType, parameterPolicyKey, typeof(T).Name));
}
+
+ // Return null if type is parameter policy but is not the exact type
+ // This is used by IInlineConstraintResolver for backwards compatibility
+ // e.g. looking for an IRouteConstraint but get a different IParameterPolicy type
+ return default;
}
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2006:UnrecognizedReflectionPattern", Justification = "This type comes from the ConstraintMap.")]
- private static IParameterPolicy CreateParameterPolicy(IServiceProvider serviceProvider, Type parameterPolicyType, string argumentString)
+ try
+ {
+ return (T)CreateParameterPolicy(serviceProvider, parameterPolicyType, argumentString);
+ }
+ catch (RouteCreationException)
{
- ConstructorInfo activationConstructor = null;
- object[] parameters = null;
- var constructors = parameterPolicyType.GetConstructors();
+ throw;
+ }
+ catch (Exception exception)
+ {
+ throw new RouteCreationException(
+ $"An error occurred while trying to create an instance of '{parameterPolicyType.FullName}'.",
+ exception);
+ }
+ }
+
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2006:UnrecognizedReflectionPattern", Justification = "This type comes from the ConstraintMap.")]
+ private static IParameterPolicy CreateParameterPolicy(IServiceProvider serviceProvider, Type parameterPolicyType, string argumentString)
+ {
+ ConstructorInfo activationConstructor = null;
+ object[] parameters = null;
+ var constructors = parameterPolicyType.GetConstructors();
- // If there is only one constructor and it has a single parameter, pass the argument string directly
- // This is necessary for the Regex RouteConstraint to ensure that patterns are not split on commas.
- if (constructors.Length == 1 && GetNonConvertableParameterTypeCount(serviceProvider, constructors[0].GetParameters()) == 1)
+ // If there is only one constructor and it has a single parameter, pass the argument string directly
+ // This is necessary for the Regex RouteConstraint to ensure that patterns are not split on commas.
+ if (constructors.Length == 1 && GetNonConvertableParameterTypeCount(serviceProvider, constructors[0].GetParameters()) == 1)
+ {
+ activationConstructor = constructors[0];
+ parameters = ConvertArguments(serviceProvider, activationConstructor.GetParameters(), new string[] { argumentString });
+ }
+ else
+ {
+ var arguments = argumentString?.Split(',', StringSplitOptions.TrimEntries) ?? Array.Empty<string>();
+
+ // We want to find the constructors that match the number of passed in arguments
+ // We either want a single match, or a single best match. The best match is the one with the most
+ // arguments that can be resolved from DI
+ //
+ // For example, ctor(string, IService) will beat ctor(string)
+ var matchingConstructors = constructors
+ .Where(ci => GetNonConvertableParameterTypeCount(serviceProvider, ci.GetParameters()) == arguments.Length)
+ .OrderByDescending(ci => ci.GetParameters().Length)
+ .ToArray();
+
+ if (matchingConstructors.Length == 0)
{
- activationConstructor = constructors[0];
- parameters = ConvertArguments(serviceProvider, activationConstructor.GetParameters(), new string[] { argumentString });
+ throw new RouteCreationException(
+ Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor(
+ parameterPolicyType.Name, arguments.Length));
}
else
{
- var arguments = argumentString?.Split(',', StringSplitOptions.TrimEntries) ?? Array.Empty<string>();
-
- // We want to find the constructors that match the number of passed in arguments
- // We either want a single match, or a single best match. The best match is the one with the most
- // arguments that can be resolved from DI
- //
- // For example, ctor(string, IService) will beat ctor(string)
- var matchingConstructors = constructors
- .Where(ci => GetNonConvertableParameterTypeCount(serviceProvider, ci.GetParameters()) == arguments.Length)
- .OrderByDescending(ci => ci.GetParameters().Length)
- .ToArray();
-
- if (matchingConstructors.Length == 0)
+ // When there are multiple matching constructors, choose the one with the most service arguments
+ if (matchingConstructors.Length == 1
+ || matchingConstructors[0].GetParameters().Length > matchingConstructors[1].GetParameters().Length)
{
- throw new RouteCreationException(
- Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor(
- parameterPolicyType.Name, arguments.Length));
+ activationConstructor = matchingConstructors[0];
}
else
{
- // When there are multiple matching constructors, choose the one with the most service arguments
- if (matchingConstructors.Length == 1
- || matchingConstructors[0].GetParameters().Length > matchingConstructors[1].GetParameters().Length)
- {
- activationConstructor = matchingConstructors[0];
- }
- else
- {
- throw new RouteCreationException(
- Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors(
- parameterPolicyType.Name, matchingConstructors[0].GetParameters().Length));
- }
-
- parameters = ConvertArguments(serviceProvider, activationConstructor.GetParameters(), arguments);
+ throw new RouteCreationException(
+ Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors(
+ parameterPolicyType.Name, matchingConstructors[0].GetParameters().Length));
}
- }
- return (IParameterPolicy)activationConstructor.Invoke(parameters);
+ parameters = ConvertArguments(serviceProvider, activationConstructor.GetParameters(), arguments);
+ }
}
- private static int GetNonConvertableParameterTypeCount(IServiceProvider serviceProvider, ParameterInfo[] parameters)
+ return (IParameterPolicy)activationConstructor.Invoke(parameters);
+ }
+
+ private static int GetNonConvertableParameterTypeCount(IServiceProvider serviceProvider, ParameterInfo[] parameters)
+ {
+ if (serviceProvider == null)
{
- if (serviceProvider == null)
- {
- return parameters.Length;
- }
+ return parameters.Length;
+ }
- var count = 0;
- for (var i = 0; i < parameters.Length; i++)
+ var count = 0;
+ for (var i = 0; i < parameters.Length; i++)
+ {
+ if (typeof(IConvertible).IsAssignableFrom(parameters[i].ParameterType))
{
- if (typeof(IConvertible).IsAssignableFrom(parameters[i].ParameterType))
- {
- count++;
- }
+ count++;
}
-
- return count;
}
- private static object[] ConvertArguments(IServiceProvider serviceProvider, ParameterInfo[] parameterInfos, string[] arguments)
+ return count;
+ }
+
+ private static object[] ConvertArguments(IServiceProvider serviceProvider, ParameterInfo[] parameterInfos, string[] arguments)
+ {
+ var parameters = new object[parameterInfos.Length];
+ var argumentPosition = 0;
+ for (var i = 0; i < parameterInfos.Length; i++)
{
- var parameters = new object[parameterInfos.Length];
- var argumentPosition = 0;
- for (var i = 0; i < parameterInfos.Length; i++)
- {
- var parameter = parameterInfos[i];
- var parameterType = parameter.ParameterType;
+ var parameter = parameterInfos[i];
+ var parameterType = parameter.ParameterType;
- if (serviceProvider != null && !typeof(IConvertible).IsAssignableFrom(parameterType))
- {
- parameters[i] = serviceProvider.GetRequiredService(parameterType);
- }
- else
- {
- parameters[i] = Convert.ChangeType(arguments[argumentPosition], parameterType, CultureInfo.InvariantCulture);
- argumentPosition++;
- }
+ if (serviceProvider != null && !typeof(IConvertible).IsAssignableFrom(parameterType))
+ {
+ parameters[i] = serviceProvider.GetRequiredService(parameterType);
+ }
+ else
+ {
+ parameters[i] = Convert.ChangeType(arguments[argumentPosition], parameterType, CultureInfo.InvariantCulture);
+ argumentPosition++;
}
-
- return parameters;
}
+
+ return parameters;
}
}
diff --git a/src/Http/Routing/src/ParameterPolicyFactory.cs b/src/Http/Routing/src/ParameterPolicyFactory.cs
index 79d044b269..330e8da156 100644
--- a/src/Http/Routing/src/ParameterPolicyFactory.cs
+++ b/src/Http/Routing/src/ParameterPolicyFactory.cs
@@ -5,56 +5,55 @@ using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Defines an abstraction for resolving inline parameter policies as instances of <see cref="IParameterPolicy"/>.
+/// </summary>
+public abstract class ParameterPolicyFactory
{
/// <summary>
- /// Defines an abstraction for resolving inline parameter policies as instances of <see cref="IParameterPolicy"/>.
+ /// Creates a parameter policy.
+ /// </summary>
+ /// <param name="parameter">The parameter the parameter policy is being created for.</param>
+ /// <param name="inlineText">The inline text to resolve.</param>
+ /// <returns>The <see cref="IParameterPolicy"/> for the parameter.</returns>
+ public abstract IParameterPolicy Create(RoutePatternParameterPart? parameter, string inlineText);
+
+ /// <summary>
+ /// Creates a parameter policy.
+ /// </summary>
+ /// <param name="parameter">The parameter the parameter policy is being created for.</param>
+ /// <param name="parameterPolicy">An existing parameter policy.</param>
+ /// <returns>The <see cref="IParameterPolicy"/> for the parameter.</returns>
+ public abstract IParameterPolicy Create(RoutePatternParameterPart? parameter, IParameterPolicy parameterPolicy);
+
+ /// <summary>
+ /// Creates a parameter policy.
/// </summary>
- public abstract class ParameterPolicyFactory
+ /// <param name="parameter">The parameter the parameter policy is being created for.</param>
+ /// <param name="reference">The reference to resolve.</param>
+ /// <returns>The <see cref="IParameterPolicy"/> for the parameter.</returns>
+ public IParameterPolicy Create(RoutePatternParameterPart? parameter, RoutePatternParameterPolicyReference reference)
{
- /// <summary>
- /// Creates a parameter policy.
- /// </summary>
- /// <param name="parameter">The parameter the parameter policy is being created for.</param>
- /// <param name="inlineText">The inline text to resolve.</param>
- /// <returns>The <see cref="IParameterPolicy"/> for the parameter.</returns>
- public abstract IParameterPolicy Create(RoutePatternParameterPart? parameter, string inlineText);
-
- /// <summary>
- /// Creates a parameter policy.
- /// </summary>
- /// <param name="parameter">The parameter the parameter policy is being created for.</param>
- /// <param name="parameterPolicy">An existing parameter policy.</param>
- /// <returns>The <see cref="IParameterPolicy"/> for the parameter.</returns>
- public abstract IParameterPolicy Create(RoutePatternParameterPart? parameter, IParameterPolicy parameterPolicy);
-
- /// <summary>
- /// Creates a parameter policy.
- /// </summary>
- /// <param name="parameter">The parameter the parameter policy is being created for.</param>
- /// <param name="reference">The reference to resolve.</param>
- /// <returns>The <see cref="IParameterPolicy"/> for the parameter.</returns>
- public IParameterPolicy Create(RoutePatternParameterPart? parameter, RoutePatternParameterPolicyReference reference)
+ if (reference == null)
{
- if (reference == null)
- {
- throw new ArgumentNullException(nameof(reference));
- }
-
- Debug.Assert(reference.ParameterPolicy != null || reference.Content != null);
+ throw new ArgumentNullException(nameof(reference));
+ }
- if (reference.ParameterPolicy != null)
- {
- return Create(parameter, reference.ParameterPolicy);
- }
+ Debug.Assert(reference.ParameterPolicy != null || reference.Content != null);
- if (reference.Content != null)
- {
- return Create(parameter, reference.Content);
- }
+ if (reference.ParameterPolicy != null)
+ {
+ return Create(parameter, reference.ParameterPolicy);
+ }
- // Unreachable
- throw new NotSupportedException();
+ if (reference.Content != null)
+ {
+ return Create(parameter, reference.Content);
}
+
+ // Unreachable
+ throw new NotSupportedException();
}
}
diff --git a/src/Http/Routing/src/PathTokenizer.cs b/src/Http/Routing/src/PathTokenizer.cs
index 1bb76dbf72..c6f04b463d 100644
--- a/src/Http/Routing/src/PathTokenizer.cs
+++ b/src/Http/Routing/src/PathTokenizer.cs
@@ -10,198 +10,197 @@ using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal struct PathTokenizer : IReadOnlyList<StringSegment>
{
- internal struct PathTokenizer : IReadOnlyList<StringSegment>
- {
- private readonly string _path;
- private int _count;
+ private readonly string _path;
+ private int _count;
- public PathTokenizer(PathString path)
- {
- _path = path.Value;
- _count = -1;
- }
+ public PathTokenizer(PathString path)
+ {
+ _path = path.Value;
+ _count = -1;
+ }
- public int Count
+ public int Count
+ {
+ get
{
- get
+ if (_count == -1)
{
- if (_count == -1)
+ // We haven't computed the real count of segments yet.
+ if (_path.Length == 0)
{
- // We haven't computed the real count of segments yet.
- if (_path.Length == 0)
- {
- // The empty string has length of 0.
- _count = 0;
- return _count;
- }
+ // The empty string has length of 0.
+ _count = 0;
+ return _count;
+ }
- // A string of length 1 must be "/" - all PathStrings start with '/'
- if (_path.Length == 1)
- {
- // We treat this as empty - there's nothing to parse here for routing, because routing ignores
- // a trailing slash.
- Debug.Assert(_path[0] == '/');
- _count = 0;
- return _count;
- }
+ // A string of length 1 must be "/" - all PathStrings start with '/'
+ if (_path.Length == 1)
+ {
+ // We treat this as empty - there's nothing to parse here for routing, because routing ignores
+ // a trailing slash.
+ Debug.Assert(_path[0] == '/');
+ _count = 0;
+ return _count;
+ }
- // This is a non-trivial PathString
- _count = 1;
+ // This is a non-trivial PathString
+ _count = 1;
- // Since a non-empty PathString must begin with a `/`, we can just count the number of occurrences
- // of `/` to find the number of segments. However, we don't look at the last character, because
- // routing ignores a trailing slash.
- for (var i = 1; i < _path.Length - 1; i++)
+ // Since a non-empty PathString must begin with a `/`, we can just count the number of occurrences
+ // of `/` to find the number of segments. However, we don't look at the last character, because
+ // routing ignores a trailing slash.
+ for (var i = 1; i < _path.Length - 1; i++)
+ {
+ if (_path[i] == '/')
{
- if (_path[i] == '/')
- {
- _count++;
- }
+ _count++;
}
}
-
- return _count;
}
+
+ return _count;
}
+ }
- public StringSegment this[int index]
+ public StringSegment this[int index]
+ {
+ get
{
- get
+ if (index >= Count)
{
- if (index >= Count)
- {
- throw new IndexOutOfRangeException();
- }
+ throw new IndexOutOfRangeException();
+ }
- var currentSegmentIndex = 0;
- var currentSegmentStart = 1;
+ var currentSegmentIndex = 0;
+ var currentSegmentStart = 1;
- // Skip the first `/`.
- var delimiterIndex = 1;
- while ((delimiterIndex = _path.IndexOf('/', delimiterIndex)) != -1)
+ // Skip the first `/`.
+ var delimiterIndex = 1;
+ while ((delimiterIndex = _path.IndexOf('/', delimiterIndex)) != -1)
+ {
+ if (currentSegmentIndex++ == index)
{
- if (currentSegmentIndex++ == index)
- {
- return new StringSegment(_path, currentSegmentStart, delimiterIndex - currentSegmentStart);
- }
- else
- {
- currentSegmentStart = delimiterIndex + 1;
- delimiterIndex++;
- }
+ return new StringSegment(_path, currentSegmentStart, delimiterIndex - currentSegmentStart);
+ }
+ else
+ {
+ currentSegmentStart = delimiterIndex + 1;
+ delimiterIndex++;
}
+ }
- // If we get here we're at the end of the string. The implementation of .Count should protect us
- // from these cases.
- Debug.Assert(_path[_path.Length - 1] != '/');
- Debug.Assert(currentSegmentIndex == index);
+ // If we get here we're at the end of the string. The implementation of .Count should protect us
+ // from these cases.
+ Debug.Assert(_path[_path.Length - 1] != '/');
+ Debug.Assert(currentSegmentIndex == index);
- return new StringSegment(_path, currentSegmentStart, _path.Length - currentSegmentStart);
- }
+ return new StringSegment(_path, currentSegmentStart, _path.Length - currentSegmentStart);
}
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ IEnumerator<StringSegment> IEnumerable<StringSegment>.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
- public Enumerator GetEnumerator()
+ public struct Enumerator : IEnumerator<StringSegment>
+ {
+ private readonly string _path;
+
+ private int _index;
+ private int _length;
+
+ public Enumerator(PathTokenizer tokenizer)
{
- return new Enumerator(this);
+ _path = tokenizer._path;
+
+ _index = -1;
+ _length = -1;
}
- IEnumerator<StringSegment> IEnumerable<StringSegment>.GetEnumerator()
+ public StringSegment Current
{
- return GetEnumerator();
+ get
+ {
+ return new StringSegment(_path, _index, _length);
+ }
}
- IEnumerator IEnumerable.GetEnumerator()
+ object IEnumerator.Current
{
- return GetEnumerator();
+ get
+ {
+ return Current;
+ }
}
- public struct Enumerator : IEnumerator<StringSegment>
+ public void Dispose()
{
- private readonly string _path;
-
- private int _index;
- private int _length;
+ }
- public Enumerator(PathTokenizer tokenizer)
+ public bool MoveNext()
+ {
+ if (_path == null || _path.Length <= 1)
{
- _path = tokenizer._path;
-
- _index = -1;
- _length = -1;
+ return false;
}
- public StringSegment Current
+ if (_index == -1)
{
- get
- {
- return new StringSegment(_path, _index, _length);
- }
+ // Skip the first `/`.
+ _index = 1;
}
-
- object IEnumerator.Current
+ else
{
- get
- {
- return Current;
- }
+ // Skip to the end of the previous segment + the separator.
+ _index += _length + 1;
}
- public void Dispose()
+ if (_index >= _path.Length)
{
+ // We're at the end
+ return false;
}
- public bool MoveNext()
+ var delimiterIndex = _path.IndexOf('/', _index);
+ if (delimiterIndex != -1)
{
- if (_path == null || _path.Length <= 1)
- {
- return false;
- }
-
- if (_index == -1)
- {
- // Skip the first `/`.
- _index = 1;
- }
- else
- {
- // Skip to the end of the previous segment + the separator.
- _index += _length + 1;
- }
-
- if (_index >= _path.Length)
- {
- // We're at the end
- return false;
- }
-
- var delimiterIndex = _path.IndexOf('/', _index);
- if (delimiterIndex != -1)
- {
- _length = delimiterIndex - _index;
- return true;
- }
-
- // We might have some trailing text after the last separator.
- if (_path[_path.Length - 1] == '/')
- {
- // If the last char is a '/' then it's just a trailing slash, we don't have another segment.
- return false;
- }
- else
- {
- _length = _path.Length - _index;
- return true;
- }
+ _length = delimiterIndex - _index;
+ return true;
}
- public void Reset()
+ // We might have some trailing text after the last separator.
+ if (_path[_path.Length - 1] == '/')
{
- _index = -1;
- _length = -1;
+ // If the last char is a '/' then it's just a trailing slash, we don't have another segment.
+ return false;
}
+ else
+ {
+ _length = _path.Length - _index;
+ return true;
+ }
+ }
+
+ public void Reset()
+ {
+ _index = -1;
+ _length = -1;
}
}
}
diff --git a/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs b/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs
index 7c7b07e825..14a960c856 100644
--- a/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs
+++ b/src/Http/Routing/src/Patterns/DefaultRoutePatternTransformer.cs
@@ -6,245 +6,244 @@
using System;
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+internal class DefaultRoutePatternTransformer : RoutePatternTransformer
{
- internal class DefaultRoutePatternTransformer : RoutePatternTransformer
- {
- private readonly ParameterPolicyFactory _policyFactory;
+ private readonly ParameterPolicyFactory _policyFactory;
- public DefaultRoutePatternTransformer(ParameterPolicyFactory policyFactory)
+ public DefaultRoutePatternTransformer(ParameterPolicyFactory policyFactory)
+ {
+ if (policyFactory == null)
{
- if (policyFactory == null)
- {
- throw new ArgumentNullException(nameof(policyFactory));
- }
-
- _policyFactory = policyFactory;
+ throw new ArgumentNullException(nameof(policyFactory));
}
- public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
- {
- if (original == null)
- {
- throw new ArgumentNullException(nameof(original));
- }
+ _policyFactory = policyFactory;
+ }
- return SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues));
+ public override RoutePattern SubstituteRequiredValues(RoutePattern original, object requiredValues)
+ {
+ if (original == null)
+ {
+ throw new ArgumentNullException(nameof(original));
}
- private RoutePattern SubstituteRequiredValuesCore(RoutePattern original, RouteValueDictionary requiredValues)
+ return SubstituteRequiredValuesCore(original, new RouteValueDictionary(requiredValues));
+ }
+
+ private RoutePattern SubstituteRequiredValuesCore(RoutePattern original, RouteValueDictionary requiredValues)
+ {
+ // Process each required value in sequence. Bail if we find any rejection criteria. The goal
+ // of rejection is to avoid creating RoutePattern instances that can't *ever* match.
+ //
+ // If we succeed, then we need to create a new RoutePattern with the provided required values.
+ //
+ // Substitution can merge with existing RequiredValues already on the RoutePattern as long
+ // as all of the success criteria are still met at the end.
+ foreach (var kvp in requiredValues)
{
- // Process each required value in sequence. Bail if we find any rejection criteria. The goal
- // of rejection is to avoid creating RoutePattern instances that can't *ever* match.
- //
- // If we succeed, then we need to create a new RoutePattern with the provided required values.
+ // There are three possible cases here:
+ // 1. Required value is null-ish
+ // 2. Required value is *any*
+ // 3. Required value corresponds to a parameter
+ // 4. Required value corresponds to a matching default value
//
- // Substitution can merge with existing RequiredValues already on the RoutePattern as long
- // as all of the success criteria are still met at the end.
- foreach (var kvp in requiredValues)
+ // If none of these are true then we can reject this substitution.
+ RoutePatternParameterPart parameter;
+ if (RouteValueEqualityComparer.Default.Equals(kvp.Value, string.Empty))
{
- // There are three possible cases here:
- // 1. Required value is null-ish
- // 2. Required value is *any*
- // 3. Required value corresponds to a parameter
- // 4. Required value corresponds to a matching default value
- //
- // If none of these are true then we can reject this substitution.
- RoutePatternParameterPart parameter;
- if (RouteValueEqualityComparer.Default.Equals(kvp.Value, string.Empty))
- {
- // 1. Required value is null-ish - check to make sure that this route doesn't have a
- // parameter or filter-like default.
+ // 1. Required value is null-ish - check to make sure that this route doesn't have a
+ // parameter or filter-like default.
- if (original.GetParameter(kvp.Key) != null)
- {
- // Fail: we can't 'require' that a parameter be null. In theory this would be possible
- // for an optional parameter, but that's not really in line with the usage of this feature
- // so we don't handle it.
- //
- // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = "" }
- return null;
- }
- else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
- !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
- {
- // Fail: this route has a non-parameter default that doesn't match.
- //
- // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = "" }
- return null;
- }
-
- // Success: (for this parameter at least)
+ if (original.GetParameter(kvp.Key) != null)
+ {
+ // Fail: we can't 'require' that a parameter be null. In theory this would be possible
+ // for an optional parameter, but that's not really in line with the usage of this feature
+ // so we don't handle it.
//
- // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
- continue;
+ // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = "" }
+ return null;
}
- else if (RoutePattern.IsRequiredValueAny(kvp.Value))
+ else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
{
- // 2. Required value is *any* - this is allowed for a parameter with a default, but not
- // a non-parameter default.
- if (original.GetParameter(kvp.Key) == null &&
- original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
- !RouteValueEqualityComparer.Default.Equals(string.Empty, defaultValue))
- {
- // Fail: this route as a non-parameter default that is stricter than *any*.
- //
- // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = *any* }
- return null;
- }
-
- // Success: (for this parameter at least)
+ // Fail: this route has a non-parameter default that doesn't match.
//
- // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = *any*, ... }
- continue;
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = "" }
+ return null;
}
- else if ((parameter = original.GetParameter(kvp.Key)) != null)
- {
- // 3. Required value corresponds to a parameter - check to make sure that this value matches
- // any IRouteConstraint implementations.
- if (!MatchesConstraints(original, parameter, kvp.Key, requiredValues))
- {
- // Fail: this route has a constraint that failed.
- //
- // Ex: Admin/{controller:regex(Home|Login)}/{action=Index}/{id?} - with required values: { controller = "Store" }
- return null;
- }
- // Success: (for this parameter at least)
+ // Success: (for this parameter at least)
+ //
+ // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
+ continue;
+ }
+ else if (RoutePattern.IsRequiredValueAny(kvp.Value))
+ {
+ // 2. Required value is *any* - this is allowed for a parameter with a default, but not
+ // a non-parameter default.
+ if (original.GetParameter(kvp.Key) == null &&
+ original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ !RouteValueEqualityComparer.Default.Equals(string.Empty, defaultValue))
+ {
+ // Fail: this route as a non-parameter default that is stricter than *any*.
//
- // Ex: {area}/{controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
- continue;
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" } - with required values: { area = *any* }
+ return null;
}
- else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
- RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
- {
- // 4. Required value corresponds to a matching default value - check to make sure that this value matches
- // any IRouteConstraint implementations. It's unlikely that this would happen in practice but it doesn't
- // hurt for us to check.
- if (!MatchesConstraints(original, parameter: null, kvp.Key, requiredValues))
- {
- // Fail: this route has a constraint that failed.
- //
- // Ex:
- // Admin/Home/{action=Index}/{id?}
- // defaults: { area = "Admin" }
- // constraints: { area = "Blog" }
- // with required values: { area = "Admin" }
- return null;
- }
- // Success: (for this parameter at least)
+ // Success: (for this parameter at least)
+ //
+ // Ex: {controller=Home}/{action=Index}/{id?} - with required values: { controller = *any*, ... }
+ continue;
+ }
+ else if ((parameter = original.GetParameter(kvp.Key)) != null)
+ {
+ // 3. Required value corresponds to a parameter - check to make sure that this value matches
+ // any IRouteConstraint implementations.
+ if (!MatchesConstraints(original, parameter, kvp.Key, requiredValues))
+ {
+ // Fail: this route has a constraint that failed.
//
- // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Admin", ... }
- continue;
+ // Ex: Admin/{controller:regex(Home|Login)}/{action=Index}/{id?} - with required values: { controller = "Store" }
+ return null;
}
- else
+
+ // Success: (for this parameter at least)
+ //
+ // Ex: {area}/{controller=Home}/{action=Index}/{id?} - with required values: { area = "", ... }
+ continue;
+ }
+ else if (original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ {
+ // 4. Required value corresponds to a matching default value - check to make sure that this value matches
+ // any IRouteConstraint implementations. It's unlikely that this would happen in practice but it doesn't
+ // hurt for us to check.
+ if (!MatchesConstraints(original, parameter: null, kvp.Key, requiredValues))
{
- // Fail: this is a required value for a key that doesn't appear in the templates, or the route
- // pattern has a different default value for a non-parameter.
+ // Fail: this route has a constraint that failed.
//
- // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Blog", ... }
- // OR (less likely)
- // Ex: Admin/{controller=Home}/{action=Index}/{id?} with required values: { page = "/Index", ... }
+ // Ex:
+ // Admin/Home/{action=Index}/{id?}
+ // defaults: { area = "Admin" }
+ // constraints: { area = "Blog" }
+ // with required values: { area = "Admin" }
return null;
}
+
+ // Success: (for this parameter at least)
+ //
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Admin", ... }
+ continue;
+ }
+ else
+ {
+ // Fail: this is a required value for a key that doesn't appear in the templates, or the route
+ // pattern has a different default value for a non-parameter.
+ //
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} defaults: { area = "Admin" }- with required values: { area = "Blog", ... }
+ // OR (less likely)
+ // Ex: Admin/{controller=Home}/{action=Index}/{id?} with required values: { page = "/Index", ... }
+ return null;
}
+ }
- List<RoutePatternParameterPart> updatedParameters = null;
- List<RoutePatternPathSegment> updatedSegments = null;
- RouteValueDictionary updatedDefaults = null;
+ List<RoutePatternParameterPart> updatedParameters = null;
+ List<RoutePatternPathSegment> updatedSegments = null;
+ RouteValueDictionary updatedDefaults = null;
- // So if we get here, we're ready to update the route pattern. We need to update two things:
- // 1. Remove any default values that conflict with the required values.
- // 2. Merge any existing required values
- foreach (var kvp in requiredValues)
- {
- var parameter = original.GetParameter(kvp.Key);
+ // So if we get here, we're ready to update the route pattern. We need to update two things:
+ // 1. Remove any default values that conflict with the required values.
+ // 2. Merge any existing required values
+ foreach (var kvp in requiredValues)
+ {
+ var parameter = original.GetParameter(kvp.Key);
- // We only need to handle the case where the required value maps to a parameter. That's the only
- // case where we allow a default and a required value to disagree, and we already validated the
- // other cases.
- //
- // If the required value is *any* then don't remove the default.
- if (parameter != null &&
- !RoutePattern.IsRequiredValueAny(kvp.Value) &&
- original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
- !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ // We only need to handle the case where the required value maps to a parameter. That's the only
+ // case where we allow a default and a required value to disagree, and we already validated the
+ // other cases.
+ //
+ // If the required value is *any* then don't remove the default.
+ if (parameter != null &&
+ !RoutePattern.IsRequiredValueAny(kvp.Value) &&
+ original.Defaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ !RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
+ {
+ if (updatedDefaults == null && updatedSegments == null && updatedParameters == null)
{
- if (updatedDefaults == null && updatedSegments == null && updatedParameters == null)
- {
- updatedDefaults = new RouteValueDictionary(original.Defaults);
- updatedSegments = new List<RoutePatternPathSegment>(original.PathSegments);
- updatedParameters = new List<RoutePatternParameterPart>(original.Parameters);
- }
-
- updatedDefaults.Remove(kvp.Key);
- RemoveParameterDefault(updatedSegments, updatedParameters, parameter);
+ updatedDefaults = new RouteValueDictionary(original.Defaults);
+ updatedSegments = new List<RoutePatternPathSegment>(original.PathSegments);
+ updatedParameters = new List<RoutePatternParameterPart>(original.Parameters);
}
- }
- foreach (var kvp in original.RequiredValues)
- {
- requiredValues.TryAdd(kvp.Key, kvp.Value);
+ updatedDefaults.Remove(kvp.Key);
+ RemoveParameterDefault(updatedSegments, updatedParameters, parameter);
}
+ }
- return new RoutePattern(
- original.RawText,
- updatedDefaults ?? original.Defaults,
- original.ParameterPolicies,
- requiredValues,
- updatedParameters ?? original.Parameters,
- updatedSegments ?? original.PathSegments);
+ foreach (var kvp in original.RequiredValues)
+ {
+ requiredValues.TryAdd(kvp.Key, kvp.Value);
}
- private bool MatchesConstraints(RoutePattern pattern, RoutePatternParameterPart parameter, string key, RouteValueDictionary requiredValues)
+ return new RoutePattern(
+ original.RawText,
+ updatedDefaults ?? original.Defaults,
+ original.ParameterPolicies,
+ requiredValues,
+ updatedParameters ?? original.Parameters,
+ updatedSegments ?? original.PathSegments);
+ }
+
+ private bool MatchesConstraints(RoutePattern pattern, RoutePatternParameterPart parameter, string key, RouteValueDictionary requiredValues)
+ {
+ if (pattern.ParameterPolicies.TryGetValue(key, out var policies))
{
- if (pattern.ParameterPolicies.TryGetValue(key, out var policies))
+ for (var i = 0; i < policies.Count; i++)
{
- for (var i = 0; i < policies.Count; i++)
+ var policy = _policyFactory.Create(parameter, policies[i]);
+ if (policy is IRouteConstraint constraint)
{
- var policy = _policyFactory.Create(parameter, policies[i]);
- if (policy is IRouteConstraint constraint)
+ if (!constraint.Match(httpContext: null, NullRouter.Instance, key, requiredValues, RouteDirection.IncomingRequest))
{
- if (!constraint.Match(httpContext: null, NullRouter.Instance, key, requiredValues, RouteDirection.IncomingRequest))
- {
- return false;
- }
+ return false;
}
}
}
-
- return true;
}
- private static void RemoveParameterDefault(List<RoutePatternPathSegment> segments, List<RoutePatternParameterPart> parameters, RoutePatternParameterPart parameter)
+ return true;
+ }
+
+ private static void RemoveParameterDefault(List<RoutePatternPathSegment> segments, List<RoutePatternParameterPart> parameters, RoutePatternParameterPart parameter)
+ {
+ // We know that a parameter can only appear once, so we only need to rewrite one segment and one parameter.
+ for (var i = 0; i < segments.Count; i++)
{
- // We know that a parameter can only appear once, so we only need to rewrite one segment and one parameter.
- for (var i = 0; i < segments.Count; i++)
+ var segment = segments[i];
+ for (var j = 0; j < segment.Parts.Count; j++)
{
- var segment = segments[i];
- for (var j = 0; j < segment.Parts.Count; j++)
+ if (object.ReferenceEquals(parameter, segment.Parts[j]))
{
- if (object.ReferenceEquals(parameter, segment.Parts[j]))
- {
- // Found it!
- var updatedParameter = RoutePatternFactory.ParameterPart(parameter.Name, @default: null, parameter.ParameterKind, parameter.ParameterPolicies);
+ // Found it!
+ var updatedParameter = RoutePatternFactory.ParameterPart(parameter.Name, @default: null, parameter.ParameterKind, parameter.ParameterPolicies);
- var updatedParts = new List<RoutePatternPart>(segment.Parts);
- updatedParts[j] = updatedParameter;
- segments[i] = RoutePatternFactory.Segment(updatedParts);
+ var updatedParts = new List<RoutePatternPart>(segment.Parts);
+ updatedParts[j] = updatedParameter;
+ segments[i] = RoutePatternFactory.Segment(updatedParts);
- for (var k = 0; k < parameters.Count; k++)
+ for (var k = 0; k < parameters.Count; k++)
+ {
+ if (ReferenceEquals(parameter, parameters[k]))
{
- if (ReferenceEquals(parameter, parameters[k]))
- {
- parameters[k] = updatedParameter;
- break;
- }
+ parameters[k] = updatedParameter;
+ break;
}
-
- return;
}
+
+ return;
}
}
}
diff --git a/src/Http/Routing/src/Patterns/RouteParameterParser.cs b/src/Http/Routing/src/Patterns/RouteParameterParser.cs
index 220eccc4d6..ec6d4d490f 100644
--- a/src/Http/Routing/src/Patterns/RouteParameterParser.cs
+++ b/src/Http/Routing/src/Patterns/RouteParameterParser.cs
@@ -3,256 +3,255 @@
using System;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+internal static class RouteParameterParser
{
- internal static class RouteParameterParser
+ // This code parses the inside of the route parameter
+ //
+ // Ex: {hello} - this method is responsible for parsing 'hello'
+ // The factoring between this class and RoutePatternParser is due to legacy.
+ public static RoutePatternParameterPart ParseRouteParameter(string parameter)
{
- // This code parses the inside of the route parameter
- //
- // Ex: {hello} - this method is responsible for parsing 'hello'
- // The factoring between this class and RoutePatternParser is due to legacy.
- public static RoutePatternParameterPart ParseRouteParameter(string parameter)
+ if (parameter == null)
{
- if (parameter == null)
- {
- throw new ArgumentNullException(nameof(parameter));
- }
-
- if (parameter.Length == 0)
- {
- return new RoutePatternParameterPart(string.Empty, null, RoutePatternParameterKind.Standard, Array.Empty<RoutePatternParameterPolicyReference>());
- }
+ throw new ArgumentNullException(nameof(parameter));
+ }
- var startIndex = 0;
- var endIndex = parameter.Length - 1;
- var encodeSlashes = true;
+ if (parameter.Length == 0)
+ {
+ return new RoutePatternParameterPart(string.Empty, null, RoutePatternParameterKind.Standard, Array.Empty<RoutePatternParameterPolicyReference>());
+ }
- var parameterKind = RoutePatternParameterKind.Standard;
+ var startIndex = 0;
+ var endIndex = parameter.Length - 1;
+ var encodeSlashes = true;
- if (parameter.StartsWith("**", StringComparison.Ordinal))
- {
- encodeSlashes = false;
- parameterKind = RoutePatternParameterKind.CatchAll;
- startIndex += 2;
- }
- else if (parameter[0] == '*')
- {
- parameterKind = RoutePatternParameterKind.CatchAll;
- startIndex++;
- }
+ var parameterKind = RoutePatternParameterKind.Standard;
- if (parameter[endIndex] == '?')
- {
- parameterKind = RoutePatternParameterKind.Optional;
- endIndex--;
- }
+ if (parameter.StartsWith("**", StringComparison.Ordinal))
+ {
+ encodeSlashes = false;
+ parameterKind = RoutePatternParameterKind.CatchAll;
+ startIndex += 2;
+ }
+ else if (parameter[0] == '*')
+ {
+ parameterKind = RoutePatternParameterKind.CatchAll;
+ startIndex++;
+ }
- var currentIndex = startIndex;
+ if (parameter[endIndex] == '?')
+ {
+ parameterKind = RoutePatternParameterKind.Optional;
+ endIndex--;
+ }
- // Parse parameter name
- var parameterName = string.Empty;
+ var currentIndex = startIndex;
- while (currentIndex <= endIndex)
- {
- var currentChar = parameter[currentIndex];
+ // Parse parameter name
+ var parameterName = string.Empty;
- if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
- {
- // Parameter names are allowed to start with delimiters used to denote constraints or default values.
- // i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
- // specifications.
- parameterName = parameter.Substring(startIndex, currentIndex - startIndex);
+ while (currentIndex <= endIndex)
+ {
+ var currentChar = parameter[currentIndex];
- // Roll the index back and move to the constraint parsing stage.
- currentIndex--;
- break;
- }
- else if (currentIndex == endIndex)
- {
- parameterName = parameter.Substring(startIndex, currentIndex - startIndex + 1);
- }
+ if ((currentChar == ':' || currentChar == '=') && startIndex != currentIndex)
+ {
+ // Parameter names are allowed to start with delimiters used to denote constraints or default values.
+ // i.e. "=foo" or ":bar" would be treated as parameter names rather than default value or constraint
+ // specifications.
+ parameterName = parameter.Substring(startIndex, currentIndex - startIndex);
- currentIndex++;
+ // Roll the index back and move to the constraint parsing stage.
+ currentIndex--;
+ break;
}
-
- var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
- currentIndex = parseResults.CurrentIndex;
-
- string? defaultValue = null;
- if (currentIndex <= endIndex &&
- parameter[currentIndex] == '=')
+ else if (currentIndex == endIndex)
{
- defaultValue = parameter.Substring(currentIndex + 1, endIndex - currentIndex);
+ parameterName = parameter.Substring(startIndex, currentIndex - startIndex + 1);
}
- return new RoutePatternParameterPart(
- parameterName,
- defaultValue,
- parameterKind,
- parseResults.ParameterPolicies,
- encodeSlashes);
+ currentIndex++;
}
- private static ParameterPolicyParseResults ParseConstraints(
- string text,
- int currentIndex,
- int endIndex)
+ var parseResults = ParseConstraints(parameter, currentIndex, endIndex);
+ currentIndex = parseResults.CurrentIndex;
+
+ string? defaultValue = null;
+ if (currentIndex <= endIndex &&
+ parameter[currentIndex] == '=')
{
- var constraints = new ArrayBuilder<RoutePatternParameterPolicyReference>(0);
- var state = ParseState.Start;
- var startIndex = currentIndex;
- do
- {
- var currentChar = currentIndex > endIndex ? null : (char?)text[currentIndex];
- switch (state)
- {
- case ParseState.Start:
- switch (currentChar)
- {
- case null:
- state = ParseState.End;
- break;
- case ':':
- state = ParseState.ParsingName;
- startIndex = currentIndex + 1;
- break;
- case '(':
- state = ParseState.InsideParenthesis;
- break;
- case '=':
- state = ParseState.End;
- currentIndex--;
- break;
- }
- break;
- case ParseState.InsideParenthesis:
- switch (currentChar)
- {
- case null:
- state = ParseState.End;
- var constraintText = text.Substring(startIndex, currentIndex - startIndex);
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
- break;
- case ')':
- // Only consume a ')' token if
- // (a) it is the last token
- // (b) the next character is the start of the new constraint ':'
- // (c) the next character is the start of the default value.
+ defaultValue = parameter.Substring(currentIndex + 1, endIndex - currentIndex);
+ }
- var nextChar = currentIndex + 1 > endIndex ? null : (char?)text[currentIndex + 1];
- switch (nextChar)
- {
- case null:
- state = ParseState.End;
- constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
- break;
- case ':':
- state = ParseState.Start;
- constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
- startIndex = currentIndex + 1;
- break;
- case '=':
- state = ParseState.End;
- constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
- break;
- }
- break;
- case ':':
- case '=':
- // In the original implementation, the Regex would've backtracked if it encountered an
- // unbalanced opening bracket followed by (not necessarily immediately) a delimiter.
- // Simply verifying that the parentheses will eventually be closed should suffice to
- // determine if the terminator needs to be consumed as part of the current constraint
- // specification.
- var indexOfClosingParantheses = text.IndexOf(')', currentIndex + 1);
- if (indexOfClosingParantheses == -1)
- {
- constraintText = text.Substring(startIndex, currentIndex - startIndex);
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ return new RoutePatternParameterPart(
+ parameterName,
+ defaultValue,
+ parameterKind,
+ parseResults.ParameterPolicies,
+ encodeSlashes);
+ }
- if (currentChar == ':')
- {
- state = ParseState.ParsingName;
- startIndex = currentIndex + 1;
- }
- else
- {
- state = ParseState.End;
- currentIndex--;
- }
- }
- else
- {
- currentIndex = indexOfClosingParantheses;
- }
+ private static ParameterPolicyParseResults ParseConstraints(
+ string text,
+ int currentIndex,
+ int endIndex)
+ {
+ var constraints = new ArrayBuilder<RoutePatternParameterPolicyReference>(0);
+ var state = ParseState.Start;
+ var startIndex = currentIndex;
+ do
+ {
+ var currentChar = currentIndex > endIndex ? null : (char?)text[currentIndex];
+ switch (state)
+ {
+ case ParseState.Start:
+ switch (currentChar)
+ {
+ case null:
+ state = ParseState.End;
+ break;
+ case ':':
+ state = ParseState.ParsingName;
+ startIndex = currentIndex + 1;
+ break;
+ case '(':
+ state = ParseState.InsideParenthesis;
+ break;
+ case '=':
+ state = ParseState.End;
+ currentIndex--;
+ break;
+ }
+ break;
+ case ParseState.InsideParenthesis:
+ switch (currentChar)
+ {
+ case null:
+ state = ParseState.End;
+ var constraintText = text.Substring(startIndex, currentIndex - startIndex);
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ break;
+ case ')':
+ // Only consume a ')' token if
+ // (a) it is the last token
+ // (b) the next character is the start of the new constraint ':'
+ // (c) the next character is the start of the default value.
- break;
- }
- break;
- case ParseState.ParsingName:
- switch (currentChar)
- {
- case null:
- state = ParseState.End;
- var constraintText = text.Substring(startIndex, currentIndex - startIndex);
- if (constraintText.Length > 0)
- {
+ var nextChar = currentIndex + 1 > endIndex ? null : (char?)text[currentIndex + 1];
+ switch (nextChar)
+ {
+ case null:
+ state = ParseState.End;
+ constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
- }
- break;
- case ':':
+ break;
+ case ':':
+ state = ParseState.Start;
+ constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ startIndex = currentIndex + 1;
+ break;
+ case '=':
+ state = ParseState.End;
+ constraintText = text.Substring(startIndex, currentIndex - startIndex + 1);
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ break;
+ }
+ break;
+ case ':':
+ case '=':
+ // In the original implementation, the Regex would've backtracked if it encountered an
+ // unbalanced opening bracket followed by (not necessarily immediately) a delimiter.
+ // Simply verifying that the parentheses will eventually be closed should suffice to
+ // determine if the terminator needs to be consumed as part of the current constraint
+ // specification.
+ var indexOfClosingParantheses = text.IndexOf(')', currentIndex + 1);
+ if (indexOfClosingParantheses == -1)
+ {
constraintText = text.Substring(startIndex, currentIndex - startIndex);
- if (constraintText.Length > 0)
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+
+ if (currentChar == ':')
{
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ state = ParseState.ParsingName;
+ startIndex = currentIndex + 1;
}
- startIndex = currentIndex + 1;
- break;
- case '(':
- state = ParseState.InsideParenthesis;
- break;
- case '=':
- state = ParseState.End;
- constraintText = text.Substring(startIndex, currentIndex - startIndex);
- if (constraintText.Length > 0)
+ else
{
- constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ state = ParseState.End;
+ currentIndex--;
}
- currentIndex--;
- break;
- }
- break;
- }
+ }
+ else
+ {
+ currentIndex = indexOfClosingParantheses;
+ }
- currentIndex++;
+ break;
+ }
+ break;
+ case ParseState.ParsingName:
+ switch (currentChar)
+ {
+ case null:
+ state = ParseState.End;
+ var constraintText = text.Substring(startIndex, currentIndex - startIndex);
+ if (constraintText.Length > 0)
+ {
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ }
+ break;
+ case ':':
+ constraintText = text.Substring(startIndex, currentIndex - startIndex);
+ if (constraintText.Length > 0)
+ {
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ }
+ startIndex = currentIndex + 1;
+ break;
+ case '(':
+ state = ParseState.InsideParenthesis;
+ break;
+ case '=':
+ state = ParseState.End;
+ constraintText = text.Substring(startIndex, currentIndex - startIndex);
+ if (constraintText.Length > 0)
+ {
+ constraints.Add(RoutePatternFactory.ParameterPolicy(constraintText));
+ }
+ currentIndex--;
+ break;
+ }
+ break;
+ }
- } while (state != ParseState.End);
+ currentIndex++;
- return new ParameterPolicyParseResults(currentIndex, constraints.ToArray());
- }
+ } while (state != ParseState.End);
- private enum ParseState
- {
- Start,
- ParsingName,
- InsideParenthesis,
- End
- }
+ return new ParameterPolicyParseResults(currentIndex, constraints.ToArray());
+ }
- private readonly struct ParameterPolicyParseResults
- {
- public readonly int CurrentIndex;
+ private enum ParseState
+ {
+ Start,
+ ParsingName,
+ InsideParenthesis,
+ End
+ }
- public readonly RoutePatternParameterPolicyReference[] ParameterPolicies;
+ private readonly struct ParameterPolicyParseResults
+ {
+ public readonly int CurrentIndex;
- public ParameterPolicyParseResults(int currentIndex, RoutePatternParameterPolicyReference[] parameterPolicies)
- {
- CurrentIndex = currentIndex;
- ParameterPolicies = parameterPolicies;
- }
+ public readonly RoutePatternParameterPolicyReference[] ParameterPolicies;
+
+ public ParameterPolicyParseResults(int currentIndex, RoutePatternParameterPolicyReference[] parameterPolicies)
+ {
+ CurrentIndex = currentIndex;
+ ParameterPolicies = parameterPolicies;
}
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePattern.cs b/src/Http/Routing/src/Patterns/RoutePattern.cs
index 64836e1543..19125fa443 100644
--- a/src/Http/Routing/src/Patterns/RoutePattern.cs
+++ b/src/Http/Routing/src/Patterns/RoutePattern.cs
@@ -7,162 +7,161 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Represents a parsed route template with default values and constraints.
+/// Use <see cref="RoutePatternFactory"/> to create <see cref="RoutePattern"/>
+/// instances. Instances of <see cref="RoutePattern"/> are immutable.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public sealed class RoutePattern
{
/// <summary>
- /// Represents a parsed route template with default values and constraints.
- /// Use <see cref="RoutePatternFactory"/> to create <see cref="RoutePattern"/>
- /// instances. Instances of <see cref="RoutePattern"/> are immutable.
+ /// A marker object that can be used in <see cref="RequiredValues"/> to designate that
+ /// any non-null or non-empty value is required.
/// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public sealed class RoutePattern
+ /// <remarks>
+ /// <see cref="RequiredValueAny"/> is only use in routing is in <see cref="RoutePattern.RequiredValues"/>.
+ /// <see cref="RequiredValueAny"/> is not valid as a route value, and will convert to the null/empty string.
+ /// </remarks>
+ public static readonly object RequiredValueAny = new RequiredValueAnySentinal();
+
+ internal static bool IsRequiredValueAny(object? value)
{
- /// <summary>
- /// A marker object that can be used in <see cref="RequiredValues"/> to designate that
- /// any non-null or non-empty value is required.
- /// </summary>
- /// <remarks>
- /// <see cref="RequiredValueAny"/> is only use in routing is in <see cref="RoutePattern.RequiredValues"/>.
- /// <see cref="RequiredValueAny"/> is not valid as a route value, and will convert to the null/empty string.
- /// </remarks>
- public static readonly object RequiredValueAny = new RequiredValueAnySentinal();
-
- internal static bool IsRequiredValueAny(object? value)
- {
- return object.ReferenceEquals(RequiredValueAny, value);
- }
+ return object.ReferenceEquals(RequiredValueAny, value);
+ }
- private const string SeparatorString = "/";
+ private const string SeparatorString = "/";
- internal RoutePattern(
- string? rawText,
- IReadOnlyDictionary<string, object?> defaults,
- IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> parameterPolicies,
- IReadOnlyDictionary<string, object?> requiredValues,
- IReadOnlyList<RoutePatternParameterPart> parameters,
- IReadOnlyList<RoutePatternPathSegment> pathSegments)
- {
- Debug.Assert(defaults != null);
- Debug.Assert(parameterPolicies != null);
- Debug.Assert(parameters != null);
- Debug.Assert(requiredValues != null);
- Debug.Assert(pathSegments != null);
-
- RawText = rawText;
- Defaults = defaults;
- ParameterPolicies = parameterPolicies;
- RequiredValues = requiredValues;
- Parameters = parameters;
- PathSegments = pathSegments;
-
- InboundPrecedence = RoutePrecedence.ComputeInbound(this);
- OutboundPrecedence = RoutePrecedence.ComputeOutbound(this);
- }
+ internal RoutePattern(
+ string? rawText,
+ IReadOnlyDictionary<string, object?> defaults,
+ IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> parameterPolicies,
+ IReadOnlyDictionary<string, object?> requiredValues,
+ IReadOnlyList<RoutePatternParameterPart> parameters,
+ IReadOnlyList<RoutePatternPathSegment> pathSegments)
+ {
+ Debug.Assert(defaults != null);
+ Debug.Assert(parameterPolicies != null);
+ Debug.Assert(parameters != null);
+ Debug.Assert(requiredValues != null);
+ Debug.Assert(pathSegments != null);
+
+ RawText = rawText;
+ Defaults = defaults;
+ ParameterPolicies = parameterPolicies;
+ RequiredValues = requiredValues;
+ Parameters = parameters;
+ PathSegments = pathSegments;
+
+ InboundPrecedence = RoutePrecedence.ComputeInbound(this);
+ OutboundPrecedence = RoutePrecedence.ComputeOutbound(this);
+ }
- /// <summary>
- /// Gets the set of default values for the route pattern.
- /// The keys of <see cref="Defaults"/> are the route parameter names.
- /// </summary>
- public IReadOnlyDictionary<string, object?> Defaults { get; }
-
- /// <summary>
- /// Gets the set of parameter policy references for the route pattern.
- /// The keys of <see cref="ParameterPolicies"/> are the route parameter names.
- /// </summary>
- public IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> ParameterPolicies { get; }
-
- /// <summary>
- /// Gets a collection of route values that must be provided for this route pattern to be considered
- /// applicable.
- /// </summary>
- /// <remarks>
- /// <para>
- /// <see cref="RequiredValues"/> allows a framework to substitute route values into a parameterized template
- /// so that the same route template specification can be used to create multiple route patterns.
- /// <example>
- /// This example shows how a route template can be used with required values to substitute known
- /// route values for parameters.
- /// <code>
- /// Route Template: "{controller=Home}/{action=Index}/{id?}"
- /// Route Values: { controller = "Store", action = "Index" }
- /// </code>
- ///
- /// A route pattern produced in this way will match and generate URL paths like: <c>/Store</c>,
- /// <c>/Store/Index</c>, and <c>/Store/Index/17</c>.
- /// </example>
- /// </para>
- /// </remarks>
- public IReadOnlyDictionary<string, object?> RequiredValues { get; }
-
- /// <summary>
- /// Gets the precedence value of the route pattern for URL matching.
- /// </summary>
- /// <remarks>
- /// Precedence is a computed value based on the structure of the route pattern
- /// used for building URL matching data structures.
- /// </remarks>
- public decimal InboundPrecedence { get; }
-
- /// <summary>
- /// Gets the precedence value of the route pattern for URL generation.
- /// </summary>
- /// <remarks>
- /// Precedence is a computed value based on the structure of the route pattern
- /// used for building URL generation data structures.
- /// </remarks>
- public decimal OutboundPrecedence { get; }
-
- /// <summary>
- /// Gets the raw text supplied when parsing the route pattern. May be null.
- /// </summary>
- public string? RawText { get; }
-
- /// <summary>
- /// Gets the list of route parameters.
- /// </summary>
- public IReadOnlyList<RoutePatternParameterPart> Parameters { get; }
-
- /// <summary>
- /// Gets the list of path segments.
- /// </summary>
- public IReadOnlyList<RoutePatternPathSegment> PathSegments { get; }
-
- /// <summary>
- /// Gets the parameter matching the given name.
- /// </summary>
- /// <param name="name">The name of the parameter to match.</param>
- /// <returns>The matching parameter or <c>null</c> if no parameter matches the given name.</returns>
- public RoutePatternParameterPart? GetParameter(string name)
- {
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ /// <summary>
+ /// Gets the set of default values for the route pattern.
+ /// The keys of <see cref="Defaults"/> are the route parameter names.
+ /// </summary>
+ public IReadOnlyDictionary<string, object?> Defaults { get; }
- var parameters = Parameters;
- // Read interface .Count once rather than per iteration
- var parametersCount = parameters.Count;
- for (var i = 0; i < parametersCount; i++)
- {
- var parameter = parameters[i];
- if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
- {
- return parameter;
- }
- }
+ /// <summary>
+ /// Gets the set of parameter policy references for the route pattern.
+ /// The keys of <see cref="ParameterPolicies"/> are the route parameter names.
+ /// </summary>
+ public IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> ParameterPolicies { get; }
- return null;
- }
+ /// <summary>
+ /// Gets a collection of route values that must be provided for this route pattern to be considered
+ /// applicable.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// <see cref="RequiredValues"/> allows a framework to substitute route values into a parameterized template
+ /// so that the same route template specification can be used to create multiple route patterns.
+ /// <example>
+ /// This example shows how a route template can be used with required values to substitute known
+ /// route values for parameters.
+ /// <code>
+ /// Route Template: "{controller=Home}/{action=Index}/{id?}"
+ /// Route Values: { controller = "Store", action = "Index" }
+ /// </code>
+ ///
+ /// A route pattern produced in this way will match and generate URL paths like: <c>/Store</c>,
+ /// <c>/Store/Index</c>, and <c>/Store/Index/17</c>.
+ /// </example>
+ /// </para>
+ /// </remarks>
+ public IReadOnlyDictionary<string, object?> RequiredValues { get; }
+
+ /// <summary>
+ /// Gets the precedence value of the route pattern for URL matching.
+ /// </summary>
+ /// <remarks>
+ /// Precedence is a computed value based on the structure of the route pattern
+ /// used for building URL matching data structures.
+ /// </remarks>
+ public decimal InboundPrecedence { get; }
- internal string DebuggerToString()
+ /// <summary>
+ /// Gets the precedence value of the route pattern for URL generation.
+ /// </summary>
+ /// <remarks>
+ /// Precedence is a computed value based on the structure of the route pattern
+ /// used for building URL generation data structures.
+ /// </remarks>
+ public decimal OutboundPrecedence { get; }
+
+ /// <summary>
+ /// Gets the raw text supplied when parsing the route pattern. May be null.
+ /// </summary>
+ public string? RawText { get; }
+
+ /// <summary>
+ /// Gets the list of route parameters.
+ /// </summary>
+ public IReadOnlyList<RoutePatternParameterPart> Parameters { get; }
+
+ /// <summary>
+ /// Gets the list of path segments.
+ /// </summary>
+ public IReadOnlyList<RoutePatternPathSegment> PathSegments { get; }
+
+ /// <summary>
+ /// Gets the parameter matching the given name.
+ /// </summary>
+ /// <param name="name">The name of the parameter to match.</param>
+ /// <returns>The matching parameter or <c>null</c> if no parameter matches the given name.</returns>
+ public RoutePatternParameterPart? GetParameter(string name)
+ {
+ if (name == null)
{
- return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
+ throw new ArgumentNullException(nameof(name));
}
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- private class RequiredValueAnySentinal
+ var parameters = Parameters;
+ // Read interface .Count once rather than per iteration
+ var parametersCount = parameters.Count;
+ for (var i = 0; i < parametersCount; i++)
{
- private string DebuggerToString() => "*any*";
+ var parameter = parameters[i];
+ if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
+ {
+ return parameter;
+ }
}
+
+ return null;
+ }
+
+ internal string DebuggerToString()
+ {
+ return RawText ?? string.Join(SeparatorString, PathSegments.Select(s => s.DebuggerToString()));
+ }
+
+ [DebuggerDisplay("{DebuggerToString(),nq}")]
+ private class RequiredValueAnySentinal
+ {
+ private string DebuggerToString() => "*any*";
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternException.cs b/src/Http/Routing/src/Patterns/RoutePatternException.cs
index f99ff8d04e..d488b2724d 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternException.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternException.cs
@@ -4,55 +4,54 @@
using System;
using System.Runtime.Serialization;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// An exception that is thrown for error constructing a <see cref="RoutePattern"/>.
+/// </summary>
+[Serializable]
+public sealed class RoutePatternException : Exception
{
+ private RoutePatternException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ {
+ Pattern = (string)info.GetValue(nameof(Pattern), typeof(string))!;
+ }
+
/// <summary>
- /// An exception that is thrown for error constructing a <see cref="RoutePattern"/>.
+ /// Creates a new instance of <see cref="RoutePatternException"/>.
/// </summary>
- [Serializable]
- public sealed class RoutePatternException : Exception
+ /// <param name="pattern">The route pattern as raw text.</param>
+ /// <param name="message">The exception message.</param>
+ public RoutePatternException(string pattern, string message)
+ : base(message)
{
- private RoutePatternException(SerializationInfo info, StreamingContext context)
- : base(info, context)
+ if (pattern == null)
{
- Pattern = (string)info.GetValue(nameof(Pattern), typeof(string))!;
+ throw new ArgumentNullException(nameof(pattern));
}
- /// <summary>
- /// Creates a new instance of <see cref="RoutePatternException"/>.
- /// </summary>
- /// <param name="pattern">The route pattern as raw text.</param>
- /// <param name="message">The exception message.</param>
- public RoutePatternException(string pattern, string message)
- : base(message)
+ if (message == null)
{
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ throw new ArgumentNullException(nameof(message));
+ }
- if (message == null)
- {
- throw new ArgumentNullException(nameof(message));
- }
+ Pattern = pattern;
+ }
- Pattern = pattern;
- }
+ /// <summary>
+ /// Gets the route pattern associated with this exception.
+ /// </summary>
+ public string Pattern { get; }
- /// <summary>
- /// Gets the route pattern associated with this exception.
- /// </summary>
- public string Pattern { get; }
-
- /// <summary>
- /// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the target object.
- /// </summary>
- /// <param name="info">The <see cref="SerializationInfo"/> to populate with data.</param>
- /// <param name="context">The destination (<see cref="StreamingContext" />) for this serialization.</param>
- public override void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- info.AddValue(nameof(Pattern), Pattern);
- base.GetObjectData(info, context);
- }
+ /// <summary>
+ /// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the target object.
+ /// </summary>
+ /// <param name="info">The <see cref="SerializationInfo"/> to populate with data.</param>
+ /// <param name="context">The destination (<see cref="StreamingContext" />) for this serialization.</param>
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue(nameof(Pattern), Pattern);
+ base.GetObjectData(info, context);
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs
index 91e6d91349..0bd5705104 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs
@@ -8,907 +8,906 @@ using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.AspNetCore.Routing.Constraints;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Contains factory methods for creating <see cref="RoutePattern"/> and related types.
+/// Use <see cref="Parse(string)"/> to parse a route pattern in
+/// string format.
+/// </summary>
+public static class RoutePatternFactory
{
+ private static readonly IReadOnlyDictionary<string, object?> EmptyDictionary =
+ new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>());
+
+ private static readonly IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> EmptyPoliciesDictionary =
+ new ReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>(new Dictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>());
+
/// <summary>
- /// Contains factory methods for creating <see cref="RoutePattern"/> and related types.
- /// Use <see cref="Parse(string)"/> to parse a route pattern in
- /// string format.
+ /// Creates a <see cref="RoutePattern"/> from its string representation.
/// </summary>
- public static class RoutePatternFactory
+ /// <param name="pattern">The route pattern string to parse.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Parse(string pattern)
{
- private static readonly IReadOnlyDictionary<string, object?> EmptyDictionary =
- new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>());
+ if (pattern == null)
+ {
+ throw new ArgumentNullException(nameof(pattern));
+ }
- private static readonly IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> EmptyPoliciesDictionary =
- new ReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>(new Dictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>());
+ return RoutePatternParser.Parse(pattern);
+ }
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from its string representation.
- /// </summary>
- /// <param name="pattern">The route pattern string to parse.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Parse(string pattern)
+ /// <summary>
+ /// Creates a <see cref="RoutePattern"/> from its string representation along
+ /// with provided default values and parameter policies.
+ /// </summary>
+ /// <param name="pattern">The route pattern string to parse.</param>
+ /// <param name="defaults">
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the parsed route pattern.
+ /// </param>
+ /// <param name="parameterPolicies">
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the parsed route pattern.
+ /// Multiple policies can be specified for a key by providing a collection as the value.
+ /// </param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies)
+ {
+ if (pattern == null)
{
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ throw new ArgumentNullException(nameof(pattern));
+ }
- return RoutePatternParser.Parse(pattern);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from its string representation along
- /// with provided default values and parameter policies.
- /// </summary>
- /// <param name="pattern">The route pattern string to parse.</param>
- /// <param name="defaults">
- /// Additional default values to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the parsed route pattern.
- /// </param>
- /// <param name="parameterPolicies">
- /// Additional parameter policies to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the parsed route pattern.
- /// Multiple policies can be specified for a key by providing a collection as the value.
- /// </param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies)
- {
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ var original = RoutePatternParser.Parse(pattern);
+ return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), requiredValues: null, original.PathSegments);
+ }
- var original = RoutePatternParser.Parse(pattern);
- return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), requiredValues: null, original.PathSegments);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from its string representation along
- /// with provided default values and parameter policies.
- /// </summary>
- /// <param name="pattern">The route pattern string to parse.</param>
- /// <param name="defaults">
- /// Additional default values to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the parsed route pattern.
- /// </param>
- /// <param name="parameterPolicies">
- /// Additional parameter policies to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the parsed route pattern.
- /// Multiple policies can be specified for a key by providing a collection as the value.
- /// </param>
- /// <param name="requiredValues">
- /// Route values that can be substituted for parameters in the route pattern. See remarks on <see cref="RoutePattern.RequiredValues"/>.
- /// </param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies, object? requiredValues)
- {
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ /// <summary>
+ /// Creates a <see cref="RoutePattern"/> from its string representation along
+ /// with provided default values and parameter policies.
+ /// </summary>
+ /// <param name="pattern">The route pattern string to parse.</param>
+ /// <param name="defaults">
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the parsed route pattern.
+ /// </param>
+ /// <param name="parameterPolicies">
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the parsed route pattern.
+ /// Multiple policies can be specified for a key by providing a collection as the value.
+ /// </param>
+ /// <param name="requiredValues">
+ /// Route values that can be substituted for parameters in the route pattern. See remarks on <see cref="RoutePattern.RequiredValues"/>.
+ /// </param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Parse(string pattern, object? defaults, object? parameterPolicies, object? requiredValues)
+ {
+ if (pattern == null)
+ {
+ throw new ArgumentNullException(nameof(pattern));
+ }
+
+ var original = RoutePatternParser.Parse(pattern);
+ return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), Wrap(requiredValues), original.PathSegments);
+ }
- var original = RoutePatternParser.Parse(pattern);
- return PatternCore(original.RawText, Wrap(defaults), Wrap(parameterPolicies), Wrap(requiredValues), original.PathSegments);
+ /// <summary>
+ /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
+ /// </summary>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(IEnumerable<RoutePatternPathSegment> segments)
+ {
+ if (segments == null)
+ {
+ throw new ArgumentNullException(nameof(segments));
}
- /// <summary>
- /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
- /// </summary>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(IEnumerable<RoutePatternPathSegment> segments)
+ return PatternCore(null, null, null, null, segments);
+ }
+
+ /// <summary>
+ /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
+ /// </summary>
+ /// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(string? rawText, IEnumerable<RoutePatternPathSegment> segments)
+ {
+ if (segments == null)
{
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ throw new ArgumentNullException(nameof(segments));
+ }
+
+ return PatternCore(rawText, null, null, null, segments);
+ }
- return PatternCore(null, null, null, null, segments);
+ /// <summary>
+ /// Creates a <see cref="RoutePattern"/> from a collection of segments along
+ /// with provided default values and parameter policies.
+ /// </summary>
+ /// <param name="defaults">
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// </param>
+ /// <param name="parameterPolicies">
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// Multiple policies can be specified for a key by providing a collection as the value.
+ /// </param>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(
+ object? defaults,
+ object? parameterPolicies,
+ IEnumerable<RoutePatternPathSegment> segments)
+ {
+ if (segments == null)
+ {
+ throw new ArgumentNullException(nameof(segments));
}
- /// <summary>
- /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
- /// </summary>
- /// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(string? rawText, IEnumerable<RoutePatternPathSegment> segments)
+ return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePattern"/> from a collection of segments along
+ /// with provided default values and parameter policies.
+ /// </summary>
+ /// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
+ /// <param name="defaults">
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// </param>
+ /// <param name="parameterPolicies">
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// Multiple policies can be specified for a key by providing a collection as the value.
+ /// </param>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(
+ string? rawText,
+ object? defaults,
+ object? parameterPolicies,
+ IEnumerable<RoutePatternPathSegment> segments)
+ {
+ if (segments == null)
{
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ throw new ArgumentNullException(nameof(segments));
+ }
- return PatternCore(rawText, null, null, null, segments);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from a collection of segments along
- /// with provided default values and parameter policies.
- /// </summary>
- /// <param name="defaults">
- /// Additional default values to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// </param>
- /// <param name="parameterPolicies">
- /// Additional parameter policies to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// Multiple policies can be specified for a key by providing a collection as the value.
- /// </param>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(
- object? defaults,
- object? parameterPolicies,
- IEnumerable<RoutePatternPathSegment> segments)
- {
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
+ }
- return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from a collection of segments along
- /// with provided default values and parameter policies.
- /// </summary>
- /// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
- /// <param name="defaults">
- /// Additional default values to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// </param>
- /// <param name="parameterPolicies">
- /// Additional parameter policies to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// Multiple policies can be specified for a key by providing a collection as the value.
- /// </param>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(
- string? rawText,
- object? defaults,
- object? parameterPolicies,
- IEnumerable<RoutePatternPathSegment> segments)
- {
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ /// <summary>
+ /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
+ /// </summary>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(params RoutePatternPathSegment[] segments)
+ {
+ if (segments == null)
+ {
+ throw new ArgumentNullException(nameof(segments));
+ }
+
+ return PatternCore(null, null, null, requiredValues: null, segments);
+ }
- return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
+ /// <summary>
+ /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
+ /// </summary>
+ /// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(string rawText, params RoutePatternPathSegment[] segments)
+ {
+ if (segments == null)
+ {
+ throw new ArgumentNullException(nameof(segments));
}
- /// <summary>
- /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
- /// </summary>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(params RoutePatternPathSegment[] segments)
+ return PatternCore(rawText, null, null, requiredValues: null, segments);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePattern"/> from a collection of segments along
+ /// with provided default values and parameter policies.
+ /// </summary>
+ /// <param name="defaults">
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// </param>
+ /// <param name="parameterPolicies">
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// Multiple policies can be specified for a key by providing a collection as the value.
+ /// </param>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(
+ object? defaults,
+ object? parameterPolicies,
+ params RoutePatternPathSegment[] segments)
+ {
+ if (segments == null)
{
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ throw new ArgumentNullException(nameof(segments));
+ }
+
+ return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
+ }
- return PatternCore(null, null, null, requiredValues: null, segments);
+ /// <summary>
+ /// Creates a <see cref="RoutePattern"/> from a collection of segments along
+ /// with provided default values and parameter policies.
+ /// </summary>
+ /// <param name="rawText">The raw text to associate with the route pattern.</param>
+ /// <param name="defaults">
+ /// Additional default values to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// </param>
+ /// <param name="parameterPolicies">
+ /// Additional parameter policies to associated with the route pattern. May be null.
+ /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
+ /// and then merged into the route pattern.
+ /// Multiple policies can be specified for a key by providing a collection as the value.
+ /// </param>
+ /// <param name="segments">The collection of segments.</param>
+ /// <returns>The <see cref="RoutePattern"/>.</returns>
+ public static RoutePattern Pattern(
+ string? rawText,
+ object? defaults,
+ object? parameterPolicies,
+ params RoutePatternPathSegment[] segments)
+ {
+ if (segments == null)
+ {
+ throw new ArgumentNullException(nameof(segments));
}
- /// <summary>
- /// Creates a new instance of <see cref="RoutePattern"/> from a collection of segments.
- /// </summary>
- /// <param name="rawText">The raw text to associate with the route pattern. May be null.</param>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(string rawText, params RoutePatternPathSegment[] segments)
+ return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
+ }
+
+ private static RoutePattern PatternCore(
+ string? rawText,
+ RouteValueDictionary? defaults,
+ RouteValueDictionary? parameterPolicies,
+ RouteValueDictionary? requiredValues,
+ IEnumerable<RoutePatternPathSegment> segments)
+ {
+ // We want to merge the segment data with the 'out of line' defaults and parameter policies.
+ //
+ // This means that for parameters that have 'out of line' defaults we will modify
+ // the parameter to contain the default (same story for parameter policies).
+ //
+ // We also maintain a collection of defaults and parameter policies that will also
+ // contain the values that don't match a parameter.
+ //
+ // It's important that these two views of the data are consistent. We don't want
+ // values specified out of line to have a different behavior.
+
+ Dictionary<string, object?>? updatedDefaults = null;
+ if (defaults != null && defaults.Count > 0)
{
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ updatedDefaults = new Dictionary<string, object?>(defaults.Count, StringComparer.OrdinalIgnoreCase);
- return PatternCore(rawText, null, null, requiredValues: null, segments);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from a collection of segments along
- /// with provided default values and parameter policies.
- /// </summary>
- /// <param name="defaults">
- /// Additional default values to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// </param>
- /// <param name="parameterPolicies">
- /// Additional parameter policies to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// Multiple policies can be specified for a key by providing a collection as the value.
- /// </param>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(
- object? defaults,
- object? parameterPolicies,
- params RoutePatternPathSegment[] segments)
- {
- if (segments == null)
+ foreach (var kvp in defaults)
{
- throw new ArgumentNullException(nameof(segments));
+ updatedDefaults.Add(kvp.Key, kvp.Value);
}
+ }
- return PatternCore(null, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePattern"/> from a collection of segments along
- /// with provided default values and parameter policies.
- /// </summary>
- /// <param name="rawText">The raw text to associate with the route pattern.</param>
- /// <param name="defaults">
- /// Additional default values to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// </param>
- /// <param name="parameterPolicies">
- /// Additional parameter policies to associated with the route pattern. May be null.
- /// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
- /// and then merged into the route pattern.
- /// Multiple policies can be specified for a key by providing a collection as the value.
- /// </param>
- /// <param name="segments">The collection of segments.</param>
- /// <returns>The <see cref="RoutePattern"/>.</returns>
- public static RoutePattern Pattern(
- string? rawText,
- object? defaults,
- object? parameterPolicies,
- params RoutePatternPathSegment[] segments)
- {
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ Dictionary<string, List<RoutePatternParameterPolicyReference>>? updatedParameterPolicies = null;
+ if (parameterPolicies != null && parameterPolicies.Count > 0)
+ {
+ updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(parameterPolicies.Count, StringComparer.OrdinalIgnoreCase);
- return PatternCore(rawText, new RouteValueDictionary(defaults), new RouteValueDictionary(parameterPolicies), requiredValues: null, segments);
- }
-
- private static RoutePattern PatternCore(
- string? rawText,
- RouteValueDictionary? defaults,
- RouteValueDictionary? parameterPolicies,
- RouteValueDictionary? requiredValues,
- IEnumerable<RoutePatternPathSegment> segments)
- {
- // We want to merge the segment data with the 'out of line' defaults and parameter policies.
- //
- // This means that for parameters that have 'out of line' defaults we will modify
- // the parameter to contain the default (same story for parameter policies).
- //
- // We also maintain a collection of defaults and parameter policies that will also
- // contain the values that don't match a parameter.
- //
- // It's important that these two views of the data are consistent. We don't want
- // values specified out of line to have a different behavior.
-
- Dictionary<string, object?>? updatedDefaults = null;
- if (defaults != null && defaults.Count > 0)
+ foreach (var kvp in parameterPolicies)
{
- updatedDefaults = new Dictionary<string, object?>(defaults.Count, StringComparer.OrdinalIgnoreCase);
+ var policyReferences = new List<RoutePatternParameterPolicyReference>();
- foreach (var kvp in defaults)
+ if (kvp.Value is IParameterPolicy parameterPolicy)
{
- updatedDefaults.Add(kvp.Key, kvp.Value);
+ policyReferences.Add(ParameterPolicy(parameterPolicy));
}
- }
-
- Dictionary<string, List<RoutePatternParameterPolicyReference>>? updatedParameterPolicies = null;
- if (parameterPolicies != null && parameterPolicies.Count > 0)
- {
- updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(parameterPolicies.Count, StringComparer.OrdinalIgnoreCase);
-
- foreach (var kvp in parameterPolicies)
+ else if (kvp.Value is string)
{
- var policyReferences = new List<RoutePatternParameterPolicyReference>();
-
- if (kvp.Value is IParameterPolicy parameterPolicy)
- {
- policyReferences.Add(ParameterPolicy(parameterPolicy));
- }
- else if (kvp.Value is string)
+ // Constraint will convert string values into regex constraints
+ policyReferences.Add(Constraint(kvp.Value));
+ }
+ else if (kvp.Value is IEnumerable multiplePolicies)
+ {
+ foreach (var item in multiplePolicies)
{
// Constraint will convert string values into regex constraints
- policyReferences.Add(Constraint(kvp.Value));
+ policyReferences.Add(item is IParameterPolicy p ? ParameterPolicy(p) : Constraint(item));
}
- else if (kvp.Value is IEnumerable multiplePolicies)
- {
- foreach (var item in multiplePolicies)
- {
- // Constraint will convert string values into regex constraints
- policyReferences.Add(item is IParameterPolicy p ? ParameterPolicy(p) : Constraint(item));
- }
- }
- else
- {
- throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
- kvp.Value ?? "null",
- typeof(IRouteConstraint)));
- }
-
- updatedParameterPolicies.Add(kvp.Key, policyReferences);
}
- }
-
- List<RoutePatternParameterPart>? parameters = null;
- var updatedSegments = segments.ToArray();
- for (var i = 0; i < updatedSegments.Length; i++)
- {
- var segment = VisitSegment(updatedSegments[i]);
- updatedSegments[i] = segment;
-
- for (var j = 0; j < segment.Parts.Count; j++)
+ else
{
- if (segment.Parts[j] is RoutePatternParameterPart parameter)
- {
- if (parameters == null)
- {
- parameters = new List<RoutePatternParameterPart>();
- }
-
- parameters.Add(parameter);
- }
+ throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
+ kvp.Value ?? "null",
+ typeof(IRouteConstraint)));
}
+
+ updatedParameterPolicies.Add(kvp.Key, policyReferences);
}
+ }
+
+ List<RoutePatternParameterPart>? parameters = null;
+ var updatedSegments = segments.ToArray();
+ for (var i = 0; i < updatedSegments.Length; i++)
+ {
+ var segment = VisitSegment(updatedSegments[i]);
+ updatedSegments[i] = segment;
- // Each Required Value either needs to either:
- // 1. be null-ish
- // 2. have a corresponding parameter
- // 3. have a corresponding default that matches both key and value
- if (requiredValues != null)
+ for (var j = 0; j < segment.Parts.Count; j++)
{
- foreach (var kvp in requiredValues)
+ if (segment.Parts[j] is RoutePatternParameterPart parameter)
{
- // 1.be null-ish
- var found = RouteValueEqualityComparer.Default.Equals(string.Empty, kvp.Value);
-
- // 2. have a corresponding parameter
- if (!found && parameters != null)
+ if (parameters == null)
{
- for (var i = 0; i < parameters.Count; i++)
- {
- if (string.Equals(kvp.Key, parameters[i].Name, StringComparison.OrdinalIgnoreCase))
- {
- found = true;
- break;
- }
- }
+ parameters = new List<RoutePatternParameterPart>();
}
- // 3. have a corresponding default that matches both key and value
- if (!found &&
- updatedDefaults != null &&
- updatedDefaults.TryGetValue(kvp.Key, out var defaultValue) &&
- RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
- {
- found = true;
- }
-
- if (!found)
- {
- throw new InvalidOperationException(
- $"No corresponding parameter or default value could be found for the required value " +
- $"'{kvp.Key}={kvp.Value}'. A non-null required value must correspond to a route parameter or the " +
- $"route pattern must have a matching default value.");
- }
+ parameters.Add(parameter);
}
}
+ }
- return new RoutePattern(
- rawText,
- updatedDefaults ?? EmptyDictionary,
- updatedParameterPolicies != null
- ? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray())
- : EmptyPoliciesDictionary,
- requiredValues ?? EmptyDictionary,
- (IReadOnlyList<RoutePatternParameterPart>?)parameters ?? Array.Empty<RoutePatternParameterPart>(),
- updatedSegments);
-
- RoutePatternPathSegment VisitSegment(RoutePatternPathSegment segment)
+ // Each Required Value either needs to either:
+ // 1. be null-ish
+ // 2. have a corresponding parameter
+ // 3. have a corresponding default that matches both key and value
+ if (requiredValues != null)
+ {
+ foreach (var kvp in requiredValues)
{
- RoutePatternPart[]? updatedParts = null;
- for (var i = 0; i < segment.Parts.Count; i++)
- {
- var part = segment.Parts[i];
- var updatedPart = VisitPart(part);
+ // 1.be null-ish
+ var found = RouteValueEqualityComparer.Default.Equals(string.Empty, kvp.Value);
- if (part != updatedPart)
+ // 2. have a corresponding parameter
+ if (!found && parameters != null)
+ {
+ for (var i = 0; i < parameters.Count; i++)
{
- if (updatedParts == null)
+ if (string.Equals(kvp.Key, parameters[i].Name, StringComparison.OrdinalIgnoreCase))
{
- updatedParts = segment.Parts.ToArray();
+ found = true;
+ break;
}
-
- updatedParts[i] = updatedPart;
}
}
- if (updatedParts == null)
+ // 3. have a corresponding default that matches both key and value
+ if (!found &&
+ updatedDefaults != null &&
+ updatedDefaults.TryGetValue(kvp.Key, out var defaultValue) &&
+ RouteValueEqualityComparer.Default.Equals(kvp.Value, defaultValue))
{
- // Segment has not changed
- return segment;
+ found = true;
}
- return new RoutePatternPathSegment(updatedParts);
- }
-
- RoutePatternPart VisitPart(RoutePatternPart part)
- {
- if (!part.IsParameter)
+ if (!found)
{
- return part;
+ throw new InvalidOperationException(
+ $"No corresponding parameter or default value could be found for the required value " +
+ $"'{kvp.Key}={kvp.Value}'. A non-null required value must correspond to a route parameter or the " +
+ $"route pattern must have a matching default value.");
}
+ }
+ }
- var parameter = (RoutePatternParameterPart)part;
- var @default = parameter.Default;
+ return new RoutePattern(
+ rawText,
+ updatedDefaults ?? EmptyDictionary,
+ updatedParameterPolicies != null
+ ? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray())
+ : EmptyPoliciesDictionary,
+ requiredValues ?? EmptyDictionary,
+ (IReadOnlyList<RoutePatternParameterPart>?)parameters ?? Array.Empty<RoutePatternParameterPart>(),
+ updatedSegments);
- if (updatedDefaults != null && updatedDefaults.TryGetValue(parameter.Name, out var newDefault))
- {
- if (parameter.Default != null && !Equals(newDefault, parameter.Default))
- {
- var message = Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(parameter.Name);
- throw new InvalidOperationException(message);
- }
+ RoutePatternPathSegment VisitSegment(RoutePatternPathSegment segment)
+ {
+ RoutePatternPart[]? updatedParts = null;
+ for (var i = 0; i < segment.Parts.Count; i++)
+ {
+ var part = segment.Parts[i];
+ var updatedPart = VisitPart(part);
- if (parameter.IsOptional)
+ if (part != updatedPart)
+ {
+ if (updatedParts == null)
{
- var message = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
- throw new InvalidOperationException(message);
+ updatedParts = segment.Parts.ToArray();
}
- @default = newDefault;
+ updatedParts[i] = updatedPart;
}
+ }
- if (parameter.Default != null)
- {
- if (updatedDefaults == null)
- {
- updatedDefaults = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
- }
+ if (updatedParts == null)
+ {
+ // Segment has not changed
+ return segment;
+ }
- updatedDefaults[parameter.Name] = parameter.Default;
- }
+ return new RoutePatternPathSegment(updatedParts);
+ }
- List<RoutePatternParameterPolicyReference>? parameterConstraints = null;
- if ((updatedParameterPolicies == null || !updatedParameterPolicies.TryGetValue(parameter.Name, out parameterConstraints)) &&
- parameter.ParameterPolicies.Count > 0)
- {
- if (updatedParameterPolicies == null)
- {
- updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(StringComparer.OrdinalIgnoreCase);
- }
+ RoutePatternPart VisitPart(RoutePatternPart part)
+ {
+ if (!part.IsParameter)
+ {
+ return part;
+ }
- parameterConstraints = new List<RoutePatternParameterPolicyReference>(parameter.ParameterPolicies.Count);
- updatedParameterPolicies.Add(parameter.Name, parameterConstraints);
- }
+ var parameter = (RoutePatternParameterPart)part;
+ var @default = parameter.Default;
- if (parameter.ParameterPolicies.Count > 0)
+ if (updatedDefaults != null && updatedDefaults.TryGetValue(parameter.Name, out var newDefault))
+ {
+ if (parameter.Default != null && !Equals(newDefault, parameter.Default))
{
- parameterConstraints!.AddRange(parameter.ParameterPolicies);
+ var message = Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(parameter.Name);
+ throw new InvalidOperationException(message);
}
- if (Equals(parameter.Default, @default)
- && parameter.ParameterPolicies.Count == 0
- && (parameterConstraints?.Count ?? 0) == 0)
+ if (parameter.IsOptional)
{
- // Part has not changed
- return part;
+ var message = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
+ throw new InvalidOperationException(message);
}
- return ParameterPartCore(
- parameter.Name,
- @default,
- parameter.ParameterKind,
- parameterConstraints?.ToArray() ?? Array.Empty<RoutePatternParameterPolicyReference>(),
- parameter.EncodeSlashes);
+ @default = newDefault;
}
- }
- /// <summary>
- /// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection
- /// of parts.
- /// </summary>
- /// <param name="parts">The collection of parts.</param>
- /// <returns>The <see cref="RoutePatternPathSegment"/>.</returns>
- public static RoutePatternPathSegment Segment(IEnumerable<RoutePatternPart> parts)
- {
- if (parts == null)
+ if (parameter.Default != null)
{
- throw new ArgumentNullException(nameof(parts));
- }
-
- return SegmentCore(parts.ToArray());
- }
+ if (updatedDefaults == null)
+ {
+ updatedDefaults = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
+ }
- /// <summary>
- /// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection
- /// of parts.
- /// </summary>
- /// <param name="parts">The collection of parts.</param>
- /// <returns>The <see cref="RoutePatternPathSegment"/>.</returns>
- public static RoutePatternPathSegment Segment(params RoutePatternPart[] parts)
- {
- if (parts == null)
- {
- throw new ArgumentNullException(nameof(parts));
+ updatedDefaults[parameter.Name] = parameter.Default;
}
- return SegmentCore((RoutePatternPart[])parts.Clone());
- }
+ List<RoutePatternParameterPolicyReference>? parameterConstraints = null;
+ if ((updatedParameterPolicies == null || !updatedParameterPolicies.TryGetValue(parameter.Name, out parameterConstraints)) &&
+ parameter.ParameterPolicies.Count > 0)
+ {
+ if (updatedParameterPolicies == null)
+ {
+ updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(StringComparer.OrdinalIgnoreCase);
+ }
- private static RoutePatternPathSegment SegmentCore(RoutePatternPart[] parts)
- {
- return new RoutePatternPathSegment(parts);
- }
+ parameterConstraints = new List<RoutePatternParameterPolicyReference>(parameter.ParameterPolicies.Count);
+ updatedParameterPolicies.Add(parameter.Name, parameterConstraints);
+ }
- /// <summary>
- /// Creates a <see cref="RoutePatternLiteralPart"/> from the provided text
- /// content.
- /// </summary>
- /// <param name="content">The text content.</param>
- /// <returns>The <see cref="RoutePatternLiteralPart"/>.</returns>
- public static RoutePatternLiteralPart LiteralPart(string content)
- {
- if (string.IsNullOrEmpty(content))
+ if (parameter.ParameterPolicies.Count > 0)
{
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
+ parameterConstraints!.AddRange(parameter.ParameterPolicies);
}
- if (content.IndexOf('?') >= 0)
+ if (Equals(parameter.Default, @default)
+ && parameter.ParameterPolicies.Count == 0
+ && (parameterConstraints?.Count ?? 0) == 0)
{
- throw new ArgumentException(Resources.FormatTemplateRoute_InvalidLiteral(content));
+ // Part has not changed
+ return part;
}
- return LiteralPartCore(content);
+ return ParameterPartCore(
+ parameter.Name,
+ @default,
+ parameter.ParameterKind,
+ parameterConstraints?.ToArray() ?? Array.Empty<RoutePatternParameterPolicyReference>(),
+ parameter.EncodeSlashes);
}
+ }
- private static RoutePatternLiteralPart LiteralPartCore(string content)
+ /// <summary>
+ /// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection
+ /// of parts.
+ /// </summary>
+ /// <param name="parts">The collection of parts.</param>
+ /// <returns>The <see cref="RoutePatternPathSegment"/>.</returns>
+ public static RoutePatternPathSegment Segment(IEnumerable<RoutePatternPart> parts)
+ {
+ if (parts == null)
{
- return new RoutePatternLiteralPart(content);
+ throw new ArgumentNullException(nameof(parts));
}
- /// <summary>
- /// Creates a <see cref="RoutePatternSeparatorPart"/> from the provided text
- /// content.
- /// </summary>
- /// <param name="content">The text content.</param>
- /// <returns>The <see cref="RoutePatternSeparatorPart"/>.</returns>
- public static RoutePatternSeparatorPart SeparatorPart(string content)
- {
- if (string.IsNullOrEmpty(content))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
- }
+ return SegmentCore(parts.ToArray());
+ }
- return SeparatorPartCore(content);
+ /// <summary>
+ /// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection
+ /// of parts.
+ /// </summary>
+ /// <param name="parts">The collection of parts.</param>
+ /// <returns>The <see cref="RoutePatternPathSegment"/>.</returns>
+ public static RoutePatternPathSegment Segment(params RoutePatternPart[] parts)
+ {
+ if (parts == null)
+ {
+ throw new ArgumentNullException(nameof(parts));
}
- private static RoutePatternSeparatorPart SeparatorPartCore(string content)
+ return SegmentCore((RoutePatternPart[])parts.Clone());
+ }
+
+ private static RoutePatternPathSegment SegmentCore(RoutePatternPart[] parts)
+ {
+ return new RoutePatternPathSegment(parts);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternLiteralPart"/> from the provided text
+ /// content.
+ /// </summary>
+ /// <param name="content">The text content.</param>
+ /// <returns>The <see cref="RoutePatternLiteralPart"/>.</returns>
+ public static RoutePatternLiteralPart LiteralPart(string content)
+ {
+ if (string.IsNullOrEmpty(content))
{
- return new RoutePatternSeparatorPart(content);
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
}
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name.
- /// </summary>
- /// <param name="parameterName">The parameter name.</param>
- /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
- public static RoutePatternParameterPart ParameterPart(string parameterName)
+ if (content.IndexOf('?') >= 0)
{
- if (string.IsNullOrEmpty(parameterName))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
- }
-
- if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
- {
- throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
- }
+ throw new ArgumentException(Resources.FormatTemplateRoute_InvalidLiteral(content));
+ }
- return ParameterPartCore(
- parameterName: parameterName,
- @default: null,
- parameterKind: RoutePatternParameterKind.Standard,
- parameterPolicies: Array.Empty<RoutePatternParameterPolicyReference>());
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
- /// and default value.
- /// </summary>
- /// <param name="parameterName">The parameter name.</param>
- /// <param name="default">The parameter default value. May be <c>null</c>.</param>
- /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
- public static RoutePatternParameterPart ParameterPart(string parameterName, object @default)
- {
- if (string.IsNullOrEmpty(parameterName))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
- }
+ return LiteralPartCore(content);
+ }
- if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
- {
- throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
- }
+ private static RoutePatternLiteralPart LiteralPartCore(string content)
+ {
+ return new RoutePatternLiteralPart(content);
+ }
- return ParameterPartCore(
- parameterName: parameterName,
- @default: @default,
- parameterKind: RoutePatternParameterKind.Standard,
- parameterPolicies: Array.Empty<RoutePatternParameterPolicyReference>());
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
- /// and default value, and parameter kind.
- /// </summary>
- /// <param name="parameterName">The parameter name.</param>
- /// <param name="default">The parameter default value. May be <c>null</c>.</param>
- /// <param name="parameterKind">The parameter kind.</param>
- /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
- public static RoutePatternParameterPart ParameterPart(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind)
- {
- if (string.IsNullOrEmpty(parameterName))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
- }
+ /// <summary>
+ /// Creates a <see cref="RoutePatternSeparatorPart"/> from the provided text
+ /// content.
+ /// </summary>
+ /// <param name="content">The text content.</param>
+ /// <returns>The <see cref="RoutePatternSeparatorPart"/>.</returns>
+ public static RoutePatternSeparatorPart SeparatorPart(string content)
+ {
+ if (string.IsNullOrEmpty(content))
+ {
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(content));
+ }
- if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
- {
- throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
- }
+ return SeparatorPartCore(content);
+ }
- if (@default != null && parameterKind == RoutePatternParameterKind.Optional)
- {
- throw new ArgumentNullException(nameof(parameterKind), Resources.TemplateRoute_OptionalCannotHaveDefaultValue);
- }
+ private static RoutePatternSeparatorPart SeparatorPartCore(string content)
+ {
+ return new RoutePatternSeparatorPart(content);
+ }
- return ParameterPartCore(
- parameterName: parameterName,
- @default: @default,
- parameterKind: parameterKind,
- parameterPolicies: Array.Empty<RoutePatternParameterPolicyReference>());
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
- /// and default value, parameter kind, and parameter policies.
- /// </summary>
- /// <param name="parameterName">The parameter name.</param>
- /// <param name="default">The parameter default value. May be <c>null</c>.</param>
- /// <param name="parameterKind">The parameter kind.</param>
- /// <param name="parameterPolicies">The parameter policies to associated with the parameter.</param>
- /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
- public static RoutePatternParameterPart ParameterPart(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind,
- IEnumerable<RoutePatternParameterPolicyReference> parameterPolicies)
- {
- if (string.IsNullOrEmpty(parameterName))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
- }
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name.
+ /// </summary>
+ /// <param name="parameterName">The parameter name.</param>
+ /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
+ public static RoutePatternParameterPart ParameterPart(string parameterName)
+ {
+ if (string.IsNullOrEmpty(parameterName))
+ {
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
+ }
- if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
- {
- throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
- }
+ if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
+ {
+ throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
+ }
- if (@default != null && parameterKind == RoutePatternParameterKind.Optional)
- {
- throw new ArgumentNullException(nameof(parameterKind), Resources.TemplateRoute_OptionalCannotHaveDefaultValue);
- }
+ return ParameterPartCore(
+ parameterName: parameterName,
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ parameterPolicies: Array.Empty<RoutePatternParameterPolicyReference>());
+ }
- if (parameterPolicies == null)
- {
- throw new ArgumentNullException(nameof(parameterPolicies));
- }
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
+ /// and default value.
+ /// </summary>
+ /// <param name="parameterName">The parameter name.</param>
+ /// <param name="default">The parameter default value. May be <c>null</c>.</param>
+ /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
+ public static RoutePatternParameterPart ParameterPart(string parameterName, object @default)
+ {
+ if (string.IsNullOrEmpty(parameterName))
+ {
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
+ }
- return ParameterPartCore(
- parameterName: parameterName,
- @default: @default,
- parameterKind: parameterKind,
- parameterPolicies: parameterPolicies.ToArray());
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
- /// and default value, parameter kind, and parameter policies.
- /// </summary>
- /// <param name="parameterName">The parameter name.</param>
- /// <param name="default">The parameter default value. May be <c>null</c>.</param>
- /// <param name="parameterKind">The parameter kind.</param>
- /// <param name="parameterPolicies">The parameter policies to associated with the parameter.</param>
- /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
- public static RoutePatternParameterPart ParameterPart(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind,
- params RoutePatternParameterPolicyReference[] parameterPolicies)
- {
- if (string.IsNullOrEmpty(parameterName))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
- }
+ if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
+ {
+ throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
+ }
- if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
- {
- throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
- }
+ return ParameterPartCore(
+ parameterName: parameterName,
+ @default: @default,
+ parameterKind: RoutePatternParameterKind.Standard,
+ parameterPolicies: Array.Empty<RoutePatternParameterPolicyReference>());
+ }
- if (@default != null && parameterKind == RoutePatternParameterKind.Optional)
- {
- throw new ArgumentNullException(nameof(parameterKind), Resources.TemplateRoute_OptionalCannotHaveDefaultValue);
- }
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
+ /// and default value, and parameter kind.
+ /// </summary>
+ /// <param name="parameterName">The parameter name.</param>
+ /// <param name="default">The parameter default value. May be <c>null</c>.</param>
+ /// <param name="parameterKind">The parameter kind.</param>
+ /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
+ public static RoutePatternParameterPart ParameterPart(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind)
+ {
+ if (string.IsNullOrEmpty(parameterName))
+ {
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
+ }
- if (parameterPolicies == null)
- {
- throw new ArgumentNullException(nameof(parameterPolicies));
- }
+ if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
+ {
+ throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
+ }
- return ParameterPartCore(
- parameterName: parameterName,
- @default: @default,
- parameterKind: parameterKind,
- parameterPolicies: (RoutePatternParameterPolicyReference[])parameterPolicies.Clone());
+ if (@default != null && parameterKind == RoutePatternParameterKind.Optional)
+ {
+ throw new ArgumentNullException(nameof(parameterKind), Resources.TemplateRoute_OptionalCannotHaveDefaultValue);
}
- private static RoutePatternParameterPart ParameterPartCore(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind,
- RoutePatternParameterPolicyReference[] parameterPolicies)
+ return ParameterPartCore(
+ parameterName: parameterName,
+ @default: @default,
+ parameterKind: parameterKind,
+ parameterPolicies: Array.Empty<RoutePatternParameterPolicyReference>());
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
+ /// and default value, parameter kind, and parameter policies.
+ /// </summary>
+ /// <param name="parameterName">The parameter name.</param>
+ /// <param name="default">The parameter default value. May be <c>null</c>.</param>
+ /// <param name="parameterKind">The parameter kind.</param>
+ /// <param name="parameterPolicies">The parameter policies to associated with the parameter.</param>
+ /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
+ public static RoutePatternParameterPart ParameterPart(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind,
+ IEnumerable<RoutePatternParameterPolicyReference> parameterPolicies)
+ {
+ if (string.IsNullOrEmpty(parameterName))
{
- return ParameterPartCore(parameterName, @default, parameterKind, parameterPolicies, encodeSlashes: true);
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
}
- private static RoutePatternParameterPart ParameterPartCore(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind,
- RoutePatternParameterPolicyReference[] parameterPolicies,
- bool encodeSlashes)
+ if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
{
- return new RoutePatternParameterPart(
- parameterName,
- @default,
- parameterKind,
- parameterPolicies,
- encodeSlashes);
- }
-
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided contraint.
- /// </summary>
- /// <param name="constraint">
- /// The constraint object, which must be of type <see cref="IRouteConstraint"/>
- /// or <see cref="string"/>. If the constraint object is a <see cref="string"/>
- /// then it will be transformed into an instance of <see cref="RegexRouteConstraint"/>.
- /// </param>
- /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
- public static RoutePatternParameterPolicyReference Constraint(object constraint)
- {
- // Similar to RouteConstraintBuilder
- if (constraint is IRouteConstraint policy)
- {
- return ParameterPolicyCore(policy);
- }
- else if (constraint is string content)
- {
- return ParameterPolicyCore(new RegexRouteConstraint("^(" + content + ")$"));
- }
- else
- {
- throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
- constraint ?? "null",
- typeof(IRouteConstraint)));
- }
+ throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
}
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided constraint.
- /// </summary>
- /// <param name="constraint">
- /// The constraint object.
- /// </param>
- /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
- public static RoutePatternParameterPolicyReference Constraint(IRouteConstraint constraint)
+ if (@default != null && parameterKind == RoutePatternParameterKind.Optional)
{
- if (constraint == null)
- {
- throw new ArgumentNullException(nameof(constraint));
- }
+ throw new ArgumentNullException(nameof(parameterKind), Resources.TemplateRoute_OptionalCannotHaveDefaultValue);
+ }
- return ParameterPolicyCore(constraint);
+ if (parameterPolicies == null)
+ {
+ throw new ArgumentNullException(nameof(parameterPolicies));
}
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided constraint.
- /// </summary>
- /// <param name="constraint">
- /// The constraint text, which will be resolved by <see cref="ParameterPolicyFactory"/>.
- /// </param>
- /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
- public static RoutePatternParameterPolicyReference Constraint(string constraint)
+ return ParameterPartCore(
+ parameterName: parameterName,
+ @default: @default,
+ parameterKind: parameterKind,
+ parameterPolicies: parameterPolicies.ToArray());
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPart"/> from the provided parameter name
+ /// and default value, parameter kind, and parameter policies.
+ /// </summary>
+ /// <param name="parameterName">The parameter name.</param>
+ /// <param name="default">The parameter default value. May be <c>null</c>.</param>
+ /// <param name="parameterKind">The parameter kind.</param>
+ /// <param name="parameterPolicies">The parameter policies to associated with the parameter.</param>
+ /// <returns>The <see cref="RoutePatternParameterPart"/>.</returns>
+ public static RoutePatternParameterPart ParameterPart(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind,
+ params RoutePatternParameterPolicyReference[] parameterPolicies)
+ {
+ if (string.IsNullOrEmpty(parameterName))
{
- if (string.IsNullOrEmpty(constraint))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(constraint));
- }
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterName));
+ }
- return ParameterPolicyCore(constraint);
+ if (parameterName.IndexOfAny(RoutePatternParser.InvalidParameterNameChars) >= 0)
+ {
+ throw new ArgumentException(Resources.FormatTemplateRoute_InvalidParameterName(parameterName));
}
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided object.
- /// </summary>
- /// <param name="parameterPolicy">
- /// The parameter policy object.
- /// </param>
- /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
- public static RoutePatternParameterPolicyReference ParameterPolicy(IParameterPolicy parameterPolicy)
+ if (@default != null && parameterKind == RoutePatternParameterKind.Optional)
{
- if (parameterPolicy == null)
- {
- throw new ArgumentNullException(nameof(parameterPolicy));
- }
+ throw new ArgumentNullException(nameof(parameterKind), Resources.TemplateRoute_OptionalCannotHaveDefaultValue);
+ }
- return ParameterPolicyCore(parameterPolicy);
+ if (parameterPolicies == null)
+ {
+ throw new ArgumentNullException(nameof(parameterPolicies));
}
- /// <summary>
- /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided object.
- /// </summary>
- /// <param name="parameterPolicy">
- /// The parameter policy text, which will be resolved by <see cref="ParameterPolicyFactory"/>.
- /// </param>
- /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
- public static RoutePatternParameterPolicyReference ParameterPolicy(string parameterPolicy)
+ return ParameterPartCore(
+ parameterName: parameterName,
+ @default: @default,
+ parameterKind: parameterKind,
+ parameterPolicies: (RoutePatternParameterPolicyReference[])parameterPolicies.Clone());
+ }
+
+ private static RoutePatternParameterPart ParameterPartCore(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind,
+ RoutePatternParameterPolicyReference[] parameterPolicies)
+ {
+ return ParameterPartCore(parameterName, @default, parameterKind, parameterPolicies, encodeSlashes: true);
+ }
+
+ private static RoutePatternParameterPart ParameterPartCore(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind,
+ RoutePatternParameterPolicyReference[] parameterPolicies,
+ bool encodeSlashes)
+ {
+ return new RoutePatternParameterPart(
+ parameterName,
+ @default,
+ parameterKind,
+ parameterPolicies,
+ encodeSlashes);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided contraint.
+ /// </summary>
+ /// <param name="constraint">
+ /// The constraint object, which must be of type <see cref="IRouteConstraint"/>
+ /// or <see cref="string"/>. If the constraint object is a <see cref="string"/>
+ /// then it will be transformed into an instance of <see cref="RegexRouteConstraint"/>.
+ /// </param>
+ /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
+ public static RoutePatternParameterPolicyReference Constraint(object constraint)
+ {
+ // Similar to RouteConstraintBuilder
+ if (constraint is IRouteConstraint policy)
{
- if (string.IsNullOrEmpty(parameterPolicy))
- {
- throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterPolicy));
- }
+ return ParameterPolicyCore(policy);
+ }
+ else if (constraint is string content)
+ {
+ return ParameterPolicyCore(new RegexRouteConstraint("^(" + content + ")$"));
+ }
+ else
+ {
+ throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
+ constraint ?? "null",
+ typeof(IRouteConstraint)));
+ }
+ }
- return ParameterPolicyCore(parameterPolicy);
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided constraint.
+ /// </summary>
+ /// <param name="constraint">
+ /// The constraint object.
+ /// </param>
+ /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
+ public static RoutePatternParameterPolicyReference Constraint(IRouteConstraint constraint)
+ {
+ if (constraint == null)
+ {
+ throw new ArgumentNullException(nameof(constraint));
}
- private static RoutePatternParameterPolicyReference ParameterPolicyCore(string parameterPolicy)
+ return ParameterPolicyCore(constraint);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided constraint.
+ /// </summary>
+ /// <param name="constraint">
+ /// The constraint text, which will be resolved by <see cref="ParameterPolicyFactory"/>.
+ /// </param>
+ /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
+ public static RoutePatternParameterPolicyReference Constraint(string constraint)
+ {
+ if (string.IsNullOrEmpty(constraint))
{
- return new RoutePatternParameterPolicyReference(parameterPolicy);
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(constraint));
}
- private static RoutePatternParameterPolicyReference ParameterPolicyCore(IParameterPolicy parameterPolicy)
+ return ParameterPolicyCore(constraint);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided object.
+ /// </summary>
+ /// <param name="parameterPolicy">
+ /// The parameter policy object.
+ /// </param>
+ /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
+ public static RoutePatternParameterPolicyReference ParameterPolicy(IParameterPolicy parameterPolicy)
+ {
+ if (parameterPolicy == null)
{
- return new RoutePatternParameterPolicyReference(parameterPolicy);
+ throw new ArgumentNullException(nameof(parameterPolicy));
}
- private static RouteValueDictionary? Wrap(object? values)
+ return ParameterPolicyCore(parameterPolicy);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="RoutePatternParameterPolicyReference"/> from the provided object.
+ /// </summary>
+ /// <param name="parameterPolicy">
+ /// The parameter policy text, which will be resolved by <see cref="ParameterPolicyFactory"/>.
+ /// </param>
+ /// <returns>The <see cref="RoutePatternParameterPolicyReference"/>.</returns>
+ public static RoutePatternParameterPolicyReference ParameterPolicy(string parameterPolicy)
+ {
+ if (string.IsNullOrEmpty(parameterPolicy))
{
- return values == null ? null : new RouteValueDictionary(values);
+ throw new ArgumentException(Resources.Argument_NullOrEmpty, nameof(parameterPolicy));
}
+
+ return ParameterPolicyCore(parameterPolicy);
+ }
+
+ private static RoutePatternParameterPolicyReference ParameterPolicyCore(string parameterPolicy)
+ {
+ return new RoutePatternParameterPolicyReference(parameterPolicy);
+ }
+
+ private static RoutePatternParameterPolicyReference ParameterPolicyCore(IParameterPolicy parameterPolicy)
+ {
+ return new RoutePatternParameterPolicyReference(parameterPolicy);
+ }
+
+ private static RouteValueDictionary? Wrap(object? values)
+ {
+ return values == null ? null : new RouteValueDictionary(values);
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternLiteralPart.cs b/src/Http/Routing/src/Patterns/RoutePatternLiteralPart.cs
index b979f046ec..ae9b930687 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternLiteralPart.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternLiteralPart.cs
@@ -3,30 +3,29 @@
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Resprents a literal text part of a route pattern. Instances of <see cref="RoutePatternLiteralPart"/>
+/// are immutable.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public sealed class RoutePatternLiteralPart : RoutePatternPart
{
- /// <summary>
- /// Resprents a literal text part of a route pattern. Instances of <see cref="RoutePatternLiteralPart"/>
- /// are immutable.
- /// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public sealed class RoutePatternLiteralPart : RoutePatternPart
+ internal RoutePatternLiteralPart(string content)
+ : base(RoutePatternPartKind.Literal)
{
- internal RoutePatternLiteralPart(string content)
- : base(RoutePatternPartKind.Literal)
- {
- Debug.Assert(!string.IsNullOrEmpty(content));
- Content = content;
- }
+ Debug.Assert(!string.IsNullOrEmpty(content));
+ Content = content;
+ }
- /// <summary>
- /// Gets the text content.
- /// </summary>
- public string Content { get; }
+ /// <summary>
+ /// Gets the text content.
+ /// </summary>
+ public string Content { get; }
- internal override string DebuggerToString()
- {
- return Content;
- }
+ internal override string DebuggerToString()
+ {
+ return Content;
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternMatcher.cs b/src/Http/Routing/src/Patterns/RoutePatternMatcher.cs
index cf8bd663aa..5a8c9c4b03 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternMatcher.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternMatcher.cs
@@ -9,497 +9,496 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class RoutePatternMatcher
{
- internal class RoutePatternMatcher
- {
- // Perf: This is a cache to avoid looking things up in 'Defaults' each request.
- private readonly bool[] _hasDefaultValue;
- private readonly object[] _defaultValues;
+ // Perf: This is a cache to avoid looking things up in 'Defaults' each request.
+ private readonly bool[] _hasDefaultValue;
+ private readonly object[] _defaultValues;
- public RoutePatternMatcher(
- RoutePattern pattern,
- RouteValueDictionary defaults)
+ public RoutePatternMatcher(
+ RoutePattern pattern,
+ RouteValueDictionary defaults)
+ {
+ if (pattern == null)
{
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ throw new ArgumentNullException(nameof(pattern));
+ }
- RoutePattern = pattern;
- Defaults = defaults ?? new RouteValueDictionary();
+ RoutePattern = pattern;
+ Defaults = defaults ?? new RouteValueDictionary();
- // Perf: cache the default value for each parameter (other than complex segments).
- _hasDefaultValue = new bool[RoutePattern.PathSegments.Count];
- _defaultValues = new object[RoutePattern.PathSegments.Count];
+ // Perf: cache the default value for each parameter (other than complex segments).
+ _hasDefaultValue = new bool[RoutePattern.PathSegments.Count];
+ _defaultValues = new object[RoutePattern.PathSegments.Count];
- for (var i = 0; i < RoutePattern.PathSegments.Count; i++)
+ for (var i = 0; i < RoutePattern.PathSegments.Count; i++)
+ {
+ var segment = RoutePattern.PathSegments[i];
+ if (!segment.IsSimple)
{
- var segment = RoutePattern.PathSegments[i];
- if (!segment.IsSimple)
- {
- continue;
- }
+ continue;
+ }
- var part = segment.Parts[0];
- if (!part.IsParameter)
- {
- continue;
- }
+ var part = segment.Parts[0];
+ if (!part.IsParameter)
+ {
+ continue;
+ }
- var parameter = (RoutePatternParameterPart)part;
- if (Defaults.TryGetValue(parameter.Name, out var value))
- {
- _hasDefaultValue[i] = true;
- _defaultValues[i] = value;
- }
+ var parameter = (RoutePatternParameterPart)part;
+ if (Defaults.TryGetValue(parameter.Name, out var value))
+ {
+ _hasDefaultValue[i] = true;
+ _defaultValues[i] = value;
}
}
+ }
- public RouteValueDictionary Defaults { get; }
+ public RouteValueDictionary Defaults { get; }
- public RoutePattern RoutePattern { get; }
+ public RoutePattern RoutePattern { get; }
+
+ public bool TryMatch(PathString path, RouteValueDictionary values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
+ }
- public bool TryMatch(PathString path, RouteValueDictionary values)
+ var i = 0;
+ var pathTokenizer = new PathTokenizer(path);
+
+ // Perf: We do a traversal of the request-segments + route-segments twice.
+ //
+ // For most segment-types, we only really need to any work on one of the two passes.
+ //
+ // On the first pass, we're just looking to see if there's anything that would disqualify us from matching.
+ // The most common case would be a literal segment that doesn't match.
+ //
+ // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
+ // and start capturing strings.
+ foreach (var stringSegment in pathTokenizer)
{
- if (values == null)
+ if (stringSegment.Length == 0)
{
- throw new ArgumentNullException(nameof(values));
+ return false;
}
- var i = 0;
- var pathTokenizer = new PathTokenizer(path);
-
- // Perf: We do a traversal of the request-segments + route-segments twice.
- //
- // For most segment-types, we only really need to any work on one of the two passes.
- //
- // On the first pass, we're just looking to see if there's anything that would disqualify us from matching.
- // The most common case would be a literal segment that doesn't match.
- //
- // On the second pass, we're almost certainly going to match the URL, so go ahead and allocate the 'values'
- // and start capturing strings.
- foreach (var stringSegment in pathTokenizer)
+ var pathSegment = i >= RoutePattern.PathSegments.Count ? null : RoutePattern.PathSegments[i];
+ if (pathSegment == null && stringSegment.Length > 0)
{
- if (stringSegment.Length == 0)
- {
- return false;
- }
-
- var pathSegment = i >= RoutePattern.PathSegments.Count ? null : RoutePattern.PathSegments[i];
- if (pathSegment == null && stringSegment.Length > 0)
- {
- // If pathSegment is null, then we're out of route segments. All we can match is the empty
- // string.
- return false;
- }
- else if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameterPart parameter && parameter.IsCatchAll)
- {
- // Nothing to validate for a catch-all - it can match any string, including the empty string.
- //
- // Also, a catch-all has to be the last part, so we're done.
- break;
- }
- if (!TryMatchLiterals(i++, stringSegment, pathSegment))
- {
- return false;
- }
+ // If pathSegment is null, then we're out of route segments. All we can match is the empty
+ // string.
+ return false;
}
-
- for (; i < RoutePattern.PathSegments.Count; i++)
+ else if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameterPart parameter && parameter.IsCatchAll)
{
- // We've matched the request path so far, but still have remaining route segments. These need
- // to be all single-part parameter segments with default values or else they won't match.
- var pathSegment = RoutePattern.PathSegments[i];
- Debug.Assert(pathSegment != null);
+ // Nothing to validate for a catch-all - it can match any string, including the empty string.
+ //
+ // Also, a catch-all has to be the last part, so we're done.
+ break;
+ }
+ if (!TryMatchLiterals(i++, stringSegment, pathSegment))
+ {
+ return false;
+ }
+ }
- if (!pathSegment.IsSimple)
- {
- // If the segment is a complex segment, it MUST contain literals, and we've parsed the full
- // path so far, so it can't match.
- return false;
- }
+ for (; i < RoutePattern.PathSegments.Count; i++)
+ {
+ // We've matched the request path so far, but still have remaining route segments. These need
+ // to be all single-part parameter segments with default values or else they won't match.
+ var pathSegment = RoutePattern.PathSegments[i];
+ Debug.Assert(pathSegment != null);
- var part = pathSegment.Parts[0];
- if (part.IsLiteral || part.IsSeparator)
- {
- // If the segment is a simple literal - which need the URL to provide a value, so we don't match.
- return false;
- }
+ if (!pathSegment.IsSimple)
+ {
+ // If the segment is a complex segment, it MUST contain literals, and we've parsed the full
+ // path so far, so it can't match.
+ return false;
+ }
- var parameter = (RoutePatternParameterPart)part;
- if (parameter.IsCatchAll)
- {
- // Nothing to validate for a catch-all - it can match any string, including the empty string.
- //
- // Also, a catch-all has to be the last part, so we're done.
- break;
- }
+ var part = pathSegment.Parts[0];
+ if (part.IsLiteral || part.IsSeparator)
+ {
+ // If the segment is a simple literal - which need the URL to provide a value, so we don't match.
+ return false;
+ }
- // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
- // defaults to have a value.
- if (!_hasDefaultValue[i] && !parameter.IsOptional)
- {
- // There's no default for this (non-optional) parameter so it can't match.
- return false;
- }
+ var parameter = (RoutePatternParameterPart)part;
+ if (parameter.IsCatchAll)
+ {
+ // Nothing to validate for a catch-all - it can match any string, including the empty string.
+ //
+ // Also, a catch-all has to be the last part, so we're done.
+ break;
}
- // At this point we've very likely got a match, so start capturing values for real.
- i = 0;
- foreach (var requestSegment in pathTokenizer)
+ // If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
+ // defaults to have a value.
+ if (!_hasDefaultValue[i] && !parameter.IsOptional)
{
- var pathSegment = RoutePattern.PathSegments[i++];
- if (SavePathSegmentsAsValues(i, values, requestSegment, pathSegment))
- {
- break;
- }
- if (!pathSegment.IsSimple)
+ // There's no default for this (non-optional) parameter so it can't match.
+ return false;
+ }
+ }
+
+ // At this point we've very likely got a match, so start capturing values for real.
+ i = 0;
+ foreach (var requestSegment in pathTokenizer)
+ {
+ var pathSegment = RoutePattern.PathSegments[i++];
+ if (SavePathSegmentsAsValues(i, values, requestSegment, pathSegment))
+ {
+ break;
+ }
+ if (!pathSegment.IsSimple)
+ {
+ if (!MatchComplexSegment(pathSegment, requestSegment.AsSpan(), values))
{
- if (!MatchComplexSegment(pathSegment, requestSegment.AsSpan(), values))
- {
- return false;
- }
+ return false;
}
}
+ }
- for (; i < RoutePattern.PathSegments.Count; i++)
- {
- // We've matched the request path so far, but still have remaining route segments. We already know these
- // are simple parameters that either have a default, or don't need to produce a value.
- var pathSegment = RoutePattern.PathSegments[i];
- Debug.Assert(pathSegment != null);
- Debug.Assert(pathSegment.IsSimple);
+ for (; i < RoutePattern.PathSegments.Count; i++)
+ {
+ // We've matched the request path so far, but still have remaining route segments. We already know these
+ // are simple parameters that either have a default, or don't need to produce a value.
+ var pathSegment = RoutePattern.PathSegments[i];
+ Debug.Assert(pathSegment != null);
+ Debug.Assert(pathSegment.IsSimple);
- var part = pathSegment.Parts[0];
- Debug.Assert(part.IsParameter);
+ var part = pathSegment.Parts[0];
+ Debug.Assert(part.IsParameter);
- // It's ok for a catch-all to produce a null value
- if (part is RoutePatternParameterPart parameter && (parameter.IsCatchAll || _hasDefaultValue[i]))
+ // It's ok for a catch-all to produce a null value
+ if (part is RoutePatternParameterPart parameter && (parameter.IsCatchAll || _hasDefaultValue[i]))
+ {
+ // Don't replace an existing value with a null.
+ var defaultValue = _defaultValues[i];
+ if (defaultValue != null || !values.ContainsKey(parameter.Name))
{
- // Don't replace an existing value with a null.
- var defaultValue = _defaultValues[i];
- if (defaultValue != null || !values.ContainsKey(parameter.Name))
- {
- values[parameter.Name] = defaultValue;
- }
+ values[parameter.Name] = defaultValue;
}
}
+ }
- // Copy all remaining default values to the route data
- foreach (var kvp in Defaults)
- {
+ // Copy all remaining default values to the route data
+ foreach (var kvp in Defaults)
+ {
#if RVD_TryAdd
values.TryAdd(kvp.Key, kvp.Value);
#else
- if (!values.ContainsKey(kvp.Key))
- {
- values.Add(kvp.Key, kvp.Value);
- }
-#endif
+ if (!values.ContainsKey(kvp.Key))
+ {
+ values.Add(kvp.Key, kvp.Value);
}
-
- return true;
+#endif
}
- private bool TryMatchLiterals(int index, StringSegment stringSegment, RoutePatternPathSegment pathSegment)
+ return true;
+ }
+
+ private bool TryMatchLiterals(int index, StringSegment stringSegment, RoutePatternPathSegment pathSegment)
+ {
+ if (pathSegment.IsSimple && !pathSegment.Parts[0].IsParameter)
{
- if (pathSegment.IsSimple && !pathSegment.Parts[0].IsParameter)
+ // This is a literal segment, so we need to match the text, or the route isn't a match.
+ if (pathSegment.Parts[0].IsLiteral)
{
- // This is a literal segment, so we need to match the text, or the route isn't a match.
- if (pathSegment.Parts[0].IsLiteral)
- {
- var part = (RoutePatternLiteralPart)pathSegment.Parts[0];
+ var part = (RoutePatternLiteralPart)pathSegment.Parts[0];
- if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- }
- else
+ if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
{
- var part = (RoutePatternSeparatorPart)pathSegment.Parts[0];
-
- if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
}
- else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
+ else
{
- // For a parameter, validate that it's a has some length, or we have a default, or it's optional.
- var part = (RoutePatternParameterPart)pathSegment.Parts[0];
- if (stringSegment.Length == 0 &&
- !_hasDefaultValue[index] &&
- !part.IsOptional)
+ var part = (RoutePatternSeparatorPart)pathSegment.Parts[0];
+
+ if (!stringSegment.Equals(part.Content, StringComparison.OrdinalIgnoreCase))
{
- // There's no value for this parameter, the route can't match.
return false;
}
}
- else
+ }
+ else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
+ {
+ // For a parameter, validate that it's a has some length, or we have a default, or it's optional.
+ var part = (RoutePatternParameterPart)pathSegment.Parts[0];
+ if (stringSegment.Length == 0 &&
+ !_hasDefaultValue[index] &&
+ !part.IsOptional)
{
- Debug.Assert(!pathSegment.IsSimple);
- // Don't attempt to validate a complex segment at this point other than being non-empty,
- // do it in the second pass.
+ // There's no value for this parameter, the route can't match.
+ return false;
}
- return true;
}
+ else
+ {
+ Debug.Assert(!pathSegment.IsSimple);
+ // Don't attempt to validate a complex segment at this point other than being non-empty,
+ // do it in the second pass.
+ }
+ return true;
+ }
- private bool SavePathSegmentsAsValues(int index, RouteValueDictionary values, StringSegment requestSegment, RoutePatternPathSegment pathSegment)
+ private bool SavePathSegmentsAsValues(int index, RouteValueDictionary values, StringSegment requestSegment, RoutePatternPathSegment pathSegment)
+ {
+ if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameterPart parameter && parameter.IsCatchAll)
{
- if (pathSegment.IsSimple && pathSegment.Parts[0] is RoutePatternParameterPart parameter && parameter.IsCatchAll)
+ // A catch-all captures til the end of the string.
+ var captured = requestSegment.Buffer.Substring(requestSegment.Offset);
+ if (captured.Length > 0)
{
- // A catch-all captures til the end of the string.
- var captured = requestSegment.Buffer.Substring(requestSegment.Offset);
- if (captured.Length > 0)
- {
- values[parameter.Name] = captured;
- }
- else
- {
- // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
- values[parameter.Name] = _defaultValues[index];
- }
+ values[parameter.Name] = captured;
+ }
+ else
+ {
+ // It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
+ values[parameter.Name] = _defaultValues[index];
+ }
- // A catch-all has to be the last part, so we're done.
- return true;
+ // A catch-all has to be the last part, so we're done.
+ return true;
+ }
+ else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
+ {
+ // A simple parameter captures the whole segment, or a default value if nothing was
+ // provided.
+ parameter = (RoutePatternParameterPart)pathSegment.Parts[0];
+ if (requestSegment.Length > 0)
+ {
+ values[parameter.Name] = requestSegment.ToString();
}
- else if (pathSegment.IsSimple && pathSegment.Parts[0].IsParameter)
+ else
{
- // A simple parameter captures the whole segment, or a default value if nothing was
- // provided.
- parameter = (RoutePatternParameterPart)pathSegment.Parts[0];
- if (requestSegment.Length > 0)
+ if (_hasDefaultValue[index])
{
- values[parameter.Name] = requestSegment.ToString();
- }
- else
- {
- if (_hasDefaultValue[index])
- {
- values[parameter.Name] = _defaultValues[index];
- }
+ values[parameter.Name] = _defaultValues[index];
}
}
- return false;
}
+ return false;
+ }
- internal static bool MatchComplexSegment(
- RoutePatternPathSegment routeSegment,
- ReadOnlySpan<char> requestSegment,
- RouteValueDictionary values)
+ internal static bool MatchComplexSegment(
+ RoutePatternPathSegment routeSegment,
+ ReadOnlySpan<char> requestSegment,
+ RouteValueDictionary values)
+ {
+ var indexOfLastSegment = routeSegment.Parts.Count - 1;
+
+ // We match the request to the template starting at the rightmost parameter
+ // If the last segment of template is optional, then request can match the
+ // template with or without the last parameter. So we start with regular matching,
+ // but if it doesn't match, we start with next to last parameter. Example:
+ // Template: {p1}/{p2}.{p3?}. If the request is one/two.three it will match right away
+ // giving p3 value of three. But if the request is one/two, we start matching from the
+ // rightmost giving p3 the value of two, then we end up not matching the segment.
+ // In this case we start again from p2 to match the request and we succeed giving
+ // the value two to p2
+ if (routeSegment.Parts[indexOfLastSegment] is RoutePatternParameterPart parameter && parameter.IsOptional &&
+ routeSegment.Parts[indexOfLastSegment - 1].IsSeparator)
{
- var indexOfLastSegment = routeSegment.Parts.Count - 1;
-
- // We match the request to the template starting at the rightmost parameter
- // If the last segment of template is optional, then request can match the
- // template with or without the last parameter. So we start with regular matching,
- // but if it doesn't match, we start with next to last parameter. Example:
- // Template: {p1}/{p2}.{p3?}. If the request is one/two.three it will match right away
- // giving p3 value of three. But if the request is one/two, we start matching from the
- // rightmost giving p3 the value of two, then we end up not matching the segment.
- // In this case we start again from p2 to match the request and we succeed giving
- // the value two to p2
- if (routeSegment.Parts[indexOfLastSegment] is RoutePatternParameterPart parameter && parameter.IsOptional &&
- routeSegment.Parts[indexOfLastSegment - 1].IsSeparator)
+ if (MatchComplexSegmentCore(routeSegment, requestSegment, values, indexOfLastSegment))
{
- if (MatchComplexSegmentCore(routeSegment, requestSegment, values, indexOfLastSegment))
- {
- return true;
- }
- else
- {
- var separator = (RoutePatternSeparatorPart)routeSegment.Parts[indexOfLastSegment - 1];
- if (requestSegment.EndsWith(
- separator.Content,
- StringComparison.OrdinalIgnoreCase))
- return false;
-
- return MatchComplexSegmentCore(
- routeSegment,
- requestSegment,
- values,
- indexOfLastSegment - 2);
- }
+ return true;
}
else
{
- return MatchComplexSegmentCore(routeSegment, requestSegment, values, indexOfLastSegment);
+ var separator = (RoutePatternSeparatorPart)routeSegment.Parts[indexOfLastSegment - 1];
+ if (requestSegment.EndsWith(
+ separator.Content,
+ StringComparison.OrdinalIgnoreCase))
+ return false;
+
+ return MatchComplexSegmentCore(
+ routeSegment,
+ requestSegment,
+ values,
+ indexOfLastSegment - 2);
}
}
-
- private static bool MatchComplexSegmentCore(
- RoutePatternPathSegment routeSegment,
- ReadOnlySpan<char> requestSegment,
- RouteValueDictionary values,
- int indexOfLastSegmentUsed)
+ else
{
- Debug.Assert(routeSegment != null);
- Debug.Assert(routeSegment.Parts.Count > 1);
+ return MatchComplexSegmentCore(routeSegment, requestSegment, values, indexOfLastSegment);
+ }
+ }
+
+ private static bool MatchComplexSegmentCore(
+ RoutePatternPathSegment routeSegment,
+ ReadOnlySpan<char> requestSegment,
+ RouteValueDictionary values,
+ int indexOfLastSegmentUsed)
+ {
+ Debug.Assert(routeSegment != null);
+ Debug.Assert(routeSegment.Parts.Count > 1);
- // Find last literal segment and get its last index in the string
- var lastIndex = requestSegment.Length;
+ // Find last literal segment and get its last index in the string
+ var lastIndex = requestSegment.Length;
- RoutePatternParameterPart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value
- RoutePatternPart lastLiteral = null; // Keeps track of the left-most literal we've encountered
+ RoutePatternParameterPart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value
+ RoutePatternPart lastLiteral = null; // Keeps track of the left-most literal we've encountered
- var outValues = new RouteValueDictionary();
+ var outValues = new RouteValueDictionary();
- while (indexOfLastSegmentUsed >= 0)
+ while (indexOfLastSegmentUsed >= 0)
+ {
+ var newLastIndex = lastIndex;
+
+ var part = routeSegment.Parts[indexOfLastSegmentUsed];
+ if (part.IsParameter)
{
- var newLastIndex = lastIndex;
+ // Hold on to the parameter so that we can fill it in when we locate the next literal
+ parameterNeedsValue = (RoutePatternParameterPart)part;
+ }
+ else
+ {
+ Debug.Assert(part.IsLiteral || part.IsSeparator);
+ lastLiteral = part;
- var part = routeSegment.Parts[indexOfLastSegmentUsed];
- if (part.IsParameter)
+ var startIndex = lastIndex;
+ // If we have a pending parameter subsegment, we must leave at least one character for that
+ if (parameterNeedsValue != null)
{
- // Hold on to the parameter so that we can fill it in when we locate the next literal
- parameterNeedsValue = (RoutePatternParameterPart)part;
+ startIndex--;
+ }
+
+ if (startIndex == 0)
+ {
+ return false;
+ }
+
+ int indexOfLiteral;
+ if (part.IsLiteral)
+ {
+ var literal = (RoutePatternLiteralPart)part;
+ indexOfLiteral = requestSegment.Slice(0, startIndex).LastIndexOf(
+ literal.Content,
+ StringComparison.OrdinalIgnoreCase);
}
else
{
- Debug.Assert(part.IsLiteral || part.IsSeparator);
- lastLiteral = part;
+ var literal = (RoutePatternSeparatorPart)part;
+ indexOfLiteral = requestSegment.Slice(0, startIndex).LastIndexOf(
+ literal.Content,
+ StringComparison.OrdinalIgnoreCase);
+ }
- var startIndex = lastIndex;
- // If we have a pending parameter subsegment, we must leave at least one character for that
- if (parameterNeedsValue != null)
- {
- startIndex--;
- }
+ if (indexOfLiteral == -1)
+ {
+ // If we couldn't find this literal index, this segment cannot match
+ return false;
+ }
- if (startIndex == 0)
+ // If the first subsegment is a literal, it must match at the right-most extent of the request URI.
+ // Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/".
+ // This check is related to the check we do at the very end of this function.
+ if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1))
+ {
+ if (part is RoutePatternLiteralPart literal && ((indexOfLiteral + literal.Content.Length) != requestSegment.Length))
{
return false;
}
-
- int indexOfLiteral;
- if (part.IsLiteral)
- {
- var literal = (RoutePatternLiteralPart)part;
- indexOfLiteral = requestSegment.Slice(0, startIndex).LastIndexOf(
- literal.Content,
- StringComparison.OrdinalIgnoreCase);
- }
- else
+ else if (part is RoutePatternSeparatorPart separator && ((indexOfLiteral + separator.Content.Length) != requestSegment.Length))
{
- var literal = (RoutePatternSeparatorPart)part;
- indexOfLiteral = requestSegment.Slice(0, startIndex).LastIndexOf(
- literal.Content,
- StringComparison.OrdinalIgnoreCase);
+ return false;
}
+ }
+
+ newLastIndex = indexOfLiteral;
+ }
+
+ if ((parameterNeedsValue != null) &&
+ (((lastLiteral != null) && !part.IsParameter) || (indexOfLastSegmentUsed == 0)))
+ {
+ // If we have a pending parameter that needs a value, grab that value
+
+ int parameterStartIndex;
+ int parameterTextLength;
- if (indexOfLiteral == -1)
+ if (lastLiteral == null)
+ {
+ if (indexOfLastSegmentUsed == 0)
{
- // If we couldn't find this literal index, this segment cannot match
- return false;
+ parameterStartIndex = 0;
}
-
- // If the first subsegment is a literal, it must match at the right-most extent of the request URI.
- // Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/".
- // This check is related to the check we do at the very end of this function.
- if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1))
+ else
{
- if (part is RoutePatternLiteralPart literal && ((indexOfLiteral + literal.Content.Length) != requestSegment.Length))
- {
- return false;
- }
- else if (part is RoutePatternSeparatorPart separator && ((indexOfLiteral + separator.Content.Length) != requestSegment.Length))
- {
- return false;
- }
+ parameterStartIndex = newLastIndex;
+ Debug.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above");
}
-
- newLastIndex = indexOfLiteral;
+ parameterTextLength = lastIndex;
}
-
- if ((parameterNeedsValue != null) &&
- (((lastLiteral != null) && !part.IsParameter) || (indexOfLastSegmentUsed == 0)))
+ else
{
- // If we have a pending parameter that needs a value, grab that value
-
- int parameterStartIndex;
- int parameterTextLength;
-
- if (lastLiteral == null)
+ // If we're getting a value for a parameter that is somewhere in the middle of the segment
+ if ((indexOfLastSegmentUsed == 0) && (part.IsParameter))
{
- if (indexOfLastSegmentUsed == 0)
- {
- parameterStartIndex = 0;
- }
- else
- {
- parameterStartIndex = newLastIndex;
- Debug.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above");
- }
+ parameterStartIndex = 0;
parameterTextLength = lastIndex;
}
else
{
- // If we're getting a value for a parameter that is somewhere in the middle of the segment
- if ((indexOfLastSegmentUsed == 0) && (part.IsParameter))
+ if (lastLiteral.IsLiteral)
{
- parameterStartIndex = 0;
- parameterTextLength = lastIndex;
+ var literal = (RoutePatternLiteralPart)lastLiteral;
+ parameterStartIndex = newLastIndex + literal.Content.Length;
}
else
{
- if (lastLiteral.IsLiteral)
- {
- var literal = (RoutePatternLiteralPart)lastLiteral;
- parameterStartIndex = newLastIndex + literal.Content.Length;
- }
- else
- {
- var separator = (RoutePatternSeparatorPart)lastLiteral;
- parameterStartIndex = newLastIndex + separator.Content.Length;
- }
- parameterTextLength = lastIndex - parameterStartIndex;
+ var separator = (RoutePatternSeparatorPart)lastLiteral;
+ parameterStartIndex = newLastIndex + separator.Content.Length;
}
+ parameterTextLength = lastIndex - parameterStartIndex;
}
+ }
- var parameterValueSpan = requestSegment.Slice(parameterStartIndex, parameterTextLength);
-
- if (parameterValueSpan.Length == 0)
- {
- // If we're here that means we have a segment that contains multiple sub-segments.
- // For these segments all parameters must have non-empty values. If the parameter
- // has an empty value it's not a match.
- return false;
+ var parameterValueSpan = requestSegment.Slice(parameterStartIndex, parameterTextLength);
- }
- else
- {
- // If there's a value in the segment for this parameter, use the subsegment value
- outValues.Add(parameterNeedsValue.Name, new string(parameterValueSpan));
- }
+ if (parameterValueSpan.Length == 0)
+ {
+ // If we're here that means we have a segment that contains multiple sub-segments.
+ // For these segments all parameters must have non-empty values. If the parameter
+ // has an empty value it's not a match.
+ return false;
- parameterNeedsValue = null;
- lastLiteral = null;
+ }
+ else
+ {
+ // If there's a value in the segment for this parameter, use the subsegment value
+ outValues.Add(parameterNeedsValue.Name, new string(parameterValueSpan));
}
- lastIndex = newLastIndex;
- indexOfLastSegmentUsed--;
+ parameterNeedsValue = null;
+ lastLiteral = null;
}
- // If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of
- // the string since the parameter will have consumed all the remaining text anyway. If the last subsegment
- // is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching
- // the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire*
- // request URI in order for it to be a match.
- // This check is related to the check we do earlier in this function for LiteralSubsegments.
- if (lastIndex == 0 || routeSegment.Parts[0].IsParameter)
- {
- foreach (var item in outValues)
- {
- values[item.Key] = item.Value;
- }
+ lastIndex = newLastIndex;
+ indexOfLastSegmentUsed--;
+ }
- return true;
+ // If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of
+ // the string since the parameter will have consumed all the remaining text anyway. If the last subsegment
+ // is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching
+ // the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire*
+ // request URI in order for it to be a match.
+ // This check is related to the check we do earlier in this function for LiteralSubsegments.
+ if (lastIndex == 0 || routeSegment.Parts[0].IsParameter)
+ {
+ foreach (var item in outValues)
+ {
+ values[item.Key] = item.Value;
}
- return false;
+ return true;
}
+
+ return false;
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternParameterKind.cs b/src/Http/Routing/src/Patterns/RoutePatternParameterKind.cs
index 6fbb0fa981..8b62f35abb 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternParameterKind.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternParameterKind.cs
@@ -1,27 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Defines the kinds of <see cref="RoutePatternParameterPart"/> instances.
+/// </summary>
+public enum RoutePatternParameterKind
{
/// <summary>
- /// Defines the kinds of <see cref="RoutePatternParameterPart"/> instances.
+ /// The <see cref="RoutePatternParameterKind"/> of a standard parameter
+ /// without optional or catch all behavior.
/// </summary>
- public enum RoutePatternParameterKind
- {
- /// <summary>
- /// The <see cref="RoutePatternParameterKind"/> of a standard parameter
- /// without optional or catch all behavior.
- /// </summary>
- Standard,
+ Standard,
- /// <summary>
- /// The <see cref="RoutePatternParameterKind"/> of an optional parameter.
- /// </summary>
- Optional,
+ /// <summary>
+ /// The <see cref="RoutePatternParameterKind"/> of an optional parameter.
+ /// </summary>
+ Optional,
- /// <summary>
- /// The <see cref="RoutePatternParameterKind"/> of a catch-all parameter.
- /// </summary>
- CatchAll,
- }
+ /// <summary>
+ /// The <see cref="RoutePatternParameterKind"/> of a catch-all parameter.
+ /// </summary>
+ CatchAll,
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternParameterPart.cs b/src/Http/Routing/src/Patterns/RoutePatternParameterPart.cs
index 607b7bcf01..b8d80e3ac1 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternParameterPart.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternParameterPart.cs
@@ -5,113 +5,112 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Represents a parameter part in a route pattern. Instances of <see cref="RoutePatternParameterPart"/>
+/// are immutable.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public sealed class RoutePatternParameterPart : RoutePatternPart
{
+ internal RoutePatternParameterPart(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind,
+ RoutePatternParameterPolicyReference[] parameterPolicies)
+ : this(parameterName, @default, parameterKind, parameterPolicies, encodeSlashes: true)
+ {
+ }
+
+ internal RoutePatternParameterPart(
+ string parameterName,
+ object? @default,
+ RoutePatternParameterKind parameterKind,
+ RoutePatternParameterPolicyReference[] parameterPolicies,
+ bool encodeSlashes)
+ : base(RoutePatternPartKind.Parameter)
+ {
+ // See #475 - this code should have some asserts, but it can't because of the design of RouteParameterParser.
+
+ Name = parameterName;
+ Default = @default;
+ ParameterKind = parameterKind;
+ ParameterPolicies = parameterPolicies;
+ EncodeSlashes = encodeSlashes;
+ }
+
/// <summary>
- /// Represents a parameter part in a route pattern. Instances of <see cref="RoutePatternParameterPart"/>
- /// are immutable.
+ /// Gets the list of parameter policies associated with this parameter.
/// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public sealed class RoutePatternParameterPart : RoutePatternPart
- {
- internal RoutePatternParameterPart(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind,
- RoutePatternParameterPolicyReference[] parameterPolicies)
- : this(parameterName, @default, parameterKind, parameterPolicies, encodeSlashes: true)
- {
- }
+ public IReadOnlyList<RoutePatternParameterPolicyReference> ParameterPolicies { get; }
- internal RoutePatternParameterPart(
- string parameterName,
- object? @default,
- RoutePatternParameterKind parameterKind,
- RoutePatternParameterPolicyReference[] parameterPolicies,
- bool encodeSlashes)
- : base(RoutePatternPartKind.Parameter)
- {
- // See #475 - this code should have some asserts, but it can't because of the design of RouteParameterParser.
+ /// <summary>
+ /// Gets the value indicating if slashes in current parameter's value should be encoded.
+ /// </summary>
+ public bool EncodeSlashes { get; }
- Name = parameterName;
- Default = @default;
- ParameterKind = parameterKind;
- ParameterPolicies = parameterPolicies;
- EncodeSlashes = encodeSlashes;
- }
+ /// <summary>
+ /// Gets the default value of this route parameter. May be null.
+ /// </summary>
+ public object? Default { get; }
- /// <summary>
- /// Gets the list of parameter policies associated with this parameter.
- /// </summary>
- public IReadOnlyList<RoutePatternParameterPolicyReference> ParameterPolicies { get; }
-
- /// <summary>
- /// Gets the value indicating if slashes in current parameter's value should be encoded.
- /// </summary>
- public bool EncodeSlashes { get; }
-
- /// <summary>
- /// Gets the default value of this route parameter. May be null.
- /// </summary>
- public object? Default { get; }
-
- /// <summary>
- /// Returns <c>true</c> if this part is a catch-all parameter.
- /// Otherwise returns <c>false</c>.
- /// </summary>
- public bool IsCatchAll => ParameterKind == RoutePatternParameterKind.CatchAll;
-
- /// <summary>
- /// Returns <c>true</c> if this part is an optional parameter.
- /// Otherwise returns <c>false</c>.
- /// </summary>
- public bool IsOptional => ParameterKind == RoutePatternParameterKind.Optional;
-
- /// <summary>
- /// Gets the <see cref="RoutePatternParameterKind"/> of this parameter.
- /// </summary>
- public RoutePatternParameterKind ParameterKind { get; }
-
- /// <summary>
- /// Gets the parameter name.
- /// </summary>
- public string Name { get; }
-
- internal override string DebuggerToString()
- {
- var builder = new StringBuilder();
- builder.Append('{');
+ /// <summary>
+ /// Returns <c>true</c> if this part is a catch-all parameter.
+ /// Otherwise returns <c>false</c>.
+ /// </summary>
+ public bool IsCatchAll => ParameterKind == RoutePatternParameterKind.CatchAll;
- if (IsCatchAll)
- {
- builder.Append('*');
- if (!EncodeSlashes)
- {
- builder.Append('*');
- }
- }
+ /// <summary>
+ /// Returns <c>true</c> if this part is an optional parameter.
+ /// Otherwise returns <c>false</c>.
+ /// </summary>
+ public bool IsOptional => ParameterKind == RoutePatternParameterKind.Optional;
- builder.Append(Name);
+ /// <summary>
+ /// Gets the <see cref="RoutePatternParameterKind"/> of this parameter.
+ /// </summary>
+ public RoutePatternParameterKind ParameterKind { get; }
- foreach (var constraint in ParameterPolicies)
- {
- builder.Append(':');
- builder.Append(constraint.ParameterPolicy);
- }
+ /// <summary>
+ /// Gets the parameter name.
+ /// </summary>
+ public string Name { get; }
- if (Default != null)
- {
- builder.Append('=');
- builder.Append(Default);
- }
+ internal override string DebuggerToString()
+ {
+ var builder = new StringBuilder();
+ builder.Append('{');
- if (IsOptional)
+ if (IsCatchAll)
+ {
+ builder.Append('*');
+ if (!EncodeSlashes)
{
- builder.Append('?');
+ builder.Append('*');
}
+ }
+
+ builder.Append(Name);
- builder.Append('}');
- return builder.ToString();
+ foreach (var constraint in ParameterPolicies)
+ {
+ builder.Append(':');
+ builder.Append(constraint.ParameterPolicy);
}
+
+ if (Default != null)
+ {
+ builder.Append('=');
+ builder.Append(Default);
+ }
+
+ if (IsOptional)
+ {
+ builder.Append('?');
+ }
+
+ builder.Append('}');
+ return builder.ToString();
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternParameterPolicyReference.cs b/src/Http/Routing/src/Patterns/RoutePatternParameterPolicyReference.cs
index 637907146f..f9ca6b1f5a 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternParameterPolicyReference.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternParameterPolicyReference.cs
@@ -4,38 +4,37 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// The parsed representation of a policy in a <see cref="RoutePattern"/> parameter. Instances
+/// of <see cref="RoutePatternParameterPolicyReference"/> are immutable.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public sealed class RoutePatternParameterPolicyReference
{
- /// <summary>
- /// The parsed representation of a policy in a <see cref="RoutePattern"/> parameter. Instances
- /// of <see cref="RoutePatternParameterPolicyReference"/> are immutable.
- /// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public sealed class RoutePatternParameterPolicyReference
+ internal RoutePatternParameterPolicyReference(string content)
{
- internal RoutePatternParameterPolicyReference(string content)
- {
- Content = content;
- }
+ Content = content;
+ }
- internal RoutePatternParameterPolicyReference(IParameterPolicy parameterPolicy)
- {
- ParameterPolicy = parameterPolicy;
- }
+ internal RoutePatternParameterPolicyReference(IParameterPolicy parameterPolicy)
+ {
+ ParameterPolicy = parameterPolicy;
+ }
- /// <summary>
- /// Gets the constraint text.
- /// </summary>
- public string? Content { get; }
+ /// <summary>
+ /// Gets the constraint text.
+ /// </summary>
+ public string? Content { get; }
- /// <summary>
- /// Gets a pre-existing <see cref="IParameterPolicy"/> that was used to construct this reference.
- /// </summary>
- public IParameterPolicy? ParameterPolicy { get; }
+ /// <summary>
+ /// Gets a pre-existing <see cref="IParameterPolicy"/> that was used to construct this reference.
+ /// </summary>
+ public IParameterPolicy? ParameterPolicy { get; }
- private string? DebuggerToString()
- {
- return Content;
- }
+ private string? DebuggerToString()
+ {
+ return Content;
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternParser.cs b/src/Http/Routing/src/Patterns/RoutePatternParser.cs
index 26e9f69b4a..8b3e4760ec 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternParser.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternParser.cs
@@ -8,568 +8,567 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+internal static class RoutePatternParser
{
- internal static class RoutePatternParser
+ private const char Separator = '/';
+ private const char OpenBrace = '{';
+ private const char CloseBrace = '}';
+ private const char QuestionMark = '?';
+ private const char Asterisk = '*';
+ private const string PeriodString = ".";
+
+ internal static readonly char[] InvalidParameterNameChars = new char[]
{
- private const char Separator = '/';
- private const char OpenBrace = '{';
- private const char CloseBrace = '}';
- private const char QuestionMark = '?';
- private const char Asterisk = '*';
- private const string PeriodString = ".";
-
- internal static readonly char[] InvalidParameterNameChars = new char[]
- {
Separator,
OpenBrace,
CloseBrace,
QuestionMark,
Asterisk
- };
+ };
- public static RoutePattern Parse(string pattern)
+ public static RoutePattern Parse(string pattern)
+ {
+ if (pattern == null)
{
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
-
- var trimmedPattern = TrimPrefix(pattern);
-
- var context = new Context(trimmedPattern);
- var segments = new List<RoutePatternPathSegment>();
-
- while (context.MoveNext())
- {
- var i = context.Index;
+ throw new ArgumentNullException(nameof(pattern));
+ }
- if (context.Current == Separator)
- {
- // If we get here is means that there's a consecutive '/' character.
- // Templates don't start with a '/' and parsing a segment consumes the separator.
- throw new RoutePatternException(pattern, Resources.TemplateRoute_CannotHaveConsecutiveSeparators);
- }
+ var trimmedPattern = TrimPrefix(pattern);
- if (!ParseSegment(context, segments))
- {
- throw new RoutePatternException(pattern, context.Error);
- }
+ var context = new Context(trimmedPattern);
+ var segments = new List<RoutePatternPathSegment>();
- // A successful parse should always result in us being at the end or at a separator.
- Debug.Assert(context.AtEnd() || context.Current == Separator);
+ while (context.MoveNext())
+ {
+ var i = context.Index;
- if (context.Index <= i)
- {
- // This shouldn't happen, but we want to crash if it does.
- var message = "Infinite loop detected in the parser. Please open an issue.";
- throw new InvalidProgramException(message);
- }
+ if (context.Current == Separator)
+ {
+ // If we get here is means that there's a consecutive '/' character.
+ // Templates don't start with a '/' and parsing a segment consumes the separator.
+ throw new RoutePatternException(pattern, Resources.TemplateRoute_CannotHaveConsecutiveSeparators);
}
- if (IsAllValid(context, segments))
+ if (!ParseSegment(context, segments))
{
- return RoutePatternFactory.Pattern(pattern, segments);
+ throw new RoutePatternException(pattern, context.Error);
}
- else
+
+ // A successful parse should always result in us being at the end or at a separator.
+ Debug.Assert(context.AtEnd() || context.Current == Separator);
+
+ if (context.Index <= i)
{
- throw new RoutePatternException(pattern, context.Error);
+ // This shouldn't happen, but we want to crash if it does.
+ var message = "Infinite loop detected in the parser. Please open an issue.";
+ throw new InvalidProgramException(message);
}
}
- private static bool ParseSegment(Context context, List<RoutePatternPathSegment> segments)
+ if (IsAllValid(context, segments))
+ {
+ return RoutePatternFactory.Pattern(pattern, segments);
+ }
+ else
{
- Debug.Assert(context != null);
- Debug.Assert(segments != null);
+ throw new RoutePatternException(pattern, context.Error);
+ }
+ }
+
+ private static bool ParseSegment(Context context, List<RoutePatternPathSegment> segments)
+ {
+ Debug.Assert(context != null);
+ Debug.Assert(segments != null);
- var parts = new List<RoutePatternPart>();
+ var parts = new List<RoutePatternPart>();
+
+ while (true)
+ {
+ var i = context.Index;
- while (true)
+ if (context.Current == OpenBrace)
{
- var i = context.Index;
+ if (!context.MoveNext())
+ {
+ // This is a dangling open-brace, which is not allowed
+ context.Error = Resources.TemplateRoute_MismatchedParameter;
+ return false;
+ }
if (context.Current == OpenBrace)
{
- if (!context.MoveNext())
+ // This is an 'escaped' brace in a literal, like "{{foo"
+ context.Back();
+ if (!ParseLiteral(context, parts))
{
- // This is a dangling open-brace, which is not allowed
- context.Error = Resources.TemplateRoute_MismatchedParameter;
return false;
}
-
- if (context.Current == OpenBrace)
- {
- // This is an 'escaped' brace in a literal, like "{{foo"
- context.Back();
- if (!ParseLiteral(context, parts))
- {
- return false;
- }
- }
- else
- {
- // This is a parameter
- context.Back();
- if (!ParseParameter(context, parts))
- {
- return false;
- }
- }
}
else
{
- if (!ParseLiteral(context, parts))
+ // This is a parameter
+ context.Back();
+ if (!ParseParameter(context, parts))
{
return false;
}
}
-
- if (context.Current == Separator || context.AtEnd())
- {
- // We've reached the end of the segment
- break;
- }
-
- if (context.Index <= i)
+ }
+ else
+ {
+ if (!ParseLiteral(context, parts))
{
- // This shouldn't happen, but we want to crash if it does.
- var message = "Infinite loop detected in the parser. Please open an issue.";
- throw new InvalidProgramException(message);
+ return false;
}
}
- if (IsSegmentValid(context, parts))
+ if (context.Current == Separator || context.AtEnd())
{
- segments.Add(new RoutePatternPathSegment(parts));
- return true;
+ // We've reached the end of the segment
+ break;
}
- else
+
+ if (context.Index <= i)
{
- return false;
+ // This shouldn't happen, but we want to crash if it does.
+ var message = "Infinite loop detected in the parser. Please open an issue.";
+ throw new InvalidProgramException(message);
}
}
- private static bool ParseParameter(Context context, List<RoutePatternPart> parts)
+ if (IsSegmentValid(context, parts))
{
- Debug.Assert(context.Current == OpenBrace);
- context.Mark();
+ segments.Add(new RoutePatternPathSegment(parts));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private static bool ParseParameter(Context context, List<RoutePatternPart> parts)
+ {
+ Debug.Assert(context.Current == OpenBrace);
+ context.Mark();
- context.MoveNext();
+ context.MoveNext();
- while (true)
+ while (true)
+ {
+ if (context.Current == OpenBrace)
{
- if (context.Current == OpenBrace)
+ // This is an open brace inside of a parameter, it has to be escaped
+ if (context.MoveNext())
{
- // This is an open brace inside of a parameter, it has to be escaped
- if (context.MoveNext())
- {
- if (context.Current != OpenBrace)
- {
- // If we see something like "{p1:regex(^\d{3", we will come here.
- context.Error = Resources.TemplateRoute_UnescapedBrace;
- return false;
- }
- }
- else
+ if (context.Current != OpenBrace)
{
- // This is a dangling open-brace, which is not allowed
- // Example: "{p1:regex(^\d{"
- context.Error = Resources.TemplateRoute_MismatchedParameter;
+ // If we see something like "{p1:regex(^\d{3", we will come here.
+ context.Error = Resources.TemplateRoute_UnescapedBrace;
return false;
}
}
- else if (context.Current == CloseBrace)
- {
- // When we encounter Closed brace here, it either means end of the parameter or it is a closed
- // brace in the parameter, in that case it needs to be escaped.
- // Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
- if (!context.MoveNext())
- {
- // This is the end of the string -and we have a valid parameter
- break;
- }
-
- if (context.Current == CloseBrace)
- {
- // This is an 'escaped' brace in a parameter name
- }
- else
- {
- // This is the end of the parameter
- break;
- }
- }
-
- if (!context.MoveNext())
+ else
{
// This is a dangling open-brace, which is not allowed
+ // Example: "{p1:regex(^\d{"
context.Error = Resources.TemplateRoute_MismatchedParameter;
return false;
}
}
+ else if (context.Current == CloseBrace)
+ {
+ // When we encounter Closed brace here, it either means end of the parameter or it is a closed
+ // brace in the parameter, in that case it needs to be escaped.
+ // Example: {p1:regex(([}}])\w+}. First pair is escaped one and last marks end of the parameter
+ if (!context.MoveNext())
+ {
+ // This is the end of the string -and we have a valid parameter
+ break;
+ }
+
+ if (context.Current == CloseBrace)
+ {
+ // This is an 'escaped' brace in a parameter name
+ }
+ else
+ {
+ // This is the end of the parameter
+ break;
+ }
+ }
- var text = context.Capture();
- if (text == "{}")
+ if (!context.MoveNext())
{
- context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty);
+ // This is a dangling open-brace, which is not allowed
+ context.Error = Resources.TemplateRoute_MismatchedParameter;
return false;
}
+ }
- var inside = text.Substring(1, text.Length - 2);
- var decoded = inside.Replace("}}", "}").Replace("{{", "{");
+ var text = context.Capture();
+ if (text == "{}")
+ {
+ context.Error = Resources.FormatTemplateRoute_InvalidParameterName(string.Empty);
+ return false;
+ }
- // At this point, we need to parse the raw name for inline constraint,
- // default values and optional parameters.
- var templatePart = RouteParameterParser.ParseRouteParameter(decoded);
+ var inside = text.Substring(1, text.Length - 2);
+ var decoded = inside.Replace("}}", "}").Replace("{{", "{");
- // See #475 - this is here because InlineRouteParameterParser can't return errors
- if (decoded.StartsWith("*", StringComparison.Ordinal) && decoded.EndsWith("?", StringComparison.Ordinal))
- {
- context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
- return false;
- }
+ // At this point, we need to parse the raw name for inline constraint,
+ // default values and optional parameters.
+ var templatePart = RouteParameterParser.ParseRouteParameter(decoded);
- if (templatePart.IsOptional && templatePart.Default != null)
- {
- // Cannot be optional and have a default value.
- // The only way to declare an optional parameter is to have a ? at the end,
- // hence we cannot have both default value and optional parameter within the template.
- // A workaround is to add it as a separate entry in the defaults argument.
- context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
- return false;
- }
+ // See #475 - this is here because InlineRouteParameterParser can't return errors
+ if (decoded.StartsWith("*", StringComparison.Ordinal) && decoded.EndsWith("?", StringComparison.Ordinal))
+ {
+ context.Error = Resources.TemplateRoute_CatchAllCannotBeOptional;
+ return false;
+ }
- var parameterName = templatePart.Name;
- if (IsValidParameterName(context, parameterName))
- {
- parts.Add(templatePart);
- return true;
- }
- else
- {
- return false;
- }
+ if (templatePart.IsOptional && templatePart.Default != null)
+ {
+ // Cannot be optional and have a default value.
+ // The only way to declare an optional parameter is to have a ? at the end,
+ // hence we cannot have both default value and optional parameter within the template.
+ // A workaround is to add it as a separate entry in the defaults argument.
+ context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
+ return false;
}
- private static bool ParseLiteral(Context context, List<RoutePatternPart> parts)
+ var parameterName = templatePart.Name;
+ if (IsValidParameterName(context, parameterName))
{
- context.Mark();
+ parts.Add(templatePart);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private static bool ParseLiteral(Context context, List<RoutePatternPart> parts)
+ {
+ context.Mark();
- while (true)
+ while (true)
+ {
+ if (context.Current == Separator)
{
- if (context.Current == Separator)
+ // End of the segment
+ break;
+ }
+ else if (context.Current == OpenBrace)
+ {
+ if (!context.MoveNext())
{
- // End of the segment
- break;
+ // This is a dangling open-brace, which is not allowed
+ context.Error = Resources.TemplateRoute_MismatchedParameter;
+ return false;
}
- else if (context.Current == OpenBrace)
- {
- if (!context.MoveNext())
- {
- // This is a dangling open-brace, which is not allowed
- context.Error = Resources.TemplateRoute_MismatchedParameter;
- return false;
- }
- if (context.Current == OpenBrace)
- {
- // This is an 'escaped' brace in a literal, like "{{foo" - keep going.
- }
- else
- {
- // We've just seen the start of a parameter, so back up.
- context.Back();
- break;
- }
- }
- else if (context.Current == CloseBrace)
+ if (context.Current == OpenBrace)
{
- if (!context.MoveNext())
- {
- // This is a dangling close-brace, which is not allowed
- context.Error = Resources.TemplateRoute_MismatchedParameter;
- return false;
- }
-
- if (context.Current == CloseBrace)
- {
- // This is an 'escaped' brace in a literal, like "{{foo" - keep going.
- }
- else
- {
- // This is an unbalanced close-brace, which is not allowed
- context.Error = Resources.TemplateRoute_MismatchedParameter;
- return false;
- }
+ // This is an 'escaped' brace in a literal, like "{{foo" - keep going.
}
-
- if (!context.MoveNext())
+ else
{
+ // We've just seen the start of a parameter, so back up.
+ context.Back();
break;
}
}
-
- var encoded = context.Capture();
- var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
- if (IsValidLiteral(context, decoded))
+ else if (context.Current == CloseBrace)
{
- parts.Add(RoutePatternFactory.LiteralPart(decoded));
- return true;
+ if (!context.MoveNext())
+ {
+ // This is a dangling close-brace, which is not allowed
+ context.Error = Resources.TemplateRoute_MismatchedParameter;
+ return false;
+ }
+
+ if (context.Current == CloseBrace)
+ {
+ // This is an 'escaped' brace in a literal, like "{{foo" - keep going.
+ }
+ else
+ {
+ // This is an unbalanced close-brace, which is not allowed
+ context.Error = Resources.TemplateRoute_MismatchedParameter;
+ return false;
+ }
}
- else
+
+ if (!context.MoveNext())
{
- return false;
+ break;
}
}
- private static bool IsAllValid(Context context, List<RoutePatternPathSegment> segments)
+ var encoded = context.Capture();
+ var decoded = encoded.Replace("}}", "}").Replace("{{", "{");
+ if (IsValidLiteral(context, decoded))
{
- // A catch-all parameter must be the last part of the last segment
- for (var i = 0; i < segments.Count; i++)
- {
- var segment = segments[i];
- for (var j = 0; j < segment.Parts.Count; j++)
- {
- var part = segment.Parts[j];
- if (part is RoutePatternParameterPart parameter
- && parameter.IsCatchAll &&
- (i != segments.Count - 1 || j != segment.Parts.Count - 1))
- {
- context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
- return false;
- }
- }
- }
-
+ parts.Add(RoutePatternFactory.LiteralPart(decoded));
return true;
}
+ else
+ {
+ return false;
+ }
+ }
- private static bool IsSegmentValid(Context context, List<RoutePatternPart> parts)
+ private static bool IsAllValid(Context context, List<RoutePatternPathSegment> segments)
+ {
+ // A catch-all parameter must be the last part of the last segment
+ for (var i = 0; i < segments.Count; i++)
{
- // If a segment has multiple parts, then it can't contain a catch all.
- for (var i = 0; i < parts.Count; i++)
+ var segment = segments[i];
+ for (var j = 0; j < segment.Parts.Count; j++)
{
- var part = parts[i];
- if (part is RoutePatternParameterPart parameter && parameter.IsCatchAll && parts.Count > 1)
+ var part = segment.Parts[j];
+ if (part is RoutePatternParameterPart parameter
+ && parameter.IsCatchAll &&
+ (i != segments.Count - 1 || j != segment.Parts.Count - 1))
{
- context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment;
+ context.Error = Resources.TemplateRoute_CatchAllMustBeLast;
return false;
}
}
+ }
- // if a segment has multiple parts, then only the last one parameter can be optional
- // if it is following a optional separator.
- for (var i = 0; i < parts.Count; i++)
+ return true;
+ }
+
+ private static bool IsSegmentValid(Context context, List<RoutePatternPart> parts)
+ {
+ // If a segment has multiple parts, then it can't contain a catch all.
+ for (var i = 0; i < parts.Count; i++)
+ {
+ var part = parts[i];
+ if (part is RoutePatternParameterPart parameter && parameter.IsCatchAll && parts.Count > 1)
{
- var part = parts[i];
+ context.Error = Resources.TemplateRoute_CannotHaveCatchAllInMultiSegment;
+ return false;
+ }
+ }
+
+ // if a segment has multiple parts, then only the last one parameter can be optional
+ // if it is following a optional separator.
+ for (var i = 0; i < parts.Count; i++)
+ {
+ var part = parts[i];
- if (part is RoutePatternParameterPart parameter && parameter.IsOptional && parts.Count > 1)
+ if (part is RoutePatternParameterPart parameter && parameter.IsOptional && parts.Count > 1)
+ {
+ // This optional parameter is the last part in the segment
+ if (i == parts.Count - 1)
{
- // This optional parameter is the last part in the segment
- if (i == parts.Count - 1)
+ var previousPart = parts[i - 1];
+
+ if (!previousPart.IsLiteral && !previousPart.IsSeparator)
{
- var previousPart = parts[i - 1];
-
- if (!previousPart.IsLiteral && !previousPart.IsSeparator)
- {
- // The optional parameter is preceded by something that is not a literal or separator
- // Example of error message:
- // "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded
- // by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter.
- context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(
- RoutePatternPathSegment.DebuggerToString(parts),
- parameter.Name,
- parts[i - 1].DebuggerToString());
-
- return false;
- }
- else if (previousPart is RoutePatternLiteralPart literal && literal.Content != PeriodString)
- {
- // The optional parameter is preceded by a literal other than period.
- // Example of error message:
- // "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded
- // by an invalid segment '-'. Only a period (.) can precede an optional parameter.
- context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(
- RoutePatternPathSegment.DebuggerToString(parts),
- parameter.Name,
- parts[i - 1].DebuggerToString());
-
- return false;
- }
-
- parts[i - 1] = RoutePatternFactory.SeparatorPart(((RoutePatternLiteralPart)previousPart).Content);
+ // The optional parameter is preceded by something that is not a literal or separator
+ // Example of error message:
+ // "In the segment '{RouteValue}{param?}', the optional parameter 'param' is preceded
+ // by an invalid segment '{RouteValue}'. Only a period (.) can precede an optional parameter.
+ context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(
+ RoutePatternPathSegment.DebuggerToString(parts),
+ parameter.Name,
+ parts[i - 1].DebuggerToString());
+
+ return false;
}
- else
+ else if (previousPart is RoutePatternLiteralPart literal && literal.Content != PeriodString)
{
- // This optional parameter is not the last one in the segment
- // Example:
- // An optional parameter must be at the end of the segment. In the segment '{RouteValue?})',
- // optional parameter 'RouteValue' is followed by ')'
- context.Error = Resources.FormatTemplateRoute_OptionalParameterHasTobeTheLast(
+ // The optional parameter is preceded by a literal other than period.
+ // Example of error message:
+ // "In the segment '{RouteValue}-{param?}', the optional parameter 'param' is preceded
+ // by an invalid segment '-'. Only a period (.) can precede an optional parameter.
+ context.Error = Resources.FormatTemplateRoute_OptionalParameterCanbBePrecededByPeriod(
RoutePatternPathSegment.DebuggerToString(parts),
parameter.Name,
- parts[i + 1].DebuggerToString());
+ parts[i - 1].DebuggerToString());
return false;
}
- }
- }
- // A segment cannot contain two consecutive parameters
- var isLastSegmentParameter = false;
- for (var i = 0; i < parts.Count; i++)
- {
- var part = parts[i];
- if (part.IsParameter && isLastSegmentParameter)
+ parts[i - 1] = RoutePatternFactory.SeparatorPart(((RoutePatternLiteralPart)previousPart).Content);
+ }
+ else
{
- context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
+ // This optional parameter is not the last one in the segment
+ // Example:
+ // An optional parameter must be at the end of the segment. In the segment '{RouteValue?})',
+ // optional parameter 'RouteValue' is followed by ')'
+ context.Error = Resources.FormatTemplateRoute_OptionalParameterHasTobeTheLast(
+ RoutePatternPathSegment.DebuggerToString(parts),
+ parameter.Name,
+ parts[i + 1].DebuggerToString());
+
return false;
}
-
- isLastSegmentParameter = part.IsParameter;
}
-
- return true;
}
- private static bool IsValidParameterName(Context context, string parameterName)
+ // A segment cannot contain two consecutive parameters
+ var isLastSegmentParameter = false;
+ for (var i = 0; i < parts.Count; i++)
{
- if (parameterName.Length == 0 || parameterName.IndexOfAny(InvalidParameterNameChars) >= 0)
+ var part = parts[i];
+ if (part.IsParameter && isLastSegmentParameter)
{
- context.Error = Resources.FormatTemplateRoute_InvalidParameterName(parameterName);
+ context.Error = Resources.TemplateRoute_CannotHaveConsecutiveParameters;
return false;
}
- if (!context.ParameterNames.Add(parameterName))
- {
- context.Error = Resources.FormatTemplateRoute_RepeatedParameter(parameterName);
- return false;
- }
+ isLastSegmentParameter = part.IsParameter;
+ }
- return true;
+ return true;
+ }
+
+ private static bool IsValidParameterName(Context context, string parameterName)
+ {
+ if (parameterName.Length == 0 || parameterName.IndexOfAny(InvalidParameterNameChars) >= 0)
+ {
+ context.Error = Resources.FormatTemplateRoute_InvalidParameterName(parameterName);
+ return false;
}
- private static bool IsValidLiteral(Context context, string literal)
+ if (!context.ParameterNames.Add(parameterName))
{
- Debug.Assert(context != null);
- Debug.Assert(literal != null);
+ context.Error = Resources.FormatTemplateRoute_RepeatedParameter(parameterName);
+ return false;
+ }
- if (literal.IndexOf(QuestionMark) != -1)
- {
- context.Error = Resources.FormatTemplateRoute_InvalidLiteral(literal);
- return false;
- }
+ return true;
+ }
- return true;
+ private static bool IsValidLiteral(Context context, string literal)
+ {
+ Debug.Assert(context != null);
+ Debug.Assert(literal != null);
+
+ if (literal.IndexOf(QuestionMark) != -1)
+ {
+ context.Error = Resources.FormatTemplateRoute_InvalidLiteral(literal);
+ return false;
}
- private static string TrimPrefix(string routePattern)
+ return true;
+ }
+
+ private static string TrimPrefix(string routePattern)
+ {
+ if (routePattern.StartsWith("~/", StringComparison.Ordinal))
{
- if (routePattern.StartsWith("~/", StringComparison.Ordinal))
- {
- return routePattern.Substring(2);
- }
- else if (routePattern.StartsWith("/", StringComparison.Ordinal))
- {
- return routePattern.Substring(1);
- }
- else if (routePattern.StartsWith("~", StringComparison.Ordinal))
- {
- throw new RoutePatternException(routePattern, Resources.TemplateRoute_InvalidRouteTemplate);
- }
- return routePattern;
+ return routePattern.Substring(2);
+ }
+ else if (routePattern.StartsWith("/", StringComparison.Ordinal))
+ {
+ return routePattern.Substring(1);
+ }
+ else if (routePattern.StartsWith("~", StringComparison.Ordinal))
+ {
+ throw new RoutePatternException(routePattern, Resources.TemplateRoute_InvalidRouteTemplate);
}
+ return routePattern;
+ }
+
+ [DebuggerDisplay("{DebuggerToString()}")]
+ private class Context
+ {
+ private readonly string _template;
+ private int _index;
+ private int? _mark;
+
+ private readonly HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- [DebuggerDisplay("{DebuggerToString()}")]
- private class Context
+ public Context(string template)
{
- private readonly string _template;
- private int _index;
- private int? _mark;
+ Debug.Assert(template != null);
+ _template = template;
- private readonly HashSet<string> _parameterNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ _index = -1;
+ }
- public Context(string template)
- {
- Debug.Assert(template != null);
- _template = template;
+ public char Current
+ {
+ get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
+ }
- _index = -1;
- }
+ public int Index => _index;
- public char Current
- {
- get { return (_index < _template.Length && _index >= 0) ? _template[_index] : (char)0; }
- }
+ public string Error
+ {
+ get;
+ set;
+ }
- public int Index => _index;
+ public HashSet<string> ParameterNames
+ {
+ get { return _parameterNames; }
+ }
- public string Error
- {
- get;
- set;
- }
+ public bool Back()
+ {
+ return --_index >= 0;
+ }
- public HashSet<string> ParameterNames
- {
- get { return _parameterNames; }
- }
+ public bool AtEnd()
+ {
+ return _index >= _template.Length;
+ }
- public bool Back()
- {
- return --_index >= 0;
- }
+ public bool MoveNext()
+ {
+ return ++_index < _template.Length;
+ }
+
+ public void Mark()
+ {
+ Debug.Assert(_index >= 0);
+
+ // Index is always the index of the character *past* Current - we want to 'mark' Current.
+ _mark = _index;
+ }
- public bool AtEnd()
+ public string Capture()
+ {
+ if (_mark.HasValue)
{
- return _index >= _template.Length;
+ var value = _template.Substring(_mark.Value, _index - _mark.Value);
+ _mark = null;
+ return value;
}
-
- public bool MoveNext()
+ else
{
- return ++_index < _template.Length;
+ return null;
}
+ }
- public void Mark()
+ private string DebuggerToString()
+ {
+ if (_index == -1)
{
- Debug.Assert(_index >= 0);
-
- // Index is always the index of the character *past* Current - we want to 'mark' Current.
- _mark = _index;
+ return _template;
}
-
- public string Capture()
+ else if (_mark.HasValue)
{
- if (_mark.HasValue)
- {
- var value = _template.Substring(_mark.Value, _index - _mark.Value);
- _mark = null;
- return value;
- }
- else
- {
- return null;
- }
+ return _template.Substring(0, _mark.Value) +
+ "|" +
+ _template.Substring(_mark.Value, _index - _mark.Value) +
+ "|" +
+ _template.Substring(_index);
}
-
- private string DebuggerToString()
+ else
{
- if (_index == -1)
- {
- return _template;
- }
- else if (_mark.HasValue)
- {
- return _template.Substring(0, _mark.Value) +
- "|" +
- _template.Substring(_mark.Value, _index - _mark.Value) +
- "|" +
- _template.Substring(_index);
- }
- else
- {
- return string.Concat(_template.Substring(0, _index), "|", _template.Substring(_index));
- }
+ return string.Concat(_template.Substring(0, _index), "|", _template.Substring(_index));
}
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternPart.cs b/src/Http/Routing/src/Patterns/RoutePatternPart.cs
index 016d145545..09308938f2 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternPart.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternPart.cs
@@ -1,42 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Represents a part of a route pattern.
+/// </summary>
+public abstract class RoutePatternPart
{
- /// <summary>
- /// Represents a part of a route pattern.
- /// </summary>
- public abstract class RoutePatternPart
+ // This class is **not** an extensibility point - every part of the routing system
+ // needs to be aware of what kind of parts we support.
+ //
+ // It is abstract so we can add semantics later inside the library.
+ private protected RoutePatternPart(RoutePatternPartKind partKind)
{
- // This class is **not** an extensibility point - every part of the routing system
- // needs to be aware of what kind of parts we support.
- //
- // It is abstract so we can add semantics later inside the library.
- private protected RoutePatternPart(RoutePatternPartKind partKind)
- {
- PartKind = partKind;
- }
+ PartKind = partKind;
+ }
- /// <summary>
- /// Gets the <see cref="RoutePatternPartKind"/> of this part.
- /// </summary>
- public RoutePatternPartKind PartKind { get; }
+ /// <summary>
+ /// Gets the <see cref="RoutePatternPartKind"/> of this part.
+ /// </summary>
+ public RoutePatternPartKind PartKind { get; }
- /// <summary>
- /// Returns <c>true</c> if this part is literal text. Otherwise returns <c>false</c>.
- /// </summary>
- public bool IsLiteral => PartKind == RoutePatternPartKind.Literal;
+ /// <summary>
+ /// Returns <c>true</c> if this part is literal text. Otherwise returns <c>false</c>.
+ /// </summary>
+ public bool IsLiteral => PartKind == RoutePatternPartKind.Literal;
- /// <summary>
- /// Returns <c>true</c> if this part is a route parameter. Otherwise returns <c>false</c>.
- /// </summary>
- public bool IsParameter => PartKind == RoutePatternPartKind.Parameter;
+ /// <summary>
+ /// Returns <c>true</c> if this part is a route parameter. Otherwise returns <c>false</c>.
+ /// </summary>
+ public bool IsParameter => PartKind == RoutePatternPartKind.Parameter;
- /// <summary>
- /// Returns <c>true</c> if this part is an optional separator. Otherwise returns <c>false</c>.
- /// </summary>
- public bool IsSeparator => PartKind == RoutePatternPartKind.Separator;
+ /// <summary>
+ /// Returns <c>true</c> if this part is an optional separator. Otherwise returns <c>false</c>.
+ /// </summary>
+ public bool IsSeparator => PartKind == RoutePatternPartKind.Separator;
- internal abstract string DebuggerToString();
- }
+ internal abstract string DebuggerToString();
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternPartKind.cs b/src/Http/Routing/src/Patterns/RoutePatternPartKind.cs
index 98904cf84b..03d58d60d5 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternPartKind.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternPartKind.cs
@@ -1,26 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Defines the kinds of <see cref="RoutePatternPart"/> instances.
+/// </summary>
+public enum RoutePatternPartKind
{
/// <summary>
- /// Defines the kinds of <see cref="RoutePatternPart"/> instances.
+ /// The <see cref="RoutePatternPartKind"/> of a <see cref="RoutePatternLiteralPart"/>.
/// </summary>
- public enum RoutePatternPartKind
- {
- /// <summary>
- /// The <see cref="RoutePatternPartKind"/> of a <see cref="RoutePatternLiteralPart"/>.
- /// </summary>
- Literal,
+ Literal,
- /// <summary>
- /// The <see cref="RoutePatternPartKind"/> of a <see cref="RoutePatternParameterPart"/>.
- /// </summary>
- Parameter,
+ /// <summary>
+ /// The <see cref="RoutePatternPartKind"/> of a <see cref="RoutePatternParameterPart"/>.
+ /// </summary>
+ Parameter,
- /// <summary>
- /// The <see cref="RoutePatternPartKind"/> of a <see cref="RoutePatternSeparatorPart"/>.
- /// </summary>
- Separator,
- }
+ /// <summary>
+ /// The <see cref="RoutePatternPartKind"/> of a <see cref="RoutePatternSeparatorPart"/>.
+ /// </summary>
+ Separator,
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternPathSegment.cs b/src/Http/Routing/src/Patterns/RoutePatternPathSegment.cs
index d7d1223f0a..5e04dd8f6f 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternPathSegment.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternPathSegment.cs
@@ -5,45 +5,44 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Represents a path segment in a route pattern. Instances of <see cref="RoutePatternPathSegment"/> are
+/// immutable.
+/// </summary>
+/// <remarks>
+/// Route patterns are made up of URL path segments, delimited by <c>/</c>. A
+/// <see cref="RoutePatternPathSegment"/> contains a group of
+/// <see cref="RoutePatternPart"/> that represent the structure of a segment
+/// in a route pattern.
+/// </remarks>
+[DebuggerDisplay("{DebuggerToString()}")]
+public sealed class RoutePatternPathSegment
{
- /// <summary>
- /// Represents a path segment in a route pattern. Instances of <see cref="RoutePatternPathSegment"/> are
- /// immutable.
- /// </summary>
- /// <remarks>
- /// Route patterns are made up of URL path segments, delimited by <c>/</c>. A
- /// <see cref="RoutePatternPathSegment"/> contains a group of
- /// <see cref="RoutePatternPart"/> that represent the structure of a segment
- /// in a route pattern.
- /// </remarks>
- [DebuggerDisplay("{DebuggerToString()}")]
- public sealed class RoutePatternPathSegment
+ internal RoutePatternPathSegment(IReadOnlyList<RoutePatternPart> parts)
{
- internal RoutePatternPathSegment(IReadOnlyList<RoutePatternPart> parts)
- {
- Parts = parts;
- }
+ Parts = parts;
+ }
- /// <summary>
- /// Returns <c>true</c> if the segment contains a single part;
- /// otherwise returns <c>false</c>.
- /// </summary>
- public bool IsSimple => Parts.Count == 1;
+ /// <summary>
+ /// Returns <c>true</c> if the segment contains a single part;
+ /// otherwise returns <c>false</c>.
+ /// </summary>
+ public bool IsSimple => Parts.Count == 1;
- /// <summary>
- /// Gets the list of parts in this segment.
- /// </summary>
- public IReadOnlyList<RoutePatternPart> Parts { get; }
+ /// <summary>
+ /// Gets the list of parts in this segment.
+ /// </summary>
+ public IReadOnlyList<RoutePatternPart> Parts { get; }
- internal string DebuggerToString()
- {
- return DebuggerToString(Parts);
- }
+ internal string DebuggerToString()
+ {
+ return DebuggerToString(Parts);
+ }
- internal static string DebuggerToString(IReadOnlyList<RoutePatternPart> parts)
- {
- return string.Join(string.Empty, parts.Select(p => p.DebuggerToString()));
- }
+ internal static string DebuggerToString(IReadOnlyList<RoutePatternPart> parts)
+ {
+ return string.Join(string.Empty, parts.Select(p => p.DebuggerToString()));
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternSeparatorPart.cs b/src/Http/Routing/src/Patterns/RoutePatternSeparatorPart.cs
index 9dd59537c7..47f124f799 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternSeparatorPart.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternSeparatorPart.cs
@@ -3,48 +3,47 @@
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// Represents an optional separator part of a route pattern. Instances of <see cref="RoutePatternSeparatorPart"/>
+/// are immutable.
+/// </summary>
+/// <remarks>
+/// <para>
+/// An optional separator is a literal text delimiter that appears between
+/// two parameter parts in the last segment of a route pattern. The only separator
+/// that is recognized is <c>.</c>.
+/// </para>
+/// <para>
+/// <example>
+/// In the route pattern <c>/{controller}/{action}/{id?}.{extension?}</c>
+/// the <c>.</c> character is an optional separator.
+/// </example>
+/// </para>
+/// <para>
+/// An optional separator character does not need to present in the URL path
+/// of a request for the route pattern to match.
+/// </para>
+/// </remarks>
+[DebuggerDisplay("{DebuggerToString()}")]
+public sealed class RoutePatternSeparatorPart : RoutePatternPart
{
- /// <summary>
- /// Represents an optional separator part of a route pattern. Instances of <see cref="RoutePatternSeparatorPart"/>
- /// are immutable.
- /// </summary>
- /// <remarks>
- /// <para>
- /// An optional separator is a literal text delimiter that appears between
- /// two parameter parts in the last segment of a route pattern. The only separator
- /// that is recognized is <c>.</c>.
- /// </para>
- /// <para>
- /// <example>
- /// In the route pattern <c>/{controller}/{action}/{id?}.{extension?}</c>
- /// the <c>.</c> character is an optional separator.
- /// </example>
- /// </para>
- /// <para>
- /// An optional separator character does not need to present in the URL path
- /// of a request for the route pattern to match.
- /// </para>
- /// </remarks>
- [DebuggerDisplay("{DebuggerToString()}")]
- public sealed class RoutePatternSeparatorPart : RoutePatternPart
+ internal RoutePatternSeparatorPart(string content)
+ : base(RoutePatternPartKind.Separator)
{
- internal RoutePatternSeparatorPart(string content)
- : base(RoutePatternPartKind.Separator)
- {
- Debug.Assert(!string.IsNullOrEmpty(content));
+ Debug.Assert(!string.IsNullOrEmpty(content));
- Content = content;
- }
+ Content = content;
+ }
- /// <summary>
- /// Gets the text content of the part.
- /// </summary>
- public string Content { get; }
+ /// <summary>
+ /// Gets the text content of the part.
+ /// </summary>
+ public string Content { get; }
- internal override string DebuggerToString()
- {
- return Content;
- }
+ internal override string DebuggerToString()
+ {
+ return Content;
}
}
diff --git a/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs b/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs
index a7eaf7b908..bd642fa6c0 100644
--- a/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs
+++ b/src/Http/Routing/src/Patterns/RoutePatternTransformer.cs
@@ -1,35 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+/// <summary>
+/// A singleton service that provides transformations on <see cref="RoutePattern"/>.
+/// </summary>
+public abstract class RoutePatternTransformer
{
/// <summary>
- /// A singleton service that provides transformations on <see cref="RoutePattern"/>.
+ /// Attempts to substitute the provided <paramref name="requiredValues"/> into the provided
+ /// <paramref name="original"/>.
/// </summary>
- public abstract class RoutePatternTransformer
- {
- /// <summary>
- /// Attempts to substitute the provided <paramref name="requiredValues"/> into the provided
- /// <paramref name="original"/>.
- /// </summary>
- /// <param name="original">The original <see cref="RoutePattern"/>.</param>
- /// <param name="requiredValues">The required values to substitute.</param>
- /// <returns>
- /// A new <see cref="RoutePattern"/> if substitution succeeds, otherwise <c>null</c>.
- /// </returns>
- /// <remarks>
- /// <para>
- /// Substituting required values into a route pattern is intended for us with a general-purpose
- /// parameterize route specification that can match many logical endpoints. Calling
- /// <see cref="SubstituteRequiredValues(RoutePattern, object)"/> can produce a derived route pattern
- /// for each set of route values that corresponds to an endpoint.
- /// </para>
- /// <para>
- /// The substitution process considers default values and <see cref="IRouteConstraint"/> implementations
- /// when examining a required value. <see cref="SubstituteRequiredValues(RoutePattern, object)"/> will
- /// return <c>null</c> if any required value cannot be substituted.
- /// </para>
- /// </remarks>
- public abstract RoutePattern? SubstituteRequiredValues(RoutePattern original, object requiredValues);
- }
+ /// <param name="original">The original <see cref="RoutePattern"/>.</param>
+ /// <param name="requiredValues">The required values to substitute.</param>
+ /// <returns>
+ /// A new <see cref="RoutePattern"/> if substitution succeeds, otherwise <c>null</c>.
+ /// </returns>
+ /// <remarks>
+ /// <para>
+ /// Substituting required values into a route pattern is intended for us with a general-purpose
+ /// parameterize route specification that can match many logical endpoints. Calling
+ /// <see cref="SubstituteRequiredValues(RoutePattern, object)"/> can produce a derived route pattern
+ /// for each set of route values that corresponds to an endpoint.
+ /// </para>
+ /// <para>
+ /// The substitution process considers default values and <see cref="IRouteConstraint"/> implementations
+ /// when examining a required value. <see cref="SubstituteRequiredValues(RoutePattern, object)"/> will
+ /// return <c>null</c> if any required value cannot be substituted.
+ /// </para>
+ /// </remarks>
+ public abstract RoutePattern? SubstituteRequiredValues(RoutePattern original, object requiredValues);
}
diff --git a/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs b/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs
index 2ae7e9515b..9a17f64d9e 100644
--- a/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs
+++ b/src/Http/Routing/src/RequestDelegateRouteBuilderExtensions.cs
@@ -8,291 +8,290 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Provides extension methods for adding new handlers to a <see cref="IRouteBuilder"/>.
+/// </summary>
+public static class RequestDelegateRouteBuilderExtensions
{
/// <summary>
- /// Provides extension methods for adding new handlers to a <see cref="IRouteBuilder"/>.
+ /// Adds a route to the <see cref="IRouteBuilder"/> for the given <paramref name="template"/>, and
+ /// <paramref name="handler"/>.
/// </summary>
- public static class RequestDelegateRouteBuilderExtensions
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapRoute(this IRouteBuilder builder, string template, RequestDelegate handler)
{
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> for the given <paramref name="template"/>, and
- /// <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapRoute(this IRouteBuilder builder, string template, RequestDelegate handler)
- {
- var route = new Route(
- new RouteHandler(handler),
- template,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: GetConstraintResolver(builder));
+ var route = new Route(
+ new RouteHandler(handler),
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: GetConstraintResolver(builder));
- builder.Routes.Add(route);
- return builder;
- }
+ builder.Routes.Add(route);
+ return builder;
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> for the given <paramref name="template"/>, and
- /// <paramref name="action"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapMiddlewareRoute(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
- {
- var nested = builder.ApplicationBuilder.New();
- action(nested);
- return builder.MapRoute(template, nested.Build());
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> for the given <paramref name="template"/>, and
+ /// <paramref name="action"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapMiddlewareRoute(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
+ {
+ var nested = builder.ApplicationBuilder.New();
+ action(nested);
+ return builder.MapRoute(template, nested.Build());
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapDelete(this IRouteBuilder builder, string template, RequestDelegate handler)
- {
- return builder.MapVerb("DELETE", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapDelete(this IRouteBuilder builder, string template, RequestDelegate handler)
+ {
+ return builder.MapVerb("DELETE", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
- /// <paramref name="template"/>, and <paramref name="action"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapMiddlewareDelete(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
- {
- return builder.MapMiddlewareVerb("DELETE", template, action);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
+ /// <paramref name="template"/>, and <paramref name="action"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapMiddlewareDelete(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
+ {
+ return builder.MapMiddlewareVerb("DELETE", template, action);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapDelete(
- this IRouteBuilder builder,
- string template,
- Func<HttpRequest, HttpResponse, RouteData, Task> handler)
- {
- return builder.MapVerb("DELETE", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP DELETE requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapDelete(
+ this IRouteBuilder builder,
+ string template,
+ Func<HttpRequest, HttpResponse, RouteData, Task> handler)
+ {
+ return builder.MapVerb("DELETE", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapGet(this IRouteBuilder builder, string template, RequestDelegate handler)
- {
- return builder.MapVerb("GET", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapGet(this IRouteBuilder builder, string template, RequestDelegate handler)
+ {
+ return builder.MapVerb("GET", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
- /// <paramref name="template"/>, and <paramref name="action"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapMiddlewareGet(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
- {
- return builder.MapMiddlewareVerb("GET", template, action);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
+ /// <paramref name="template"/>, and <paramref name="action"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapMiddlewareGet(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
+ {
+ return builder.MapMiddlewareVerb("GET", template, action);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapGet(
- this IRouteBuilder builder,
- string template,
- Func<HttpRequest, HttpResponse, RouteData, Task> handler)
- {
- return builder.MapVerb("GET", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP GET requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapGet(
+ this IRouteBuilder builder,
+ string template,
+ Func<HttpRequest, HttpResponse, RouteData, Task> handler)
+ {
+ return builder.MapVerb("GET", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapPost(this IRouteBuilder builder, string template, RequestDelegate handler)
- {
- return builder.MapVerb("POST", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapPost(this IRouteBuilder builder, string template, RequestDelegate handler)
+ {
+ return builder.MapVerb("POST", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
- /// <paramref name="template"/>, and <paramref name="action"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapMiddlewarePost(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
- {
- return builder.MapMiddlewareVerb("POST", template, action);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
+ /// <paramref name="template"/>, and <paramref name="action"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapMiddlewarePost(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
+ {
+ return builder.MapMiddlewareVerb("POST", template, action);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapPost(
- this IRouteBuilder builder,
- string template,
- Func<HttpRequest, HttpResponse, RouteData, Task> handler)
- {
- return builder.MapVerb("POST", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP POST requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapPost(
+ this IRouteBuilder builder,
+ string template,
+ Func<HttpRequest, HttpResponse, RouteData, Task> handler)
+ {
+ return builder.MapVerb("POST", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapPut(this IRouteBuilder builder, string template, RequestDelegate handler)
- {
- return builder.MapVerb("PUT", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapPut(this IRouteBuilder builder, string template, RequestDelegate handler)
+ {
+ return builder.MapVerb("PUT", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
- /// <paramref name="template"/>, and <paramref name="action"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapMiddlewarePut(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
- {
- return builder.MapMiddlewareVerb("PUT", template, action);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
+ /// <paramref name="template"/>, and <paramref name="action"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapMiddlewarePut(this IRouteBuilder builder, string template, Action<IApplicationBuilder> action)
+ {
+ return builder.MapMiddlewareVerb("PUT", template, action);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
- /// <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapPut(
- this IRouteBuilder builder,
- string template,
- Func<HttpRequest, HttpResponse, RouteData, Task> handler)
- {
- return builder.MapVerb("PUT", template, handler);
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP PUT requests for the given
+ /// <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapPut(
+ this IRouteBuilder builder,
+ string template,
+ Func<HttpRequest, HttpResponse, RouteData, Task> handler)
+ {
+ return builder.MapVerb("PUT", template, handler);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
- /// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="verb">The HTTP verb allowed by the route.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapVerb(
- this IRouteBuilder builder,
- string verb,
- string template,
- Func<HttpRequest, HttpResponse, RouteData, Task> handler)
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
+ /// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="verb">The HTTP verb allowed by the route.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapVerb(
+ this IRouteBuilder builder,
+ string verb,
+ string template,
+ Func<HttpRequest, HttpResponse, RouteData, Task> handler)
+ {
+ RequestDelegate requestDelegate = (httpContext) =>
{
- RequestDelegate requestDelegate = (httpContext) =>
- {
- return handler(httpContext.Request, httpContext.Response, httpContext.GetRouteData());
- };
+ return handler(httpContext.Request, httpContext.Response, httpContext.GetRouteData());
+ };
- return builder.MapVerb(verb, template, requestDelegate);
- }
+ return builder.MapVerb(verb, template, requestDelegate);
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
- /// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="handler"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="verb">The HTTP verb allowed by the route.</param>
- /// <param name="template">The route template.</param>
- /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapVerb(
- this IRouteBuilder builder,
- string verb,
- string template,
- RequestDelegate handler)
- {
- var route = new Route(
- new RouteHandler(handler),
- template,
- defaults: null,
- constraints: new RouteValueDictionary(new { httpMethod = new HttpMethodRouteConstraint(verb) })!,
- dataTokens: null,
- inlineConstraintResolver: GetConstraintResolver(builder));
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
+ /// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="handler"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="verb">The HTTP verb allowed by the route.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="handler">The <see cref="RequestDelegate"/> route handler.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapVerb(
+ this IRouteBuilder builder,
+ string verb,
+ string template,
+ RequestDelegate handler)
+ {
+ var route = new Route(
+ new RouteHandler(handler),
+ template,
+ defaults: null,
+ constraints: new RouteValueDictionary(new { httpMethod = new HttpMethodRouteConstraint(verb) })!,
+ dataTokens: null,
+ inlineConstraintResolver: GetConstraintResolver(builder));
- builder.Routes.Add(route);
- return builder;
- }
+ builder.Routes.Add(route);
+ return builder;
+ }
- /// <summary>
- /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
- /// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="action"/>.
- /// </summary>
- /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
- /// <param name="verb">The HTTP verb allowed by the route.</param>
- /// <param name="template">The route template.</param>
- /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
- /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
- public static IRouteBuilder MapMiddlewareVerb(
- this IRouteBuilder builder,
- string verb,
- string template,
- Action<IApplicationBuilder> action)
- {
- var nested = builder.ApplicationBuilder.New();
- action(nested);
- return builder.MapVerb(verb, template, nested.Build());
- }
+ /// <summary>
+ /// Adds a route to the <see cref="IRouteBuilder"/> that only matches HTTP requests for the given
+ /// <paramref name="verb"/>, <paramref name="template"/>, and <paramref name="action"/>.
+ /// </summary>
+ /// <param name="builder">The <see cref="IRouteBuilder"/>.</param>
+ /// <param name="verb">The HTTP verb allowed by the route.</param>
+ /// <param name="template">The route template.</param>
+ /// <param name="action">The action to apply to the <see cref="IApplicationBuilder"/>.</param>
+ /// <returns>A reference to the <paramref name="builder"/> after this operation has completed.</returns>
+ public static IRouteBuilder MapMiddlewareVerb(
+ this IRouteBuilder builder,
+ string verb,
+ string template,
+ Action<IApplicationBuilder> action)
+ {
+ var nested = builder.ApplicationBuilder.New();
+ action(nested);
+ return builder.MapVerb(verb, template, nested.Build());
+ }
- private static IInlineConstraintResolver GetConstraintResolver(IRouteBuilder builder)
- {
- return builder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
- }
+ private static IInlineConstraintResolver GetConstraintResolver(IRouteBuilder builder)
+ {
+ return builder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>();
}
}
diff --git a/src/Http/Routing/src/Route.cs b/src/Http/Routing/src/Route.cs
index 80150899f1..ed9ba05b76 100644
--- a/src/Http/Routing/src/Route.cs
+++ b/src/Http/Routing/src/Route.cs
@@ -5,105 +5,104 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents an instance of a route.
+/// </summary>
+public class Route : RouteBase
{
+ private readonly IRouter _target;
+
/// <summary>
- /// Represents an instance of a route.
+ /// Constructs a new <see cref="Route"/> instance.
/// </summary>
- public class Route : RouteBase
+ /// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
+ /// <param name="routeTemplate">A string representation of the route template.</param>
+ /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
+ public Route(
+ IRouter target,
+ string routeTemplate,
+ IInlineConstraintResolver inlineConstraintResolver)
+ : this(
+ target,
+ routeTemplate,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: inlineConstraintResolver)
{
- private readonly IRouter _target;
+ }
- /// <summary>
- /// Constructs a new <see cref="Route"/> instance.
- /// </summary>
- /// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
- /// <param name="routeTemplate">A string representation of the route template.</param>
- /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
- public Route(
- IRouter target,
- string routeTemplate,
- IInlineConstraintResolver inlineConstraintResolver)
- : this(
- target,
- routeTemplate,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: inlineConstraintResolver)
- {
- }
+ /// <summary>
+ /// Constructs a new <see cref="Route"/> instance.
+ /// </summary>
+ /// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
+ /// <param name="routeTemplate">A string representation of the route template.</param>
+ /// <param name="defaults">The default values for parameters in the route.</param>
+ /// <param name="constraints">The constraints for the route.</param>
+ /// <param name="dataTokens">The data tokens for the route.</param>
+ /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
+ public Route(
+ IRouter target,
+ string routeTemplate,
+ RouteValueDictionary? defaults,
+ IDictionary<string, object>? constraints,
+ RouteValueDictionary? dataTokens,
+ IInlineConstraintResolver inlineConstraintResolver)
+ : this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
+ {
+ }
- /// <summary>
- /// Constructs a new <see cref="Route"/> instance.
- /// </summary>
- /// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
- /// <param name="routeTemplate">A string representation of the route template.</param>
- /// <param name="defaults">The default values for parameters in the route.</param>
- /// <param name="constraints">The constraints for the route.</param>
- /// <param name="dataTokens">The data tokens for the route.</param>
- /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
- public Route(
- IRouter target,
- string routeTemplate,
- RouteValueDictionary? defaults,
- IDictionary<string, object>? constraints,
- RouteValueDictionary? dataTokens,
- IInlineConstraintResolver inlineConstraintResolver)
- : this(target, null, routeTemplate, defaults, constraints, dataTokens, inlineConstraintResolver)
+ /// <summary>
+ /// Constructs a new <see cref="Route"/> instance.
+ /// </summary>
+ /// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
+ /// <param name="routeName">The name of the route.</param>
+ /// <param name="routeTemplate">A string representation of the route template.</param>
+ /// <param name="defaults">The default values for parameters in the route.</param>
+ /// <param name="constraints">The constraints for the route.</param>
+ /// <param name="dataTokens">The data tokens for the route.</param>
+ /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
+ public Route(
+ IRouter target,
+ string? routeName,
+ string? routeTemplate,
+ RouteValueDictionary? defaults,
+ IDictionary<string, object>? constraints,
+ RouteValueDictionary? dataTokens,
+ IInlineConstraintResolver inlineConstraintResolver)
+ : base(
+ routeTemplate,
+ routeName,
+ inlineConstraintResolver,
+ defaults,
+ constraints,
+ dataTokens)
+ {
+ if (target == null)
{
+ throw new ArgumentNullException(nameof(target));
}
- /// <summary>
- /// Constructs a new <see cref="Route"/> instance.
- /// </summary>
- /// <param name="target">An <see cref="IRouter"/> instance associated with the component.</param>
- /// <param name="routeName">The name of the route.</param>
- /// <param name="routeTemplate">A string representation of the route template.</param>
- /// <param name="defaults">The default values for parameters in the route.</param>
- /// <param name="constraints">The constraints for the route.</param>
- /// <param name="dataTokens">The data tokens for the route.</param>
- /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
- public Route(
- IRouter target,
- string? routeName,
- string? routeTemplate,
- RouteValueDictionary? defaults,
- IDictionary<string, object>? constraints,
- RouteValueDictionary? dataTokens,
- IInlineConstraintResolver inlineConstraintResolver)
- : base(
- routeTemplate,
- routeName,
- inlineConstraintResolver,
- defaults,
- constraints,
- dataTokens)
- {
- if (target == null)
- {
- throw new ArgumentNullException(nameof(target));
- }
-
- _target = target;
- }
+ _target = target;
+ }
- /// <summary>
- /// Gets a string representation of the route template.
- /// </summary>
- public string? RouteTemplate => ParsedTemplate.TemplateText;
+ /// <summary>
+ /// Gets a string representation of the route template.
+ /// </summary>
+ public string? RouteTemplate => ParsedTemplate.TemplateText;
- /// <inheritdoc />
- protected override Task OnRouteMatched(RouteContext context)
- {
- context.RouteData.Routers.Add(_target);
- return _target.RouteAsync(context);
- }
+ /// <inheritdoc />
+ protected override Task OnRouteMatched(RouteContext context)
+ {
+ context.RouteData.Routers.Add(_target);
+ return _target.RouteAsync(context);
+ }
- /// <inheritdoc />
- protected override VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context)
- {
- return _target.GetVirtualPath(context);
- }
+ /// <inheritdoc />
+ protected override VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context)
+ {
+ return _target.GetVirtualPath(context);
}
}
diff --git a/src/Http/Routing/src/RouteBase.cs b/src/Http/Routing/src/RouteBase.cs
index f6a05da567..7b5a07f45e 100644
--- a/src/Http/Routing/src/RouteBase.cs
+++ b/src/Http/Routing/src/RouteBase.cs
@@ -11,251 +11,251 @@ using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Base class implementation of an <see cref="IRouter"/>.
+/// </summary>
+public abstract partial class RouteBase : IRouter, INamedRouter
{
+ private readonly object _loggersLock = new object();
+
+ private TemplateMatcher? _matcher;
+ private TemplateBinder? _binder;
+ private ILogger? _logger;
+ private ILogger? _constraintLogger;
+
/// <summary>
- /// Base class implementation of an <see cref="IRouter"/>.
+ /// Creates a new <see cref="RouteBase"/> instance.
/// </summary>
- public abstract partial class RouteBase : IRouter, INamedRouter
+ /// <param name="template">The route template.</param>
+ /// <param name="name">The name of the route.</param>
+ /// <param name="constraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
+ /// <param name="defaults">The default values for parameters in the route.</param>
+ /// <param name="constraints">The constraints for the route.</param>
+ /// <param name="dataTokens">The data tokens for the route.</param>
+ public RouteBase(
+ string? template,
+ string? name,
+ IInlineConstraintResolver constraintResolver,
+ RouteValueDictionary? defaults,
+ IDictionary<string, object>? constraints,
+ RouteValueDictionary? dataTokens)
{
- private readonly object _loggersLock = new object();
-
- private TemplateMatcher? _matcher;
- private TemplateBinder? _binder;
- private ILogger? _logger;
- private ILogger? _constraintLogger;
-
- /// <summary>
- /// Creates a new <see cref="RouteBase"/> instance.
- /// </summary>
- /// <param name="template">The route template.</param>
- /// <param name="name">The name of the route.</param>
- /// <param name="constraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
- /// <param name="defaults">The default values for parameters in the route.</param>
- /// <param name="constraints">The constraints for the route.</param>
- /// <param name="dataTokens">The data tokens for the route.</param>
- public RouteBase(
- string? template,
- string? name,
- IInlineConstraintResolver constraintResolver,
- RouteValueDictionary? defaults,
- IDictionary<string, object>? constraints,
- RouteValueDictionary? dataTokens)
+ if (constraintResolver == null)
{
- if (constraintResolver == null)
- {
- throw new ArgumentNullException(nameof(constraintResolver));
- }
+ throw new ArgumentNullException(nameof(constraintResolver));
+ }
- template = template ?? string.Empty;
- Name = name;
- ConstraintResolver = constraintResolver;
- DataTokens = dataTokens ?? new RouteValueDictionary();
+ template = template ?? string.Empty;
+ Name = name;
+ ConstraintResolver = constraintResolver;
+ DataTokens = dataTokens ?? new RouteValueDictionary();
- try
- {
- // Data we parse from the template will be used to fill in the rest of the constraints or
- // defaults. The parser will throw for invalid routes.
- ParsedTemplate = TemplateParser.Parse(template);
+ try
+ {
+ // Data we parse from the template will be used to fill in the rest of the constraints or
+ // defaults. The parser will throw for invalid routes.
+ ParsedTemplate = TemplateParser.Parse(template);
- Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
- Defaults = GetDefaults(ParsedTemplate, defaults);
- }
- catch (Exception exception)
- {
- throw new RouteCreationException(Resources.FormatTemplateRoute_Exception(name, template), exception);
- }
+ Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints);
+ Defaults = GetDefaults(ParsedTemplate, defaults);
}
-
- /// <summary>
- /// Gets the set of constraints associated with each route.
- /// </summary>
- public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
-
- /// <summary>
- /// Gets the resolver used for resolving inline constraints.
- /// </summary>
- protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
-
- /// <summary>
- /// Gets the data tokens associated with the route.
- /// </summary>
- public virtual RouteValueDictionary DataTokens { get; protected set; }
-
- /// <summary>
- /// Gets the default values for each route parameter.
- /// </summary>
- public virtual RouteValueDictionary Defaults { get; protected set; }
-
- /// <inheritdoc />
- public virtual string? Name { get; protected set; }
-
- /// <summary>
- /// Gets the <see cref="RouteTemplate"/> associated with the route.
- /// </summary>
- public virtual RouteTemplate ParsedTemplate { get; protected set; }
-
- /// <summary>
- /// Executes asynchronously whenever routing occurs.
- /// </summary>
- /// <param name="context">A <see cref="RouteContext"/> instance.</param>
- protected abstract Task OnRouteMatched(RouteContext context);
-
- /// <summary>
- /// Executes whenever a virtual path is derived from a <paramref name="context"/>.
- /// </summary>
- /// <param name="context">A <see cref="VirtualPathContext"/> instance.</param>
- /// <returns>A <see cref="VirtualPathData"/> instance.</returns>
- protected abstract VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context);
-
- /// <inheritdoc />
- public virtual Task RouteAsync(RouteContext context)
+ catch (Exception exception)
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
+ throw new RouteCreationException(Resources.FormatTemplateRoute_Exception(name, template), exception);
+ }
+ }
- EnsureMatcher();
- EnsureLoggers(context.HttpContext);
+ /// <summary>
+ /// Gets the set of constraints associated with each route.
+ /// </summary>
+ public virtual IDictionary<string, IRouteConstraint> Constraints { get; protected set; }
- var requestPath = context.HttpContext.Request.Path;
+ /// <summary>
+ /// Gets the resolver used for resolving inline constraints.
+ /// </summary>
+ protected virtual IInlineConstraintResolver ConstraintResolver { get; set; }
- if (!_matcher.TryMatch(requestPath, context.RouteData.Values))
- {
- // If we got back a null value set, that means the URI did not match
- return Task.CompletedTask;
- }
+ /// <summary>
+ /// Gets the data tokens associated with the route.
+ /// </summary>
+ public virtual RouteValueDictionary DataTokens { get; protected set; }
- // Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all
- // created lazily.
- if (DataTokens.Count > 0)
- {
- MergeValues(context.RouteData.DataTokens, DataTokens);
- }
+ /// <summary>
+ /// Gets the default values for each route parameter.
+ /// </summary>
+ public virtual RouteValueDictionary Defaults { get; protected set; }
- if (!RouteConstraintMatcher.Match(
- Constraints,
- context.RouteData.Values,
- context.HttpContext,
- this,
- RouteDirection.IncomingRequest,
- _constraintLogger))
- {
- return Task.CompletedTask;
- }
- Log.RequestMatchedRoute(_logger, Name, ParsedTemplate.TemplateText);
+ /// <inheritdoc />
+ public virtual string? Name { get; protected set; }
+
+ /// <summary>
+ /// Gets the <see cref="RouteTemplate"/> associated with the route.
+ /// </summary>
+ public virtual RouteTemplate ParsedTemplate { get; protected set; }
- return OnRouteMatched(context);
+ /// <summary>
+ /// Executes asynchronously whenever routing occurs.
+ /// </summary>
+ /// <param name="context">A <see cref="RouteContext"/> instance.</param>
+ protected abstract Task OnRouteMatched(RouteContext context);
+
+ /// <summary>
+ /// Executes whenever a virtual path is derived from a <paramref name="context"/>.
+ /// </summary>
+ /// <param name="context">A <see cref="VirtualPathContext"/> instance.</param>
+ /// <returns>A <see cref="VirtualPathData"/> instance.</returns>
+ protected abstract VirtualPathData? OnVirtualPathGenerated(VirtualPathContext context);
+
+ /// <inheritdoc />
+ public virtual Task RouteAsync(RouteContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
}
- /// <inheritdoc />
- public virtual VirtualPathData? GetVirtualPath(VirtualPathContext context)
+ EnsureMatcher();
+ EnsureLoggers(context.HttpContext);
+
+ var requestPath = context.HttpContext.Request.Path;
+
+ if (!_matcher.TryMatch(requestPath, context.RouteData.Values))
{
- EnsureBinder(context.HttpContext);
- EnsureLoggers(context.HttpContext);
+ // If we got back a null value set, that means the URI did not match
+ return Task.CompletedTask;
+ }
- var values = _binder.GetValues(context.AmbientValues, context.Values);
- if (values == null)
- {
- // We're missing one of the required values for this route.
- return null;
- }
+ // Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all
+ // created lazily.
+ if (DataTokens.Count > 0)
+ {
+ MergeValues(context.RouteData.DataTokens, DataTokens);
+ }
- if (!RouteConstraintMatcher.Match(
- Constraints,
- values.CombinedValues,
- context.HttpContext,
- this,
- RouteDirection.UrlGeneration,
- _constraintLogger))
- {
- return null;
- }
+ if (!RouteConstraintMatcher.Match(
+ Constraints,
+ context.RouteData.Values,
+ context.HttpContext,
+ this,
+ RouteDirection.IncomingRequest,
+ _constraintLogger))
+ {
+ return Task.CompletedTask;
+ }
+ Log.RequestMatchedRoute(_logger, Name, ParsedTemplate.TemplateText);
- context.Values = values.CombinedValues;
+ return OnRouteMatched(context);
+ }
- var pathData = OnVirtualPathGenerated(context);
- if (pathData != null)
- {
- // If the target generates a value then that can short circuit.
- return pathData;
- }
+ /// <inheritdoc />
+ public virtual VirtualPathData? GetVirtualPath(VirtualPathContext context)
+ {
+ EnsureBinder(context.HttpContext);
+ EnsureLoggers(context.HttpContext);
- // If we can produce a value go ahead and do it, the caller can check context.IsBound
- // to see if the values were validated.
+ var values = _binder.GetValues(context.AmbientValues, context.Values);
+ if (values == null)
+ {
+ // We're missing one of the required values for this route.
+ return null;
+ }
- // When we still cannot produce a value, this should return null.
- var virtualPath = _binder.BindValues(values.AcceptedValues);
- if (virtualPath == null)
- {
- return null;
- }
+ if (!RouteConstraintMatcher.Match(
+ Constraints,
+ values.CombinedValues,
+ context.HttpContext,
+ this,
+ RouteDirection.UrlGeneration,
+ _constraintLogger))
+ {
+ return null;
+ }
- pathData = new VirtualPathData(this, virtualPath);
- if (DataTokens != null)
- {
- foreach (var dataToken in DataTokens)
- {
- pathData.DataTokens.Add(dataToken.Key, dataToken.Value);
- }
- }
+ context.Values = values.CombinedValues;
+ var pathData = OnVirtualPathGenerated(context);
+ if (pathData != null)
+ {
+ // If the target generates a value then that can short circuit.
return pathData;
}
- /// <summary>
- /// Extracts constatins from a given <see cref="RouteTemplate"/>.
- /// </summary>
- /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
- /// <param name="parsedTemplate">A <see cref="RouteTemplate"/> instance.</param>
- /// <param name="constraints">A collection of constraints on the route template.</param>
- protected static IDictionary<string, IRouteConstraint> GetConstraints(
- IInlineConstraintResolver inlineConstraintResolver,
- RouteTemplate parsedTemplate,
- IDictionary<string, object>? constraints)
+ // If we can produce a value go ahead and do it, the caller can check context.IsBound
+ // to see if the values were validated.
+
+ // When we still cannot produce a value, this should return null.
+ var virtualPath = _binder.BindValues(values.AcceptedValues);
+ if (virtualPath == null)
{
- var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText!);
+ return null;
+ }
- if (constraints != null)
+ pathData = new VirtualPathData(this, virtualPath);
+ if (DataTokens != null)
+ {
+ foreach (var dataToken in DataTokens)
{
- foreach (var kvp in constraints)
- {
- constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
- }
+ pathData.DataTokens.Add(dataToken.Key, dataToken.Value);
}
+ }
+
+ return pathData;
+ }
+
+ /// <summary>
+ /// Extracts constatins from a given <see cref="RouteTemplate"/>.
+ /// </summary>
+ /// <param name="inlineConstraintResolver">An <see cref="IInlineConstraintResolver"/> used for resolving inline constraints.</param>
+ /// <param name="parsedTemplate">A <see cref="RouteTemplate"/> instance.</param>
+ /// <param name="constraints">A collection of constraints on the route template.</param>
+ protected static IDictionary<string, IRouteConstraint> GetConstraints(
+ IInlineConstraintResolver inlineConstraintResolver,
+ RouteTemplate parsedTemplate,
+ IDictionary<string, object>? constraints)
+ {
+ var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText!);
- foreach (var parameter in parsedTemplate.Parameters)
+ if (constraints != null)
+ {
+ foreach (var kvp in constraints)
{
- if (parameter.IsOptional)
- {
- constraintBuilder.SetOptional(parameter.Name!);
- }
+ constraintBuilder.AddConstraint(kvp.Key, kvp.Value);
+ }
+ }
- foreach (var inlineConstraint in parameter.InlineConstraints)
- {
- constraintBuilder.AddResolvedConstraint(parameter.Name!, inlineConstraint.Constraint);
- }
+ foreach (var parameter in parsedTemplate.Parameters)
+ {
+ if (parameter.IsOptional)
+ {
+ constraintBuilder.SetOptional(parameter.Name!);
}
- return constraintBuilder.Build();
+ foreach (var inlineConstraint in parameter.InlineConstraints)
+ {
+ constraintBuilder.AddResolvedConstraint(parameter.Name!, inlineConstraint.Constraint);
+ }
}
- /// <summary>
- /// Gets the default values for parameters in a templates.
- /// </summary>
- /// <param name="parsedTemplate">A <see cref="RouteTemplate"/> instance.</param>
- /// <param name="defaults">A collection of defaults for each parameter.</param>
- protected static RouteValueDictionary GetDefaults(
- RouteTemplate parsedTemplate,
- RouteValueDictionary? defaults)
- {
- var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
+ return constraintBuilder.Build();
+ }
+
+ /// <summary>
+ /// Gets the default values for parameters in a templates.
+ /// </summary>
+ /// <param name="parsedTemplate">A <see cref="RouteTemplate"/> instance.</param>
+ /// <param name="defaults">A collection of defaults for each parameter.</param>
+ protected static RouteValueDictionary GetDefaults(
+ RouteTemplate parsedTemplate,
+ RouteValueDictionary? defaults)
+ {
+ var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults);
- foreach (var parameter in parsedTemplate.Parameters)
+ foreach (var parameter in parsedTemplate.Parameters)
+ {
+ if (parameter.DefaultValue != null)
{
- if (parameter.DefaultValue != null)
- {
#if RVD_TryAdd
if (!result.TryAdd(parameter.Name, parameter.DefaultValue))
{
@@ -264,97 +264,96 @@ namespace Microsoft.AspNetCore.Routing
parameter.Name));
}
#else
- if (result.ContainsKey(parameter.Name!))
- {
- throw new InvalidOperationException(
- Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
- parameter.Name));
- }
- else
- {
- result.Add(parameter.Name!, parameter.DefaultValue);
- }
-#endif
+ if (result.ContainsKey(parameter.Name!))
+ {
+ throw new InvalidOperationException(
+ Resources.FormatTemplateRoute_CannotHaveDefaultValueSpecifiedInlineAndExplicitly(
+ parameter.Name));
+ }
+ else
+ {
+ result.Add(parameter.Name!, parameter.DefaultValue);
}
+#endif
}
-
- return result;
}
- private static void MergeValues(
- RouteValueDictionary destination,
- RouteValueDictionary values)
+ return result;
+ }
+
+ private static void MergeValues(
+ RouteValueDictionary destination,
+ RouteValueDictionary values)
+ {
+ foreach (var kvp in values)
{
- foreach (var kvp in values)
- {
- // This will replace the original value for the specified key.
- // Values from the matched route will take preference over previous
- // data in the route context.
- destination[kvp.Key] = kvp.Value;
- }
+ // This will replace the original value for the specified key.
+ // Values from the matched route will take preference over previous
+ // data in the route context.
+ destination[kvp.Key] = kvp.Value;
}
+ }
- [MemberNotNull(nameof(_binder))]
- private void EnsureBinder(HttpContext context)
+ [MemberNotNull(nameof(_binder))]
+ private void EnsureBinder(HttpContext context)
+ {
+ if (_binder == null)
{
- if (_binder == null)
- {
- var binderFactory = context.RequestServices.GetRequiredService<TemplateBinderFactory>();
- _binder = binderFactory.Create(ParsedTemplate, Defaults);
- }
+ var binderFactory = context.RequestServices.GetRequiredService<TemplateBinderFactory>();
+ _binder = binderFactory.Create(ParsedTemplate, Defaults);
}
+ }
- [MemberNotNull(nameof(_logger), nameof(_constraintLogger))]
- private void EnsureLoggers(HttpContext context)
+ [MemberNotNull(nameof(_logger), nameof(_constraintLogger))]
+ private void EnsureLoggers(HttpContext context)
+ {
+ // We check first using the _logger to see if the loggers have been initialized to avoid taking
+ // the lock on the most common case.
+ if (_logger == null)
{
- // We check first using the _logger to see if the loggers have been initialized to avoid taking
- // the lock on the most common case.
- if (_logger == null)
+ // We need to lock here to ensure that _constraintLogger and _logger get initialized atomically.
+ lock (_loggersLock)
{
- // We need to lock here to ensure that _constraintLogger and _logger get initialized atomically.
- lock (_loggersLock)
+ if (_logger != null)
{
- if (_logger != null)
- {
- // Multiple threads might have tried to acquire the lock at the same time. Technically
- // there is nothing wrong if things get reinitialized by a second thread, but its easy
- // to prevent by just rechecking and returning here.
- Debug.Assert(_constraintLogger != null);
+ // Multiple threads might have tried to acquire the lock at the same time. Technically
+ // there is nothing wrong if things get reinitialized by a second thread, but its easy
+ // to prevent by just rechecking and returning here.
+ Debug.Assert(_constraintLogger != null);
- return;
- }
-
- var factory = context.RequestServices.GetRequiredService<ILoggerFactory>();
- _constraintLogger = factory.CreateLogger(typeof(RouteConstraintMatcher).FullName!);
- _logger = factory.CreateLogger(typeof(RouteBase).FullName!);
+ return;
}
+ var factory = context.RequestServices.GetRequiredService<ILoggerFactory>();
+ _constraintLogger = factory.CreateLogger(typeof(RouteConstraintMatcher).FullName!);
+ _logger = factory.CreateLogger(typeof(RouteBase).FullName!);
}
- Debug.Assert(_constraintLogger != null);
}
- [MemberNotNull(nameof(_matcher))]
- private void EnsureMatcher()
- {
- if (_matcher == null)
- {
- _matcher = new TemplateMatcher(ParsedTemplate, Defaults);
- }
- }
+ Debug.Assert(_constraintLogger != null);
+ }
- /// <inheritdoc />
- public override string ToString()
+ [MemberNotNull(nameof(_matcher))]
+ private void EnsureMatcher()
+ {
+ if (_matcher == null)
{
- return ParsedTemplate.TemplateText!;
+ _matcher = new TemplateMatcher(ParsedTemplate, Defaults);
}
+ }
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Debug,
- "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'",
- EventName = "RequestMatchedRoute")]
- public static partial void RequestMatchedRoute(ILogger logger, string? routeName, string? routeTemplate);
- }
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ return ParsedTemplate.TemplateText!;
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Debug,
+ "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'",
+ EventName = "RequestMatchedRoute")]
+ public static partial void RequestMatchedRoute(ILogger logger, string? routeName, string? routeTemplate);
}
}
diff --git a/src/Http/Routing/src/RouteBuilder.cs b/src/Http/Routing/src/RouteBuilder.cs
index 8836122f62..b40120bccd 100644
--- a/src/Http/Routing/src/RouteBuilder.cs
+++ b/src/Http/Routing/src/RouteBuilder.cs
@@ -8,73 +8,72 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Provides support for specifying routes in an application.
+/// </summary>
+public class RouteBuilder : IRouteBuilder
{
/// <summary>
- /// Provides support for specifying routes in an application.
+ /// Constructs a new <see cref="RouteBuilder"/> instance given an <paramref name="applicationBuilder"/>.
/// </summary>
- public class RouteBuilder : IRouteBuilder
+ /// <param name="applicationBuilder">An <see cref="IApplicationBuilder"/> instance.</param>
+ public RouteBuilder(IApplicationBuilder applicationBuilder)
+ : this(applicationBuilder, defaultHandler: null)
{
- /// <summary>
- /// Constructs a new <see cref="RouteBuilder"/> instance given an <paramref name="applicationBuilder"/>.
- /// </summary>
- /// <param name="applicationBuilder">An <see cref="IApplicationBuilder"/> instance.</param>
- public RouteBuilder(IApplicationBuilder applicationBuilder)
- : this(applicationBuilder, defaultHandler: null)
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="RouteBuilder"/> instance given an <paramref name="applicationBuilder"/>
+ /// and <paramref name="defaultHandler"/>.
+ /// </summary>
+ /// <param name="applicationBuilder">An <see cref="IApplicationBuilder"/> instance.</param>
+ /// <param name="defaultHandler">The default <see cref="IRouter"/> used if a new route is added without a handler.</param>
+ public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter? defaultHandler)
+ {
+ if (applicationBuilder == null)
{
+ throw new ArgumentNullException(nameof(applicationBuilder));
}
- /// <summary>
- /// Constructs a new <see cref="RouteBuilder"/> instance given an <paramref name="applicationBuilder"/>
- /// and <paramref name="defaultHandler"/>.
- /// </summary>
- /// <param name="applicationBuilder">An <see cref="IApplicationBuilder"/> instance.</param>
- /// <param name="defaultHandler">The default <see cref="IRouter"/> used if a new route is added without a handler.</param>
- public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter? defaultHandler)
+ if (applicationBuilder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
{
- if (applicationBuilder == null)
- {
- throw new ArgumentNullException(nameof(applicationBuilder));
- }
+ throw new InvalidOperationException(Resources.FormatUnableToFindServices(
+ nameof(IServiceCollection),
+ nameof(RoutingServiceCollectionExtensions.AddRouting),
+ "ConfigureServices(...)"));
+ }
- if (applicationBuilder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null)
- {
- throw new InvalidOperationException(Resources.FormatUnableToFindServices(
- nameof(IServiceCollection),
- nameof(RoutingServiceCollectionExtensions.AddRouting),
- "ConfigureServices(...)"));
- }
+ ApplicationBuilder = applicationBuilder;
+ DefaultHandler = defaultHandler;
+ ServiceProvider = applicationBuilder.ApplicationServices;
- ApplicationBuilder = applicationBuilder;
- DefaultHandler = defaultHandler;
- ServiceProvider = applicationBuilder.ApplicationServices;
+ Routes = new List<IRouter>();
+ }
- Routes = new List<IRouter>();
- }
+ /// <inheritdoc />
+ public IApplicationBuilder ApplicationBuilder { get; }
- /// <inheritdoc />
- public IApplicationBuilder ApplicationBuilder { get; }
+ /// <inheritdoc />
+ public IRouter? DefaultHandler { get; set; }
- /// <inheritdoc />
- public IRouter? DefaultHandler { get; set; }
+ /// <inheritdoc />
+ public IServiceProvider ServiceProvider { get; }
- /// <inheritdoc />
- public IServiceProvider ServiceProvider { get; }
+ /// <inheritdoc />
+ public IList<IRouter> Routes { get; }
- /// <inheritdoc />
- public IList<IRouter> Routes { get; }
+ /// <inheritdoc />
+ public IRouter Build()
+ {
+ var routeCollection = new RouteCollection();
- /// <inheritdoc />
- public IRouter Build()
+ foreach (var route in Routes)
{
- var routeCollection = new RouteCollection();
-
- foreach (var route in Routes)
- {
- routeCollection.Add(route);
- }
-
- return routeCollection;
+ routeCollection.Add(route);
}
+
+ return routeCollection;
}
}
diff --git a/src/Http/Routing/src/RouteCollection.cs b/src/Http/Routing/src/RouteCollection.cs
index 55e21d4225..4d904c8078 100644
--- a/src/Http/Routing/src/RouteCollection.cs
+++ b/src/Http/Routing/src/RouteCollection.cs
@@ -12,195 +12,194 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Supports managing a collection for multiple routes.
+/// </summary>
+public class RouteCollection : IRouteCollection
{
+ private static readonly char[] UrlQueryDelimiters = new char[] { '?', '#' };
+ private readonly List<IRouter> _routes = new List<IRouter>();
+ private readonly List<IRouter> _unnamedRoutes = new List<IRouter>();
+ private readonly Dictionary<string, INamedRouter> _namedRoutes =
+ new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase);
+
+ private RouteOptions? _options;
+
/// <summary>
- /// Supports managing a collection for multiple routes.
+ /// Gets the route at a given index.
/// </summary>
- public class RouteCollection : IRouteCollection
+ /// <value>The route at the given index.</value>
+ public IRouter this[int index]
{
- private static readonly char[] UrlQueryDelimiters = new char[] { '?', '#' };
- private readonly List<IRouter> _routes = new List<IRouter>();
- private readonly List<IRouter> _unnamedRoutes = new List<IRouter>();
- private readonly Dictionary<string, INamedRouter> _namedRoutes =
- new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase);
-
- private RouteOptions? _options;
-
- /// <summary>
- /// Gets the route at a given index.
- /// </summary>
- /// <value>The route at the given index.</value>
- public IRouter this[int index]
- {
- get { return _routes[index]; }
- }
+ get { return _routes[index]; }
+ }
+
+ /// <summary>
+ /// Gets the total number of routes registered in the collection.
+ /// </summary>
+ public int Count
+ {
+ get { return _routes.Count; }
+ }
- /// <summary>
- /// Gets the total number of routes registered in the collection.
- /// </summary>
- public int Count
+ /// <inheritdoc />
+ public void Add(IRouter router)
+ {
+ if (router == null)
{
- get { return _routes.Count; }
+ throw new ArgumentNullException(nameof(router));
}
- /// <inheritdoc />
- public void Add(IRouter router)
+ var namedRouter = router as INamedRouter;
+ if (namedRouter != null)
{
- if (router == null)
+ if (!string.IsNullOrEmpty(namedRouter.Name))
{
- throw new ArgumentNullException(nameof(router));
+ _namedRoutes.Add(namedRouter.Name, namedRouter);
}
+ }
+ else
+ {
+ _unnamedRoutes.Add(router);
+ }
- var namedRouter = router as INamedRouter;
- if (namedRouter != null)
- {
- if (!string.IsNullOrEmpty(namedRouter.Name))
- {
- _namedRoutes.Add(namedRouter.Name, namedRouter);
- }
- }
- else
- {
- _unnamedRoutes.Add(router);
- }
+ _routes.Add(router);
+ }
- _routes.Add(router);
- }
+ /// <inheritdoc />
+ public virtual async Task RouteAsync(RouteContext context)
+ {
+ // Perf: We want to avoid allocating a new RouteData for each route we need to process.
+ // We can do this by snapshotting the state at the beginning and then restoring it
+ // for each router we execute.
+ var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);
- /// <inheritdoc />
- public virtual async Task RouteAsync(RouteContext context)
+ for (var i = 0; i < Count; i++)
{
- // Perf: We want to avoid allocating a new RouteData for each route we need to process.
- // We can do this by snapshotting the state at the beginning and then restoring it
- // for each router we execute.
- var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null);
+ var route = this[i];
+ context.RouteData.Routers.Add(route);
- for (var i = 0; i < Count; i++)
+ try
{
- var route = this[i];
- context.RouteData.Routers.Add(route);
+ await route.RouteAsync(context);
- try
+ if (context.Handler != null)
{
- await route.RouteAsync(context);
-
- if (context.Handler != null)
- {
- break;
- }
+ break;
}
- finally
+ }
+ finally
+ {
+ if (context.Handler == null)
{
- if (context.Handler == null)
- {
- snapshot.Restore();
- }
+ snapshot.Restore();
}
}
}
+ }
+
+ /// <inheritdoc />
+ public virtual VirtualPathData? GetVirtualPath(VirtualPathContext context)
+ {
+ EnsureOptions(context.HttpContext);
- /// <inheritdoc />
- public virtual VirtualPathData? GetVirtualPath(VirtualPathContext context)
+ if (!string.IsNullOrEmpty(context.RouteName))
{
- EnsureOptions(context.HttpContext);
+ VirtualPathData? namedRoutePathData = null;
- if (!string.IsNullOrEmpty(context.RouteName))
+ if (_namedRoutes.TryGetValue(context.RouteName, out var matchedNamedRoute))
{
- VirtualPathData? namedRoutePathData = null;
-
- if (_namedRoutes.TryGetValue(context.RouteName, out var matchedNamedRoute))
- {
- namedRoutePathData = matchedNamedRoute.GetVirtualPath(context);
- }
-
- var pathData = GetVirtualPath(context, _unnamedRoutes);
+ namedRoutePathData = matchedNamedRoute.GetVirtualPath(context);
+ }
- // If the named route and one of the unnamed routes also matches, then we have an ambiguity.
- if (namedRoutePathData != null && pathData != null)
- {
- var message = Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName);
- throw new InvalidOperationException(message);
- }
+ var pathData = GetVirtualPath(context, _unnamedRoutes);
- return NormalizeVirtualPath(namedRoutePathData ?? pathData);
- }
- else
+ // If the named route and one of the unnamed routes also matches, then we have an ambiguity.
+ if (namedRoutePathData != null && pathData != null)
{
- return NormalizeVirtualPath(GetVirtualPath(context, _routes));
+ var message = Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName);
+ throw new InvalidOperationException(message);
}
- }
- private static VirtualPathData? GetVirtualPath(VirtualPathContext context, List<IRouter> routes)
+ return NormalizeVirtualPath(namedRoutePathData ?? pathData);
+ }
+ else
{
- for (var i = 0; i < routes.Count; i++)
- {
- var route = routes[i];
-
- var pathData = route.GetVirtualPath(context);
- if (pathData != null)
- {
- return pathData;
- }
- }
-
- return null;
+ return NormalizeVirtualPath(GetVirtualPath(context, _routes));
}
+ }
- private VirtualPathData? NormalizeVirtualPath(VirtualPathData? pathData)
+ private static VirtualPathData? GetVirtualPath(VirtualPathContext context, List<IRouter> routes)
+ {
+ for (var i = 0; i < routes.Count; i++)
{
- if (pathData == null)
+ var route = routes[i];
+
+ var pathData = route.GetVirtualPath(context);
+ if (pathData != null)
{
return pathData;
}
+ }
- Debug.Assert(_options != null);
+ return null;
+ }
- var url = pathData.VirtualPath;
+ private VirtualPathData? NormalizeVirtualPath(VirtualPathData? pathData)
+ {
+ if (pathData == null)
+ {
+ return pathData;
+ }
- if (!string.IsNullOrEmpty(url) && (_options.LowercaseUrls || _options.AppendTrailingSlash))
- {
- var indexOfSeparator = url.IndexOfAny(UrlQueryDelimiters);
- var urlWithoutQueryString = url;
- var queryString = string.Empty;
+ Debug.Assert(_options != null);
- if (indexOfSeparator != -1)
- {
- urlWithoutQueryString = url.Substring(0, indexOfSeparator);
- queryString = url.Substring(indexOfSeparator);
- }
+ var url = pathData.VirtualPath;
- if (_options.LowercaseUrls)
- {
- urlWithoutQueryString = urlWithoutQueryString.ToLowerInvariant();
- }
+ if (!string.IsNullOrEmpty(url) && (_options.LowercaseUrls || _options.AppendTrailingSlash))
+ {
+ var indexOfSeparator = url.IndexOfAny(UrlQueryDelimiters);
+ var urlWithoutQueryString = url;
+ var queryString = string.Empty;
- if (_options.LowercaseUrls && _options.LowercaseQueryStrings)
- {
- queryString = queryString.ToLowerInvariant();
- }
+ if (indexOfSeparator != -1)
+ {
+ urlWithoutQueryString = url.Substring(0, indexOfSeparator);
+ queryString = url.Substring(indexOfSeparator);
+ }
- if (_options.AppendTrailingSlash && !urlWithoutQueryString.EndsWith("/", StringComparison.Ordinal))
- {
- urlWithoutQueryString += "/";
- }
+ if (_options.LowercaseUrls)
+ {
+ urlWithoutQueryString = urlWithoutQueryString.ToLowerInvariant();
+ }
- // queryString will contain the delimiter ? or # as the first character, so it's safe to append.
- url = urlWithoutQueryString + queryString;
+ if (_options.LowercaseUrls && _options.LowercaseQueryStrings)
+ {
+ queryString = queryString.ToLowerInvariant();
+ }
- return new VirtualPathData(pathData.Router, url, pathData.DataTokens);
+ if (_options.AppendTrailingSlash && !urlWithoutQueryString.EndsWith("/", StringComparison.Ordinal))
+ {
+ urlWithoutQueryString += "/";
}
- return pathData;
+ // queryString will contain the delimiter ? or # as the first character, so it's safe to append.
+ url = urlWithoutQueryString + queryString;
+
+ return new VirtualPathData(pathData.Router, url, pathData.DataTokens);
}
- [MemberNotNull(nameof(_options))]
- private void EnsureOptions(HttpContext context)
+ return pathData;
+ }
+
+ [MemberNotNull(nameof(_options))]
+ private void EnsureOptions(HttpContext context)
+ {
+ if (_options == null)
{
- if (_options == null)
- {
- _options = context.RequestServices.GetRequiredService<IOptions<RouteOptions>>().Value;
- }
+ _options = context.RequestServices.GetRequiredService<IOptions<RouteOptions>>().Value;
}
}
}
diff --git a/src/Http/Routing/src/RouteConstraintBuilder.cs b/src/Http/Routing/src/RouteConstraintBuilder.cs
index 287db69d6b..729ca07838 100644
--- a/src/Http/Routing/src/RouteConstraintBuilder.cs
+++ b/src/Http/Routing/src/RouteConstraintBuilder.cs
@@ -5,191 +5,190 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Constraints;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A builder for produding a mapping of keys to see <see cref="IRouteConstraint"/>.
+/// </summary>
+/// <remarks>
+/// <see cref="RouteConstraintBuilder"/> allows iterative building a set of route constraints, and will
+/// merge multiple entries for the same key.
+/// </remarks>
+public class RouteConstraintBuilder
{
+ private readonly IInlineConstraintResolver _inlineConstraintResolver;
+ private readonly string _displayName;
+
+ private readonly Dictionary<string, List<IRouteConstraint>> _constraints;
+ private readonly HashSet<string> _optionalParameters;
/// <summary>
- /// A builder for produding a mapping of keys to see <see cref="IRouteConstraint"/>.
+ /// Creates a new instance of <see cref="RouteConstraintBuilder"/> instance.
/// </summary>
- /// <remarks>
- /// <see cref="RouteConstraintBuilder"/> allows iterative building a set of route constraints, and will
- /// merge multiple entries for the same key.
- /// </remarks>
- public class RouteConstraintBuilder
+ /// <param name="inlineConstraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
+ /// <param name="displayName">The display name (for use in error messages).</param>
+ public RouteConstraintBuilder(
+ IInlineConstraintResolver inlineConstraintResolver,
+ string displayName)
{
- private readonly IInlineConstraintResolver _inlineConstraintResolver;
- private readonly string _displayName;
-
- private readonly Dictionary<string, List<IRouteConstraint>> _constraints;
- private readonly HashSet<string> _optionalParameters;
- /// <summary>
- /// Creates a new instance of <see cref="RouteConstraintBuilder"/> instance.
- /// </summary>
- /// <param name="inlineConstraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
- /// <param name="displayName">The display name (for use in error messages).</param>
- public RouteConstraintBuilder(
- IInlineConstraintResolver inlineConstraintResolver,
- string displayName)
+ if (inlineConstraintResolver == null)
{
- if (inlineConstraintResolver == null)
- {
- throw new ArgumentNullException(nameof(inlineConstraintResolver));
- }
+ throw new ArgumentNullException(nameof(inlineConstraintResolver));
+ }
- if (displayName == null)
- {
- throw new ArgumentNullException(nameof(displayName));
- }
+ if (displayName == null)
+ {
+ throw new ArgumentNullException(nameof(displayName));
+ }
- _inlineConstraintResolver = inlineConstraintResolver;
- _displayName = displayName;
+ _inlineConstraintResolver = inlineConstraintResolver;
+ _displayName = displayName;
- _constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
- _optionalParameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- }
+ _constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
+ _optionalParameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+ }
- /// <summary>
- /// Builds a mapping of constraints.
- /// </summary>
- /// <returns>An <see cref="IDictionary{String, IRouteConstraint}"/> of the constraints.</returns>
- public IDictionary<string, IRouteConstraint> Build()
+ /// <summary>
+ /// Builds a mapping of constraints.
+ /// </summary>
+ /// <returns>An <see cref="IDictionary{String, IRouteConstraint}"/> of the constraints.</returns>
+ public IDictionary<string, IRouteConstraint> Build()
+ {
+ var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase);
+ foreach (var kvp in _constraints)
{
- var constraints = new Dictionary<string, IRouteConstraint>(StringComparer.OrdinalIgnoreCase);
- foreach (var kvp in _constraints)
+ IRouteConstraint constraint;
+ if (kvp.Value.Count == 1)
{
- IRouteConstraint constraint;
- if (kvp.Value.Count == 1)
- {
- constraint = kvp.Value[0];
- }
- else
- {
- constraint = new CompositeRouteConstraint(kvp.Value.ToArray());
- }
-
- if (_optionalParameters.Contains(kvp.Key))
- {
- var optionalConstraint = new OptionalRouteConstraint(constraint);
- constraints.Add(kvp.Key, optionalConstraint);
- }
- else
- {
- constraints.Add(kvp.Key, constraint);
- }
+ constraint = kvp.Value[0];
}
-
- return constraints;
- }
-
- /// <summary>
- /// Adds a constraint instance for the given key.
- /// </summary>
- /// <param name="key">The key.</param>
- /// <param name="value">
- /// The constraint instance. Must either be a string or an instance of <see cref="IRouteConstraint"/>.
- /// </param>
- /// <remarks>
- /// If the <paramref name="value"/> is a string, it will be converted to a <see cref="RegexRouteConstraint"/>.
- ///
- /// For example, the string <c>Product[0-9]+</c> will be converted to the regular expression
- /// <c>^(Product[0-9]+)</c>. See <see cref="System.Text.RegularExpressions.Regex"/> for more details.
- /// </remarks>
- public void AddConstraint(string key, object value)
- {
- if (key == null)
+ else
{
- throw new ArgumentNullException(nameof(key));
+ constraint = new CompositeRouteConstraint(kvp.Value.ToArray());
}
- if (value == null)
+ if (_optionalParameters.Contains(kvp.Key))
{
- throw new ArgumentNullException(nameof(value));
+ var optionalConstraint = new OptionalRouteConstraint(constraint);
+ constraints.Add(kvp.Key, optionalConstraint);
}
-
- var constraint = value as IRouteConstraint;
- if (constraint == null)
+ else
{
- var regexPattern = value as string;
- if (regexPattern == null)
- {
- throw new RouteCreationException(
- Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
- key,
- value,
- _displayName,
- typeof(IRouteConstraint)));
- }
-
- var constraintsRegEx = "^(" + regexPattern + ")$";
- constraint = new RegexRouteConstraint(constraintsRegEx);
+ constraints.Add(kvp.Key, constraint);
}
-
- Add(key, constraint);
}
- /// <summary>
- /// Adds a constraint for the given key, resolved by the <see cref="IInlineConstraintResolver"/>.
- /// </summary>
- /// <param name="key">The key.</param>
- /// <param name="constraintText">The text to be resolved by <see cref="IInlineConstraintResolver"/>.</param>
- /// <remarks>
- /// The <see cref="IInlineConstraintResolver"/> can create <see cref="IRouteConstraint"/> instances
- /// based on <paramref name="constraintText"/>. See <see cref="RouteOptions.ConstraintMap"/> to register
- /// custom constraint types.
- /// </remarks>
- public void AddResolvedConstraint(string key, string constraintText)
+ return constraints;
+ }
+
+ /// <summary>
+ /// Adds a constraint instance for the given key.
+ /// </summary>
+ /// <param name="key">The key.</param>
+ /// <param name="value">
+ /// The constraint instance. Must either be a string or an instance of <see cref="IRouteConstraint"/>.
+ /// </param>
+ /// <remarks>
+ /// If the <paramref name="value"/> is a string, it will be converted to a <see cref="RegexRouteConstraint"/>.
+ ///
+ /// For example, the string <c>Product[0-9]+</c> will be converted to the regular expression
+ /// <c>^(Product[0-9]+)</c>. See <see cref="System.Text.RegularExpressions.Regex"/> for more details.
+ /// </remarks>
+ public void AddConstraint(string key, object value)
+ {
+ if (key == null)
{
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
+ throw new ArgumentNullException(nameof(key));
+ }
- if (constraintText == null)
- {
- throw new ArgumentNullException(nameof(constraintText));
- }
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
- var constraint = _inlineConstraintResolver.ResolveConstraint(constraintText);
- if (constraint == null)
+ var constraint = value as IRouteConstraint;
+ if (constraint == null)
+ {
+ var regexPattern = value as string;
+ if (regexPattern == null)
{
- throw new InvalidOperationException(
- Resources.FormatRouteConstraintBuilder_CouldNotResolveConstraint(
+ throw new RouteCreationException(
+ Resources.FormatRouteConstraintBuilder_ValidationMustBeStringOrCustomConstraint(
key,
- constraintText,
+ value,
_displayName,
- _inlineConstraintResolver.GetType().Name));
- }
- else if (constraint == NullRouteConstraint.Instance)
- {
- // A null route constraint can be returned for other parameter policy types
- return;
+ typeof(IRouteConstraint)));
}
- Add(key, constraint);
+ var constraintsRegEx = "^(" + regexPattern + ")$";
+ constraint = new RegexRouteConstraint(constraintsRegEx);
}
- /// <summary>
- /// Sets the given key as optional.
- /// </summary>
- /// <param name="key">The key.</param>
- public void SetOptional(string key)
+ Add(key, constraint);
+ }
+
+ /// <summary>
+ /// Adds a constraint for the given key, resolved by the <see cref="IInlineConstraintResolver"/>.
+ /// </summary>
+ /// <param name="key">The key.</param>
+ /// <param name="constraintText">The text to be resolved by <see cref="IInlineConstraintResolver"/>.</param>
+ /// <remarks>
+ /// The <see cref="IInlineConstraintResolver"/> can create <see cref="IRouteConstraint"/> instances
+ /// based on <paramref name="constraintText"/>. See <see cref="RouteOptions.ConstraintMap"/> to register
+ /// custom constraint types.
+ /// </remarks>
+ public void AddResolvedConstraint(string key, string constraintText)
+ {
+ if (key == null)
{
- if (key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
+ throw new ArgumentNullException(nameof(key));
+ }
- _optionalParameters.Add(key);
+ if (constraintText == null)
+ {
+ throw new ArgumentNullException(nameof(constraintText));
}
- private void Add(string key, IRouteConstraint constraint)
+ var constraint = _inlineConstraintResolver.ResolveConstraint(constraintText);
+ if (constraint == null)
{
- if (!_constraints.TryGetValue(key, out var list))
- {
- list = new List<IRouteConstraint>();
- _constraints.Add(key, list);
- }
+ throw new InvalidOperationException(
+ Resources.FormatRouteConstraintBuilder_CouldNotResolveConstraint(
+ key,
+ constraintText,
+ _displayName,
+ _inlineConstraintResolver.GetType().Name));
+ }
+ else if (constraint == NullRouteConstraint.Instance)
+ {
+ // A null route constraint can be returned for other parameter policy types
+ return;
+ }
+
+ Add(key, constraint);
+ }
+
+ /// <summary>
+ /// Sets the given key as optional.
+ /// </summary>
+ /// <param name="key">The key.</param>
+ public void SetOptional(string key)
+ {
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ _optionalParameters.Add(key);
+ }
- list.Add(constraint);
+ private void Add(string key, IRouteConstraint constraint)
+ {
+ if (!_constraints.TryGetValue(key, out var list))
+ {
+ list = new List<IRouteConstraint>();
+ _constraints.Add(key, list);
}
+
+ list.Add(constraint);
}
}
diff --git a/src/Http/Routing/src/RouteConstraintMatcher.cs b/src/Http/Routing/src/RouteConstraintMatcher.cs
index 41a2eaf18b..40d6dd2d71 100644
--- a/src/Http/Routing/src/RouteConstraintMatcher.cs
+++ b/src/Http/Routing/src/RouteConstraintMatcher.cs
@@ -6,88 +6,87 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Use to evaluate if all route parameter values match their constraints.
+/// </summary>
+public static partial class RouteConstraintMatcher
{
/// <summary>
- /// Use to evaluate if all route parameter values match their constraints.
+ /// Determines if <paramref name="routeValues"/> match the provided <paramref name="constraints"/>.
/// </summary>
- public static partial class RouteConstraintMatcher
+ /// <param name="constraints">The constraints for the route.</param>
+ /// <param name="routeValues">The route parameter values extracted from the matched route.</param>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="route">The router that this constraint belongs to.</param>
+ /// <param name="routeDirection">
+ /// Indicates whether the constraint check is performed
+ /// when the incoming request is handled or when a URL is generated.
+ /// </param>
+ /// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
+ /// <returns><see langword="true"/> if the all route values match their constraints.</returns>
+ public static bool Match(
+ IDictionary<string, IRouteConstraint> constraints,
+ RouteValueDictionary routeValues,
+ HttpContext httpContext,
+ IRouter route,
+ RouteDirection routeDirection,
+ ILogger logger)
{
- /// <summary>
- /// Determines if <paramref name="routeValues"/> match the provided <paramref name="constraints"/>.
- /// </summary>
- /// <param name="constraints">The constraints for the route.</param>
- /// <param name="routeValues">The route parameter values extracted from the matched route.</param>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="route">The router that this constraint belongs to.</param>
- /// <param name="routeDirection">
- /// Indicates whether the constraint check is performed
- /// when the incoming request is handled or when a URL is generated.
- /// </param>
- /// <param name="logger">The <see cref="ILogger{TCategoryName}"/>.</param>
- /// <returns><see langword="true"/> if the all route values match their constraints.</returns>
- public static bool Match(
- IDictionary<string, IRouteConstraint> constraints,
- RouteValueDictionary routeValues,
- HttpContext httpContext,
- IRouter route,
- RouteDirection routeDirection,
- ILogger logger)
+ if (routeValues == null)
{
- if (routeValues == null)
- {
- throw new ArgumentNullException(nameof(routeValues));
- }
+ throw new ArgumentNullException(nameof(routeValues));
+ }
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
- if (route == null)
- {
- throw new ArgumentNullException(nameof(route));
- }
+ if (route == null)
+ {
+ throw new ArgumentNullException(nameof(route));
+ }
- if (logger == null)
- {
- throw new ArgumentNullException(nameof(logger));
- }
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
- if (constraints == null || constraints.Count == 0)
- {
- return true;
- }
+ if (constraints == null || constraints.Count == 0)
+ {
+ return true;
+ }
- foreach (var kvp in constraints)
+ foreach (var kvp in constraints)
+ {
+ var constraint = kvp.Value;
+ if (!constraint.Match(httpContext, route, kvp.Key, routeValues, routeDirection))
{
- var constraint = kvp.Value;
- if (!constraint.Match(httpContext, route, kvp.Key, routeValues, routeDirection))
+ if (routeDirection.Equals(RouteDirection.IncomingRequest))
{
- if (routeDirection.Equals(RouteDirection.IncomingRequest))
- {
- routeValues.TryGetValue(kvp.Key, out var routeValue);
+ routeValues.TryGetValue(kvp.Key, out var routeValue);
- Log.ConstraintNotMatched(logger, routeValue!, kvp.Key, kvp.Value);
- }
-
- return false;
+ Log.ConstraintNotMatched(logger, routeValue!, kvp.Key, kvp.Value);
}
- }
- return true;
+ return false;
+ }
}
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Debug,
- "Route value '{RouteValue}' with key '{RouteKey}' did not match the constraint '{RouteConstraint}'",
- EventName = "ConstraintNotMatched")]
- public static partial void ConstraintNotMatched(
- ILogger logger,
- object routeValue,
- string routeKey,
- IRouteConstraint routeConstraint);
- }
+ return true;
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Debug,
+ "Route value '{RouteValue}' with key '{RouteKey}' did not match the constraint '{RouteConstraint}'",
+ EventName = "ConstraintNotMatched")]
+ public static partial void ConstraintNotMatched(
+ ILogger logger,
+ object routeValue,
+ string routeKey,
+ IRouteConstraint routeConstraint);
}
}
diff --git a/src/Http/Routing/src/RouteCreationException.cs b/src/Http/Routing/src/RouteCreationException.cs
index 14c79faad9..f17bdac29f 100644
--- a/src/Http/Routing/src/RouteCreationException.cs
+++ b/src/Http/Routing/src/RouteCreationException.cs
@@ -3,31 +3,30 @@
using System;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// The exception that is thrown for invalid routes or constraints.
+/// </summary>
+public class RouteCreationException : Exception
{
/// <summary>
- /// The exception that is thrown for invalid routes or constraints.
+ /// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message.
/// </summary>
- public class RouteCreationException : Exception
+ /// <param name="message">The message that describes the error.</param>
+ public RouteCreationException(string message)
+ : base(message)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message.
- /// </summary>
- /// <param name="message">The message that describes the error.</param>
- public RouteCreationException(string message)
- : base(message)
- {
- }
+ }
- /// <summary>
- /// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message
- /// and a reference to the inner exception that is the cause of this exception.
- /// </summary>
- /// <param name="message">The error message that explains the reason for the exception.</param>
- /// <param name="innerException">The exception that is the cause of the current exception.</param>
- public RouteCreationException(string message, Exception innerException)
- : base(message, innerException)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RouteCreationException"/> class with a specified error message
+ /// and a reference to the inner exception that is the cause of this exception.
+ /// </summary>
+ /// <param name="message">The error message that explains the reason for the exception.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception.</param>
+ public RouteCreationException(string message, Exception innerException)
+ : base(message, innerException)
+ {
}
}
diff --git a/src/Http/Routing/src/RouteEndpoint.cs b/src/Http/Routing/src/RouteEndpoint.cs
index 693f79ec15..1cde8a0843 100644
--- a/src/Http/Routing/src/RouteEndpoint.cs
+++ b/src/Http/Routing/src/RouteEndpoint.cs
@@ -7,57 +7,56 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents an <see cref="Endpoint"/> that can be used in URL matching or URL generation.
+/// </summary>
+public sealed class RouteEndpoint : Endpoint
{
/// <summary>
- /// Represents an <see cref="Endpoint"/> that can be used in URL matching or URL generation.
+ /// Initializes a new instance of the <see cref="RouteEndpoint"/> class.
/// </summary>
- public sealed class RouteEndpoint : Endpoint
+ /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
+ /// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
+ /// <param name="order">The order assigned to the endpoint.</param>
+ /// <param name="metadata">
+ /// The <see cref="EndpointMetadataCollection"/> or metadata associated with the endpoint.
+ /// </param>
+ /// <param name="displayName">The informational display name of the endpoint.</param>
+ public RouteEndpoint(
+ RequestDelegate requestDelegate,
+ RoutePattern routePattern,
+ int order,
+ EndpointMetadataCollection? metadata,
+ string? displayName)
+ : base(requestDelegate, metadata, displayName)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="RouteEndpoint"/> class.
- /// </summary>
- /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
- /// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
- /// <param name="order">The order assigned to the endpoint.</param>
- /// <param name="metadata">
- /// The <see cref="EndpointMetadataCollection"/> or metadata associated with the endpoint.
- /// </param>
- /// <param name="displayName">The informational display name of the endpoint.</param>
- public RouteEndpoint(
- RequestDelegate requestDelegate,
- RoutePattern routePattern,
- int order,
- EndpointMetadataCollection? metadata,
- string? displayName)
- : base(requestDelegate, metadata, displayName)
+ if (requestDelegate == null)
{
- if (requestDelegate == null)
- {
- throw new ArgumentNullException(nameof(requestDelegate));
- }
-
- if (routePattern == null)
- {
- throw new ArgumentNullException(nameof(routePattern));
- }
-
- RoutePattern = routePattern;
- Order = order;
+ throw new ArgumentNullException(nameof(requestDelegate));
}
- /// <summary>
- /// Gets the order value of endpoint.
- /// </summary>
- /// <remarks>
- /// The order value provides absolute control over the priority
- /// of an endpoint. Endpoints with a lower numeric value of order have higher priority.
- /// </remarks>
- public int Order { get; }
+ if (routePattern == null)
+ {
+ throw new ArgumentNullException(nameof(routePattern));
+ }
- /// <summary>
- /// Gets the <see cref="RoutePattern"/> associated with the endpoint.
- /// </summary>
- public RoutePattern RoutePattern { get; }
+ RoutePattern = routePattern;
+ Order = order;
}
+
+ /// <summary>
+ /// Gets the order value of endpoint.
+ /// </summary>
+ /// <remarks>
+ /// The order value provides absolute control over the priority
+ /// of an endpoint. Endpoints with a lower numeric value of order have higher priority.
+ /// </remarks>
+ public int Order { get; }
+
+ /// <summary>
+ /// Gets the <see cref="RoutePattern"/> associated with the endpoint.
+ /// </summary>
+ public RoutePattern RoutePattern { get; }
}
diff --git a/src/Http/Routing/src/RouteEndpointBuilder.cs b/src/Http/Routing/src/RouteEndpointBuilder.cs
index 73dcc9cf79..be8cc2cb97 100644
--- a/src/Http/Routing/src/RouteEndpointBuilder.cs
+++ b/src/Http/Routing/src/RouteEndpointBuilder.cs
@@ -6,55 +6,54 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Supports building a new <see cref="RouteEndpoint"/>.
+/// </summary>
+public sealed class RouteEndpointBuilder : EndpointBuilder
{
/// <summary>
- /// Supports building a new <see cref="RouteEndpoint"/>.
+ /// Gets or sets the <see cref="RoutePattern"/> associated with this endpoint.
+ /// </summary>
+ public RoutePattern RoutePattern { get; set; }
+
+ /// <summary>
+ /// Gets or sets the order assigned to the endpoint.
+ /// </summary>
+ public int Order { get; set; }
+
+ /// <summary>
+ /// Constructs a new <see cref="RouteEndpointBuilder"/> instance.
/// </summary>
- public sealed class RouteEndpointBuilder : EndpointBuilder
+ /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
+ /// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
+ /// <param name="order">The order assigned to the endpoint.</param>
+ public RouteEndpointBuilder(
+ RequestDelegate requestDelegate,
+ RoutePattern routePattern,
+ int order)
{
- /// <summary>
- /// Gets or sets the <see cref="RoutePattern"/> associated with this endpoint.
- /// </summary>
- public RoutePattern RoutePattern { get; set; }
-
- /// <summary>
- /// Gets or sets the order assigned to the endpoint.
- /// </summary>
- public int Order { get; set; }
-
- /// <summary>
- /// Constructs a new <see cref="RouteEndpointBuilder"/> instance.
- /// </summary>
- /// <param name="requestDelegate">The delegate used to process requests for the endpoint.</param>
- /// <param name="routePattern">The <see cref="RoutePattern"/> to use in URL matching.</param>
- /// <param name="order">The order assigned to the endpoint.</param>
- public RouteEndpointBuilder(
- RequestDelegate requestDelegate,
- RoutePattern routePattern,
- int order)
- {
- RequestDelegate = requestDelegate;
- RoutePattern = routePattern;
- Order = order;
- }
+ RequestDelegate = requestDelegate;
+ RoutePattern = routePattern;
+ Order = order;
+ }
- /// <inheritdoc />
- public override Endpoint Build()
+ /// <inheritdoc />
+ public override Endpoint Build()
+ {
+ if (RequestDelegate is null)
{
- if (RequestDelegate is null)
- {
- throw new InvalidOperationException($"{nameof(RequestDelegate)} must be specified to construct a {nameof(RouteEndpoint)}.");
- }
-
- var routeEndpoint = new RouteEndpoint(
- RequestDelegate,
- RoutePattern,
- Order,
- new EndpointMetadataCollection(Metadata),
- DisplayName);
-
- return routeEndpoint;
+ throw new InvalidOperationException($"{nameof(RequestDelegate)} must be specified to construct a {nameof(RouteEndpoint)}.");
}
+
+ var routeEndpoint = new RouteEndpoint(
+ RequestDelegate,
+ RoutePattern,
+ Order,
+ new EndpointMetadataCollection(Metadata),
+ DisplayName);
+
+ return routeEndpoint;
}
}
diff --git a/src/Http/Routing/src/RouteHandler.cs b/src/Http/Routing/src/RouteHandler.cs
index a255ff99a0..3849ca0ae2 100644
--- a/src/Http/Routing/src/RouteHandler.cs
+++ b/src/Http/Routing/src/RouteHandler.cs
@@ -6,42 +6,41 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Supports implementing a handler that executes for a given route.
+/// </summary>
+public class RouteHandler : IRouteHandler, IRouter
{
+ private readonly RequestDelegate _requestDelegate;
+
/// <summary>
- /// Supports implementing a handler that executes for a given route.
+ /// Constructs a new <see cref="RouteHandler"/> instance.
/// </summary>
- public class RouteHandler : IRouteHandler, IRouter
+ /// <param name="requestDelegate">The delegate used to process requests.</param>
+ public RouteHandler(RequestDelegate requestDelegate)
+ {
+ _requestDelegate = requestDelegate;
+ }
+
+ /// <inheritdoc />
+ public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
+ {
+ return _requestDelegate;
+ }
+
+ /// <inheritdoc />
+ public VirtualPathData? GetVirtualPath(VirtualPathContext context)
+ {
+ // Nothing to do.
+ return null;
+ }
+
+ /// <inheritdoc />
+ public Task RouteAsync(RouteContext context)
{
- private readonly RequestDelegate _requestDelegate;
-
- /// <summary>
- /// Constructs a new <see cref="RouteHandler"/> instance.
- /// </summary>
- /// <param name="requestDelegate">The delegate used to process requests.</param>
- public RouteHandler(RequestDelegate requestDelegate)
- {
- _requestDelegate = requestDelegate;
- }
-
- /// <inheritdoc />
- public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
- {
- return _requestDelegate;
- }
-
- /// <inheritdoc />
- public VirtualPathData? GetVirtualPath(VirtualPathContext context)
- {
- // Nothing to do.
- return null;
- }
-
- /// <inheritdoc />
- public Task RouteAsync(RouteContext context)
- {
- context.Handler = _requestDelegate;
- return Task.CompletedTask;
- }
+ context.Handler = _requestDelegate;
+ return Task.CompletedTask;
}
}
diff --git a/src/Http/Routing/src/RouteHandlerOptions.cs b/src/Http/Routing/src/RouteHandlerOptions.cs
index b16f6db6f2..4fb2106e17 100644
--- a/src/Http/Routing/src/RouteHandlerOptions.cs
+++ b/src/Http/Routing/src/RouteHandlerOptions.cs
@@ -6,21 +6,20 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Options for controlling the behavior of <see cref="EndpointRouteBuilderExtensions.MapGet(IEndpointRouteBuilder, string, Delegate)"/>
+/// and similar methods.
+/// </summary>
+public sealed class RouteHandlerOptions
{
/// <summary>
- /// Options for controlling the behavior of <see cref="EndpointRouteBuilderExtensions.MapGet(IEndpointRouteBuilder, string, Delegate)"/>
- /// and similar methods.
+ /// Controls whether endpoints should throw a <see cref="BadHttpRequestException"/> in addition to
+ /// writing a <see cref="LogLevel.Debug"/> log when handling invalid requests.
/// </summary>
- public sealed class RouteHandlerOptions
- {
- /// <summary>
- /// Controls whether endpoints should throw a <see cref="BadHttpRequestException"/> in addition to
- /// writing a <see cref="LogLevel.Debug"/> log when handling invalid requests.
- /// </summary>
- /// <remarks>
- /// Defaults to <see cref="HostEnvironmentEnvExtensions.IsDevelopment(IHostEnvironment)"/>.
- /// </remarks>
- public bool ThrowOnBadRequest { get; set; }
- }
+ /// <remarks>
+ /// Defaults to <see cref="HostEnvironmentEnvExtensions.IsDevelopment(IHostEnvironment)"/>.
+ /// </remarks>
+ public bool ThrowOnBadRequest { get; set; }
}
diff --git a/src/Http/Routing/src/RouteNameMetadata.cs b/src/Http/Routing/src/RouteNameMetadata.cs
index e60b98406a..9f7a1f9470 100644
--- a/src/Http/Routing/src/RouteNameMetadata.cs
+++ b/src/Http/Routing/src/RouteNameMetadata.cs
@@ -4,31 +4,30 @@
using System;
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Metadata used during link generation to find the associated endpoint using route name.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+public sealed class RouteNameMetadata : IRouteNameMetadata
{
/// <summary>
- /// Metadata used during link generation to find the associated endpoint using route name.
+ /// Creates a new instance of <see cref="RouteNameMetadata"/> with the provided route name.
/// </summary>
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- public sealed class RouteNameMetadata : IRouteNameMetadata
+ /// <param name="routeName">The route name. Can be <see langword="null"/>.</param>
+ public RouteNameMetadata(string? routeName)
{
- /// <summary>
- /// Creates a new instance of <see cref="RouteNameMetadata"/> with the provided route name.
- /// </summary>
- /// <param name="routeName">The route name. Can be <see langword="null"/>.</param>
- public RouteNameMetadata(string? routeName)
- {
- RouteName = routeName;
- }
+ RouteName = routeName;
+ }
- /// <summary>
- /// Gets the route name. Can be <see langword="null"/>.
- /// </summary>
- public string? RouteName { get; }
+ /// <summary>
+ /// Gets the route name. Can be <see langword="null"/>.
+ /// </summary>
+ public string? RouteName { get; }
- internal string DebuggerToString()
- {
- return $"Name: {RouteName}";
- }
+ internal string DebuggerToString()
+ {
+ return $"Name: {RouteName}";
}
}
diff --git a/src/Http/Routing/src/RouteOptions.cs b/src/Http/Routing/src/RouteOptions.cs
index 1a2e81f66b..48f691d527 100644
--- a/src/Http/Routing/src/RouteOptions.cs
+++ b/src/Http/Routing/src/RouteOptions.cs
@@ -7,153 +7,152 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Routing.Constraints;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents the configurable options on a route.
+/// </summary>
+public class RouteOptions
{
+ private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
+ private ICollection<EndpointDataSource> _endpointDataSources = default!;
+
/// <summary>
- /// Represents the configurable options on a route.
+ /// Gets a collection of <see cref="EndpointDataSource"/> instances configured with routing.
/// </summary>
- public class RouteOptions
+ internal ICollection<EndpointDataSource> EndpointDataSources
{
- private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
- private ICollection<EndpointDataSource> _endpointDataSources = default!;
-
- /// <summary>
- /// Gets a collection of <see cref="EndpointDataSource"/> instances configured with routing.
- /// </summary>
- internal ICollection<EndpointDataSource> EndpointDataSources
+ get
{
- get
- {
- Debug.Assert(_endpointDataSources != null, "Endpoint data sources should have been set in DI.");
- return _endpointDataSources;
- }
- set => _endpointDataSources = value;
+ Debug.Assert(_endpointDataSources != null, "Endpoint data sources should have been set in DI.");
+ return _endpointDataSources;
}
+ set => _endpointDataSources = value;
+ }
- /// <summary>
- /// Gets or sets a value indicating whether all generated paths URLs are lowercase.
- /// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
- /// </summary>
- public bool LowercaseUrls { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether a generated query strings are lowercase.
- /// This property will not be used unless <see cref="LowercaseUrls" /> is also <c>true</c>.
- /// </summary>
- public bool LowercaseQueryStrings { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
- /// </summary>
- public bool AppendTrailingSlash { get; set; }
-
- /// <summary>
- /// Gets or sets a value that indicates if the check for unhandled security endpoint metadata is suppressed.
- /// <para>
- /// Endpoints can be associated with metadata such as authorization, or CORS, that needs to be
- /// handled by a specific middleware to be actionable. If the middleware is not configured, such
- /// metadata will go unhandled.
- /// </para>
- /// <para>
- /// When <see langword="false"/>, prior to the execution of the endpoint, routing will verify that
- /// all known security-specific metadata has been handled.
- /// Setting this property to <see langword="true"/> suppresses this check.
- /// </para>
- /// </summary>
- /// <value>Defaults to <see langword="false"/>.</value>
- /// <remarks>
- /// This check exists as a safeguard against accidental insecure configuration. You may suppress
- /// this check if it does not match your application's requirements.
- /// </remarks>
- public bool SuppressCheckForUnhandledSecurityMetadata { get; set; }
-
- /// <summary>
- /// Gets or sets a collection of constraints on the current route.
- /// </summary>
- public IDictionary<string, Type> ConstraintMap
- {
- [RequiresUnreferencedCode($"The linker cannot determine what constraints are being added via the ConstraintMap property. Prefer {nameof(RouteOptions)}.{nameof(SetParameterPolicy)} instead for setting constraints. This warning can be suppressed if this property is being used to read of delete constraints.")]
- get
- {
- return _constraintTypeMap;
- }
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(ConstraintMap));
- }
+ /// <summary>
+ /// Gets or sets a value indicating whether all generated paths URLs are lowercase.
+ /// Use <see cref="LowercaseQueryStrings" /> to configure the behavior for query strings.
+ /// </summary>
+ public bool LowercaseUrls { get; set; }
- _constraintTypeMap = value;
- }
- }
+ /// <summary>
+ /// Gets or sets a value indicating whether a generated query strings are lowercase.
+ /// This property will not be used unless <see cref="LowercaseUrls" /> is also <c>true</c>.
+ /// </summary>
+ public bool LowercaseQueryStrings { get; set; }
- private static IDictionary<string, Type> GetDefaultConstraintMap()
- {
- var defaults = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
-
- // Type-specific constraints
- AddConstraint<IntRouteConstraint>(defaults, "int");
- AddConstraint<BoolRouteConstraint>(defaults, "bool");
- AddConstraint<DateTimeRouteConstraint>(defaults, "datetime");
- AddConstraint<DecimalRouteConstraint>(defaults, "decimal");
- AddConstraint<DoubleRouteConstraint>(defaults, "double");
- AddConstraint<FloatRouteConstraint>(defaults, "float");
- AddConstraint<GuidRouteConstraint>(defaults, "guid");
- AddConstraint<LongRouteConstraint>(defaults, "long");
-
- // Length constraints
- AddConstraint<MinLengthRouteConstraint>(defaults, "minlength");
- AddConstraint<MaxLengthRouteConstraint>(defaults, "maxlength");
- AddConstraint<LengthRouteConstraint>(defaults, "length");
-
- // Min/Max value constraints
- AddConstraint<MinRouteConstraint>(defaults, "min");
- AddConstraint<MaxRouteConstraint>(defaults, "max");
- AddConstraint<RangeRouteConstraint>(defaults, "range");
-
- // Regex-based constraints
- AddConstraint<AlphaRouteConstraint>(defaults, "alpha");
- AddConstraint<RegexInlineRouteConstraint>(defaults, "regex");
-
- AddConstraint<RequiredRouteConstraint>(defaults, "required");
-
- // Files
- AddConstraint<FileNameRouteConstraint>(defaults, "file");
- AddConstraint<NonFileNameRouteConstraint>(defaults, "nonfile");
-
- return defaults;
- }
+ /// <summary>
+ /// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
+ /// </summary>
+ public bool AppendTrailingSlash { get; set; }
- /// <summary>
- /// Adds or overwrites the parameter policy with the associated route pattern token.
- /// </summary>
- /// <typeparam name="T">The parameter policy type.</typeparam>
- /// <param name="token">The route token used to apply the parameter policy.</param>
- public void SetParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]T>(string token) where T : IParameterPolicy
+ /// <summary>
+ /// Gets or sets a value that indicates if the check for unhandled security endpoint metadata is suppressed.
+ /// <para>
+ /// Endpoints can be associated with metadata such as authorization, or CORS, that needs to be
+ /// handled by a specific middleware to be actionable. If the middleware is not configured, such
+ /// metadata will go unhandled.
+ /// </para>
+ /// <para>
+ /// When <see langword="false"/>, prior to the execution of the endpoint, routing will verify that
+ /// all known security-specific metadata has been handled.
+ /// Setting this property to <see langword="true"/> suppresses this check.
+ /// </para>
+ /// </summary>
+ /// <value>Defaults to <see langword="false"/>.</value>
+ /// <remarks>
+ /// This check exists as a safeguard against accidental insecure configuration. You may suppress
+ /// this check if it does not match your application's requirements.
+ /// </remarks>
+ public bool SuppressCheckForUnhandledSecurityMetadata { get; set; }
+
+ /// <summary>
+ /// Gets or sets a collection of constraints on the current route.
+ /// </summary>
+ public IDictionary<string, Type> ConstraintMap
+ {
+ [RequiresUnreferencedCode($"The linker cannot determine what constraints are being added via the ConstraintMap property. Prefer {nameof(RouteOptions)}.{nameof(SetParameterPolicy)} instead for setting constraints. This warning can be suppressed if this property is being used to read of delete constraints.")]
+ get
{
- ConstraintMap[token] = typeof(T);
+ return _constraintTypeMap;
}
-
- /// <summary>
- /// Adds or overwrites the parameter policy with the associated route pattern token.
- /// </summary>
- /// <param name="token">The route token used to apply the parameter policy.</param>
- /// <param name="type">The parameter policy type.</param>
- /// <exception cref="InvalidOperationException">Throws an exception if the type is not an <see cref="IParameterPolicy"/>.</exception>
- public void SetParameterPolicy(string token, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
+ set
{
- if (!type.IsAssignableTo(typeof(IParameterPolicy)))
+ if (value == null)
{
- throw new InvalidOperationException($"{type} must implement {typeof(IParameterPolicy)}");
+ throw new ArgumentNullException(nameof(ConstraintMap));
}
- ConstraintMap[token] = type;
+ _constraintTypeMap = value;
}
+ }
+
+ private static IDictionary<string, Type> GetDefaultConstraintMap()
+ {
+ var defaults = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
+
+ // Type-specific constraints
+ AddConstraint<IntRouteConstraint>(defaults, "int");
+ AddConstraint<BoolRouteConstraint>(defaults, "bool");
+ AddConstraint<DateTimeRouteConstraint>(defaults, "datetime");
+ AddConstraint<DecimalRouteConstraint>(defaults, "decimal");
+ AddConstraint<DoubleRouteConstraint>(defaults, "double");
+ AddConstraint<FloatRouteConstraint>(defaults, "float");
+ AddConstraint<GuidRouteConstraint>(defaults, "guid");
+ AddConstraint<LongRouteConstraint>(defaults, "long");
+
+ // Length constraints
+ AddConstraint<MinLengthRouteConstraint>(defaults, "minlength");
+ AddConstraint<MaxLengthRouteConstraint>(defaults, "maxlength");
+ AddConstraint<LengthRouteConstraint>(defaults, "length");
+
+ // Min/Max value constraints
+ AddConstraint<MinRouteConstraint>(defaults, "min");
+ AddConstraint<MaxRouteConstraint>(defaults, "max");
+ AddConstraint<RangeRouteConstraint>(defaults, "range");
+
+ // Regex-based constraints
+ AddConstraint<AlphaRouteConstraint>(defaults, "alpha");
+ AddConstraint<RegexInlineRouteConstraint>(defaults, "regex");
+
+ AddConstraint<RequiredRouteConstraint>(defaults, "required");
+
+ // Files
+ AddConstraint<FileNameRouteConstraint>(defaults, "file");
+ AddConstraint<NonFileNameRouteConstraint>(defaults, "nonfile");
+
+ return defaults;
+ }
- private static void AddConstraint<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TConstraint>(Dictionary<string, Type> constraintMap, string text) where TConstraint : IRouteConstraint
+ /// <summary>
+ /// Adds or overwrites the parameter policy with the associated route pattern token.
+ /// </summary>
+ /// <typeparam name="T">The parameter policy type.</typeparam>
+ /// <param name="token">The route token used to apply the parameter policy.</param>
+ public void SetParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]T>(string token) where T : IParameterPolicy
+ {
+ ConstraintMap[token] = typeof(T);
+ }
+
+ /// <summary>
+ /// Adds or overwrites the parameter policy with the associated route pattern token.
+ /// </summary>
+ /// <param name="token">The route token used to apply the parameter policy.</param>
+ /// <param name="type">The parameter policy type.</param>
+ /// <exception cref="InvalidOperationException">Throws an exception if the type is not an <see cref="IParameterPolicy"/>.</exception>
+ public void SetParameterPolicy(string token, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
+ {
+ if (!type.IsAssignableTo(typeof(IParameterPolicy)))
{
- constraintMap[text] = typeof(TConstraint);
+ throw new InvalidOperationException($"{type} must implement {typeof(IParameterPolicy)}");
}
+
+ ConstraintMap[token] = type;
+ }
+
+ private static void AddConstraint<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TConstraint>(Dictionary<string, Type> constraintMap, string text) where TConstraint : IRouteConstraint
+ {
+ constraintMap[text] = typeof(TConstraint);
}
}
diff --git a/src/Http/Routing/src/RouteValueEqualityComparer.cs b/src/Http/Routing/src/RouteValueEqualityComparer.cs
index f38980ddbe..3960d2329d 100644
--- a/src/Http/Routing/src/RouteValueEqualityComparer.cs
+++ b/src/Http/Routing/src/RouteValueEqualityComparer.cs
@@ -5,54 +5,53 @@ using System;
using System.Collections.Generic;
using System.Globalization;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// An <see cref="IEqualityComparer{Object}"/> implementation that compares objects as-if
+/// they were route value strings.
+/// </summary>
+/// <remarks>
+/// Values that are are not strings are converted to strings using
+/// <c>Convert.ToString(x, CultureInfo.InvariantCulture)</c>. <c>null</c> values are converted
+/// to the empty string.
+///
+/// strings are compared using <see cref="StringComparison.OrdinalIgnoreCase"/>.
+/// </remarks>
+public class RouteValueEqualityComparer : IEqualityComparer<object?>
{
/// <summary>
- /// An <see cref="IEqualityComparer{Object}"/> implementation that compares objects as-if
- /// they were route value strings.
+ /// A default instance of the <see cref="RouteValueEqualityComparer"/>.
/// </summary>
- /// <remarks>
- /// Values that are are not strings are converted to strings using
- /// <c>Convert.ToString(x, CultureInfo.InvariantCulture)</c>. <c>null</c> values are converted
- /// to the empty string.
- ///
- /// strings are compared using <see cref="StringComparison.OrdinalIgnoreCase"/>.
- /// </remarks>
- public class RouteValueEqualityComparer : IEqualityComparer<object?>
+ public static readonly RouteValueEqualityComparer Default = new RouteValueEqualityComparer();
+
+ /// <inheritdoc />
+ public new bool Equals(object? x, object? y)
{
- /// <summary>
- /// A default instance of the <see cref="RouteValueEqualityComparer"/>.
- /// </summary>
- public static readonly RouteValueEqualityComparer Default = new RouteValueEqualityComparer();
+ var stringX = x as string ?? Convert.ToString(x, CultureInfo.InvariantCulture);
+ var stringY = y as string ?? Convert.ToString(y, CultureInfo.InvariantCulture);
- /// <inheritdoc />
- public new bool Equals(object? x, object? y)
+ if (string.IsNullOrEmpty(stringX) && string.IsNullOrEmpty(stringY))
{
- var stringX = x as string ?? Convert.ToString(x, CultureInfo.InvariantCulture);
- var stringY = y as string ?? Convert.ToString(y, CultureInfo.InvariantCulture);
-
- if (string.IsNullOrEmpty(stringX) && string.IsNullOrEmpty(stringY))
- {
- return true;
- }
- else
- {
- return string.Equals(stringX, stringY, StringComparison.OrdinalIgnoreCase);
- }
+ return true;
+ }
+ else
+ {
+ return string.Equals(stringX, stringY, StringComparison.OrdinalIgnoreCase);
}
+ }
- /// <inheritdoc />
- public int GetHashCode(object obj)
+ /// <inheritdoc />
+ public int GetHashCode(object obj)
+ {
+ var stringObj = obj as string ?? Convert.ToString(obj, CultureInfo.InvariantCulture);
+ if (string.IsNullOrEmpty(stringObj))
+ {
+ return StringComparer.OrdinalIgnoreCase.GetHashCode(string.Empty);
+ }
+ else
{
- var stringObj = obj as string ?? Convert.ToString(obj, CultureInfo.InvariantCulture);
- if (string.IsNullOrEmpty(stringObj))
- {
- return StringComparer.OrdinalIgnoreCase.GetHashCode(string.Empty);
- }
- else
- {
- return StringComparer.OrdinalIgnoreCase.GetHashCode(stringObj);
- }
+ return StringComparer.OrdinalIgnoreCase.GetHashCode(stringObj);
}
}
}
diff --git a/src/Http/Routing/src/RouteValuesAddress.cs b/src/Http/Routing/src/RouteValuesAddress.cs
index 54ce175c47..23ed96b10b 100644
--- a/src/Http/Routing/src/RouteValuesAddress.cs
+++ b/src/Http/Routing/src/RouteValuesAddress.cs
@@ -3,26 +3,25 @@
#nullable enable
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// An address of route name and values.
+/// </summary>
+public class RouteValuesAddress
{
/// <summary>
- /// An address of route name and values.
+ /// Gets or sets the route name.
/// </summary>
- public class RouteValuesAddress
- {
- /// <summary>
- /// Gets or sets the route name.
- /// </summary>
- public string? RouteName { get; set; }
+ public string? RouteName { get; set; }
- /// <summary>
- /// Gets or sets the route values that are explicitly specified.
- /// </summary>
- public RouteValueDictionary ExplicitValues { get; set; } = default!;
+ /// <summary>
+ /// Gets or sets the route values that are explicitly specified.
+ /// </summary>
+ public RouteValueDictionary ExplicitValues { get; set; } = default!;
- /// <summary>
- /// Gets or sets ambient route values from the current HTTP request.
- /// </summary>
- public RouteValueDictionary? AmbientValues { get; set; }
- }
+ /// <summary>
+ /// Gets or sets ambient route values from the current HTTP request.
+ /// </summary>
+ public RouteValueDictionary? AmbientValues { get; set; }
}
diff --git a/src/Http/Routing/src/RouteValuesAddressScheme.cs b/src/Http/Routing/src/RouteValuesAddressScheme.cs
index e9e7db70fe..12043f677b 100644
--- a/src/Http/Routing/src/RouteValuesAddressScheme.cs
+++ b/src/Http/Routing/src/RouteValuesAddressScheme.cs
@@ -7,186 +7,185 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.Routing.Tree;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal sealed class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress>, IDisposable
{
- internal sealed class RouteValuesAddressScheme : IEndpointAddressScheme<RouteValuesAddress>, IDisposable
+ private readonly DataSourceDependentCache<StateEntry> _cache;
+
+ public RouteValuesAddressScheme(EndpointDataSource dataSource)
{
- private readonly DataSourceDependentCache<StateEntry> _cache;
+ _cache = new DataSourceDependentCache<StateEntry>(dataSource, Initialize);
+ }
+
+ // Internal for tests
+ internal StateEntry State => _cache.EnsureInitialized();
- public RouteValuesAddressScheme(EndpointDataSource dataSource)
+ public IEnumerable<Endpoint> FindEndpoints(RouteValuesAddress address)
+ {
+ if (address == null)
{
- _cache = new DataSourceDependentCache<StateEntry>(dataSource, Initialize);
+ throw new ArgumentNullException(nameof(address));
}
- // Internal for tests
- internal StateEntry State => _cache.EnsureInitialized();
+ var state = State;
- public IEnumerable<Endpoint> FindEndpoints(RouteValuesAddress address)
+ IList<OutboundMatchResult>? matchResults = null;
+ if (string.IsNullOrEmpty(address.RouteName))
{
- if (address == null)
- {
- throw new ArgumentNullException(nameof(address));
- }
-
- var state = State;
-
- IList<OutboundMatchResult>? matchResults = null;
- if (string.IsNullOrEmpty(address.RouteName))
- {
- matchResults = state.AllMatchesLinkGenerationTree.GetMatches(
- address.ExplicitValues,
- address.AmbientValues);
- }
- else if (state.NamedMatches.TryGetValue(address.RouteName, out var namedMatchResults))
- {
- matchResults = namedMatchResults;
- }
+ matchResults = state.AllMatchesLinkGenerationTree.GetMatches(
+ address.ExplicitValues,
+ address.AmbientValues);
+ }
+ else if (state.NamedMatches.TryGetValue(address.RouteName, out var namedMatchResults))
+ {
+ matchResults = namedMatchResults;
+ }
- if (matchResults != null)
+ if (matchResults != null)
+ {
+ var matchCount = matchResults.Count;
+ if (matchCount > 0)
{
- var matchCount = matchResults.Count;
- if (matchCount > 0)
+ if (matchResults.Count == 1)
+ {
+ // Special case having a single result to avoid creating iterator state machine
+ return new[] { (RouteEndpoint)matchResults[0].Match.Entry.Data };
+ }
+ else
{
- if (matchResults.Count == 1)
- {
- // Special case having a single result to avoid creating iterator state machine
- return new[] { (RouteEndpoint)matchResults[0].Match.Entry.Data };
- }
- else
- {
- // Use separate method since one cannot have regular returns in an iterator method
- return GetEndpoints(matchResults, matchCount);
- }
+ // Use separate method since one cannot have regular returns in an iterator method
+ return GetEndpoints(matchResults, matchCount);
}
}
-
- return Array.Empty<Endpoint>();
}
- private static IEnumerable<Endpoint> GetEndpoints(IList<OutboundMatchResult> matchResults, int matchCount)
+ return Array.Empty<Endpoint>();
+ }
+
+ private static IEnumerable<Endpoint> GetEndpoints(IList<OutboundMatchResult> matchResults, int matchCount)
+ {
+ for (var i = 0; i < matchCount; i++)
{
- for (var i = 0; i < matchCount; i++)
- {
- yield return (RouteEndpoint)matchResults[i].Match.Entry.Data;
- }
+ yield return (RouteEndpoint)matchResults[i].Match.Entry.Data;
}
+ }
- private StateEntry Initialize(IReadOnlyList<Endpoint> endpoints)
+ private StateEntry Initialize(IReadOnlyList<Endpoint> endpoints)
+ {
+ var matchesWithRequiredValues = new List<OutboundMatch>();
+ var namedOutboundMatchResults = new Dictionary<string, List<OutboundMatchResult>>(StringComparer.OrdinalIgnoreCase);
+
+ // Decision tree is built using the 'required values' of actions.
+ // - When generating a url using route values, decision tree checks the explicitly supplied route values +
+ // ambient values to see if they have a match for the required-values-based-tree.
+ // - When generating a url using route name, route values for controller, action etc.might not be provided
+ // (this is expected because as a user I want to avoid writing all those and instead chose to use a
+ // routename which is quick). So since these values are not provided and might not be even in ambient
+ // values, decision tree would fail to find a match. So for this reason decision tree is not used for named
+ // matches. Instead all named matches are returned as is and the LinkGenerator uses a TemplateBinder to
+ // decide which of the matches can generate a url.
+ // For example, for a route defined like below with current ambient values like new { controller = "Home",
+ // action = "Index" }
+ // "api/orders/{id}",
+ // routeName: "OrdersApi",
+ // defaults: new { controller = "Orders", action = "GetById" },
+ // requiredValues: new { controller = "Orders", action = "GetById" },
+ // A call to GetLink("OrdersApi", new { id = "10" }) cannot generate url as neither the supplied values or
+ // current ambient values do not satisfy the decision tree that is built based on the required values.
+ for (var i = 0; i < endpoints.Count; i++)
{
- var matchesWithRequiredValues = new List<OutboundMatch>();
- var namedOutboundMatchResults = new Dictionary<string, List<OutboundMatchResult>>(StringComparer.OrdinalIgnoreCase);
-
- // Decision tree is built using the 'required values' of actions.
- // - When generating a url using route values, decision tree checks the explicitly supplied route values +
- // ambient values to see if they have a match for the required-values-based-tree.
- // - When generating a url using route name, route values for controller, action etc.might not be provided
- // (this is expected because as a user I want to avoid writing all those and instead chose to use a
- // routename which is quick). So since these values are not provided and might not be even in ambient
- // values, decision tree would fail to find a match. So for this reason decision tree is not used for named
- // matches. Instead all named matches are returned as is and the LinkGenerator uses a TemplateBinder to
- // decide which of the matches can generate a url.
- // For example, for a route defined like below with current ambient values like new { controller = "Home",
- // action = "Index" }
- // "api/orders/{id}",
- // routeName: "OrdersApi",
- // defaults: new { controller = "Orders", action = "GetById" },
- // requiredValues: new { controller = "Orders", action = "GetById" },
- // A call to GetLink("OrdersApi", new { id = "10" }) cannot generate url as neither the supplied values or
- // current ambient values do not satisfy the decision tree that is built based on the required values.
- for (var i = 0; i < endpoints.Count; i++)
+ var endpoint = endpoints[i];
+ if (!(endpoint is RouteEndpoint routeEndpoint))
{
- var endpoint = endpoints[i];
- if (!(endpoint is RouteEndpoint routeEndpoint))
- {
- continue;
- }
-
- var metadata = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
- if (metadata == null && routeEndpoint.RoutePattern.RequiredValues.Count == 0)
- {
- continue;
- }
-
- if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true)
- {
- continue;
- }
+ continue;
+ }
- var entry = CreateOutboundRouteEntry(
- routeEndpoint,
- routeEndpoint.RoutePattern.RequiredValues,
- metadata?.RouteName);
+ var metadata = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
+ if (metadata == null && routeEndpoint.RoutePattern.RequiredValues.Count == 0)
+ {
+ continue;
+ }
- var outboundMatch = new OutboundMatch() { Entry = entry };
+ if (endpoint.Metadata.GetMetadata<ISuppressLinkGenerationMetadata>()?.SuppressLinkGeneration == true)
+ {
+ continue;
+ }
- if (routeEndpoint.RoutePattern.RequiredValues.Count > 0)
- {
- // Entries with a route name but no required values can only be matched by name.
- // Otherwise, these endpoints will match any attempt at action link generation.
- // Entries with neither a route name nor required values have already been skipped above.
- // See https://github.com/dotnet/aspnetcore/issues/35592
- matchesWithRequiredValues.Add(outboundMatch);
- }
+ var entry = CreateOutboundRouteEntry(
+ routeEndpoint,
+ routeEndpoint.RoutePattern.RequiredValues,
+ metadata?.RouteName);
- if (string.IsNullOrEmpty(entry.RouteName))
- {
- continue;
- }
+ var outboundMatch = new OutboundMatch() { Entry = entry };
- if (!namedOutboundMatchResults.TryGetValue(entry.RouteName, out var matchResults))
- {
- matchResults = new List<OutboundMatchResult>();
- namedOutboundMatchResults.Add(entry.RouteName, matchResults);
- }
- matchResults.Add(new OutboundMatchResult(outboundMatch, isFallbackMatch: false));
+ if (routeEndpoint.RoutePattern.RequiredValues.Count > 0)
+ {
+ // Entries with a route name but no required values can only be matched by name.
+ // Otherwise, these endpoints will match any attempt at action link generation.
+ // Entries with neither a route name nor required values have already been skipped above.
+ // See https://github.com/dotnet/aspnetcore/issues/35592
+ matchesWithRequiredValues.Add(outboundMatch);
}
- return new StateEntry(
- matchesWithRequiredValues,
- new LinkGenerationDecisionTree(matchesWithRequiredValues),
- namedOutboundMatchResults);
- }
+ if (string.IsNullOrEmpty(entry.RouteName))
+ {
+ continue;
+ }
- private static OutboundRouteEntry CreateOutboundRouteEntry(
- RouteEndpoint endpoint,
- IReadOnlyDictionary<string, object?> requiredValues,
- string? routeName)
- {
- var entry = new OutboundRouteEntry()
+ if (!namedOutboundMatchResults.TryGetValue(entry.RouteName, out var matchResults))
{
- Handler = NullRouter.Instance,
- Order = endpoint.Order,
- Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
- RequiredLinkValues = new RouteValueDictionary(requiredValues),
- RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
- Data = endpoint,
- RouteName = routeName,
- };
- entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
- return entry;
+ matchResults = new List<OutboundMatchResult>();
+ namedOutboundMatchResults.Add(entry.RouteName, matchResults);
+ }
+ matchResults.Add(new OutboundMatchResult(outboundMatch, isFallbackMatch: false));
}
- public void Dispose()
+ return new StateEntry(
+ matchesWithRequiredValues,
+ new LinkGenerationDecisionTree(matchesWithRequiredValues),
+ namedOutboundMatchResults);
+ }
+
+ private static OutboundRouteEntry CreateOutboundRouteEntry(
+ RouteEndpoint endpoint,
+ IReadOnlyDictionary<string, object?> requiredValues,
+ string? routeName)
+ {
+ var entry = new OutboundRouteEntry()
{
- _cache.Dispose();
- }
+ Handler = NullRouter.Instance,
+ Order = endpoint.Order,
+ Precedence = RoutePrecedence.ComputeOutbound(endpoint.RoutePattern),
+ RequiredLinkValues = new RouteValueDictionary(requiredValues),
+ RouteTemplate = new RouteTemplate(endpoint.RoutePattern),
+ Data = endpoint,
+ RouteName = routeName,
+ };
+ entry.Defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
+ return entry;
+ }
+
+ public void Dispose()
+ {
+ _cache.Dispose();
+ }
- internal class StateEntry
+ internal class StateEntry
+ {
+ // For testing
+ public readonly List<OutboundMatch> MatchesWithRequiredValues;
+ public readonly LinkGenerationDecisionTree AllMatchesLinkGenerationTree;
+ public readonly Dictionary<string, List<OutboundMatchResult>> NamedMatches;
+
+ public StateEntry(
+ List<OutboundMatch> matchesWithRequiredValues,
+ LinkGenerationDecisionTree allMatchesLinkGenerationTree,
+ Dictionary<string, List<OutboundMatchResult>> namedMatches)
{
- // For testing
- public readonly List<OutboundMatch> MatchesWithRequiredValues;
- public readonly LinkGenerationDecisionTree AllMatchesLinkGenerationTree;
- public readonly Dictionary<string, List<OutboundMatchResult>> NamedMatches;
-
- public StateEntry(
- List<OutboundMatch> matchesWithRequiredValues,
- LinkGenerationDecisionTree allMatchesLinkGenerationTree,
- Dictionary<string, List<OutboundMatchResult>> namedMatches)
- {
- MatchesWithRequiredValues = matchesWithRequiredValues;
- AllMatchesLinkGenerationTree = allMatchesLinkGenerationTree;
- NamedMatches = namedMatches;
- }
+ MatchesWithRequiredValues = matchesWithRequiredValues;
+ AllMatchesLinkGenerationTree = allMatchesLinkGenerationTree;
+ NamedMatches = namedMatches;
}
}
}
diff --git a/src/Http/Routing/src/RouterMiddleware.cs b/src/Http/Routing/src/RouterMiddleware.cs
index cb1650b14a..fa884cf621 100644
--- a/src/Http/Routing/src/RouterMiddleware.cs
+++ b/src/Http/Routing/src/RouterMiddleware.cs
@@ -6,70 +6,69 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+/// <summary>
+/// Middleware responsible for routing.
+/// </summary>
+public partial class RouterMiddleware
{
+ private readonly ILogger _logger;
+ private readonly RequestDelegate _next;
+ private readonly IRouter _router;
+
/// <summary>
- /// Middleware responsible for routing.
+ /// Constructs a new <see cref="RouterMiddleware"/> instance with a given <paramref name="router"/>.
/// </summary>
- public partial class RouterMiddleware
+ /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
+ /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
+ /// <param name="router">The <see cref="IRouter"/> to use for routing requests.</param>
+ public RouterMiddleware(
+ RequestDelegate next,
+ ILoggerFactory loggerFactory,
+ IRouter router)
{
- private readonly ILogger _logger;
- private readonly RequestDelegate _next;
- private readonly IRouter _router;
+ _next = next;
+ _router = router;
- /// <summary>
- /// Constructs a new <see cref="RouterMiddleware"/> instance with a given <paramref name="router"/>.
- /// </summary>
- /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
- /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
- /// <param name="router">The <see cref="IRouter"/> to use for routing requests.</param>
- public RouterMiddleware(
- RequestDelegate next,
- ILoggerFactory loggerFactory,
- IRouter router)
- {
- _next = next;
- _router = router;
-
- _logger = loggerFactory.CreateLogger<RouterMiddleware>();
- }
+ _logger = loggerFactory.CreateLogger<RouterMiddleware>();
+ }
- /// <summary>
- /// Evaluates the handler associated with the <see cref="RouteContext"/>
- /// derived from <paramref name="httpContext"/>.
- /// </summary>
- /// <param name="httpContext">A <see cref="HttpContext"/> instance.</param>
- public async Task Invoke(HttpContext httpContext)
- {
- var context = new RouteContext(httpContext);
- context.RouteData.Routers.Add(_router);
+ /// <summary>
+ /// Evaluates the handler associated with the <see cref="RouteContext"/>
+ /// derived from <paramref name="httpContext"/>.
+ /// </summary>
+ /// <param name="httpContext">A <see cref="HttpContext"/> instance.</param>
+ public async Task Invoke(HttpContext httpContext)
+ {
+ var context = new RouteContext(httpContext);
+ context.RouteData.Routers.Add(_router);
- await _router.RouteAsync(context);
+ await _router.RouteAsync(context);
- if (context.Handler == null)
- {
- Log.RequestNotMatched(_logger);
- await _next.Invoke(httpContext);
- }
- else
+ if (context.Handler == null)
+ {
+ Log.RequestNotMatched(_logger);
+ await _next.Invoke(httpContext);
+ }
+ else
+ {
+ var routingFeature = new RoutingFeature()
{
- var routingFeature = new RoutingFeature()
- {
- RouteData = context.RouteData
- };
+ RouteData = context.RouteData
+ };
- // Set the RouteValues on the current request, this is to keep the IRouteValuesFeature inline with the IRoutingFeature
- httpContext.Request.RouteValues = context.RouteData.Values;
- httpContext.Features.Set<IRoutingFeature>(routingFeature);
+ // Set the RouteValues on the current request, this is to keep the IRouteValuesFeature inline with the IRoutingFeature
+ httpContext.Request.RouteValues = context.RouteData.Values;
+ httpContext.Features.Set<IRoutingFeature>(routingFeature);
- await context.Handler(context.HttpContext);
- }
+ await context.Handler(context.HttpContext);
}
+ }
- private static partial class Log
- {
- [LoggerMessage(1, LogLevel.Debug, "Request did not match any routes", EventName = "RequestNotMatched")]
- public static partial void RequestNotMatched(ILogger logger);
- }
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Debug, "Request did not match any routes", EventName = "RequestNotMatched")]
+ public static partial void RequestNotMatched(ILogger logger);
}
}
diff --git a/src/Http/Routing/src/RoutingFeature.cs b/src/Http/Routing/src/RoutingFeature.cs
index 8eb288220a..587dcaf205 100644
--- a/src/Http/Routing/src/RoutingFeature.cs
+++ b/src/Http/Routing/src/RoutingFeature.cs
@@ -1,14 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A feature for routing functionality.
+/// </summary>
+public class RoutingFeature : IRoutingFeature
{
- /// <summary>
- /// A feature for routing functionality.
- /// </summary>
- public class RoutingFeature : IRoutingFeature
- {
- /// <inheritdoc />
- public RouteData? RouteData { get; set; }
- }
+ /// <inheritdoc />
+ public RouteData? RouteData { get; set; }
}
diff --git a/src/Http/Routing/src/RoutingMarkerService.cs b/src/Http/Routing/src/RoutingMarkerService.cs
index 63f5266a35..b529fac658 100644
--- a/src/Http/Routing/src/RoutingMarkerService.cs
+++ b/src/Http/Routing/src/RoutingMarkerService.cs
@@ -3,13 +3,12 @@
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// A marker class used to determine if all the routing services were added
+/// to the <see cref="IServiceCollection"/> before routing is configured.
+/// </summary>
+internal class RoutingMarkerService
{
- /// <summary>
- /// A marker class used to determine if all the routing services were added
- /// to the <see cref="IServiceCollection"/> before routing is configured.
- /// </summary>
- internal class RoutingMarkerService
- {
- }
}
diff --git a/src/Http/Routing/src/SegmentState.cs b/src/Http/Routing/src/SegmentState.cs
index 995a2d6d40..66dfb773a3 100644
--- a/src/Http/Routing/src/SegmentState.cs
+++ b/src/Http/Routing/src/SegmentState.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// Segments are treated as all-or-none. We should never output a partial segment.
+// If we add any subsegment of this segment to the generated URI, we have to add
+// the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we
+// used a value for {p1}, we have to output the entire segment up to the next "/".
+// Otherwise we could end up with the partial segment "v1" instead of the entire
+// segment "v1-v2.xml".
+internal enum SegmentState
{
- // Segments are treated as all-or-none. We should never output a partial segment.
- // If we add any subsegment of this segment to the generated URI, we have to add
- // the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we
- // used a value for {p1}, we have to output the entire segment up to the next "/".
- // Otherwise we could end up with the partial segment "v1" instead of the entire
- // segment "v1-v2.xml".
- internal enum SegmentState
- {
- Beginning,
- Inside,
- }
+ Beginning,
+ Inside,
}
diff --git a/src/Http/Routing/src/SuppressLinkGenerationMetadata.cs b/src/Http/Routing/src/SuppressLinkGenerationMetadata.cs
index 31c32603c7..d42614ae1a 100644
--- a/src/Http/Routing/src/SuppressLinkGenerationMetadata.cs
+++ b/src/Http/Routing/src/SuppressLinkGenerationMetadata.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Represents metadata used during link generation. If <see cref="SuppressLinkGeneration"/> is <c>true</c>
+/// the associated endpoint will not be used for link generation.
+/// </summary>
+public sealed class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata
{
/// <summary>
- /// Represents metadata used during link generation. If <see cref="SuppressLinkGeneration"/> is <c>true</c>
- /// the associated endpoint will not be used for link generation.
+ /// Gets a value indicating whether the assocated endpoint should be used for link generation.
/// </summary>
- public sealed class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata
- {
- /// <summary>
- /// Gets a value indicating whether the assocated endpoint should be used for link generation.
- /// </summary>
- public bool SuppressLinkGeneration => true;
- }
-} \ No newline at end of file
+ public bool SuppressLinkGeneration => true;
+}
diff --git a/src/Http/Routing/src/SuppressMatchingMetadata.cs b/src/Http/Routing/src/SuppressMatchingMetadata.cs
index 540f748b28..8d59dcefb3 100644
--- a/src/Http/Routing/src/SuppressMatchingMetadata.cs
+++ b/src/Http/Routing/src/SuppressMatchingMetadata.cs
@@ -1,17 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+/// <summary>
+/// Metadata used to prevent URL matching. If <see cref="SuppressMatching"/> is <c>true</c> the
+/// associated endpoint will not be considered for URL matching.
+/// </summary>
+public sealed class SuppressMatchingMetadata : ISuppressMatchingMetadata
{
/// <summary>
- /// Metadata used to prevent URL matching. If <see cref="SuppressMatching"/> is <c>true</c> the
- /// associated endpoint will not be considered for URL matching.
+ /// Gets a value indicating whether the associated endpoint should be used for URL matching.
/// </summary>
- public sealed class SuppressMatchingMetadata : ISuppressMatchingMetadata
- {
- /// <summary>
- /// Gets a value indicating whether the associated endpoint should be used for URL matching.
- /// </summary>
- public bool SuppressMatching => true;
- }
+ public bool SuppressMatching => true;
}
diff --git a/src/Http/Routing/src/Template/DefaultTemplateBinderFactory.cs b/src/Http/Routing/src/Template/DefaultTemplateBinderFactory.cs
index 9522355d14..652ae0860f 100644
--- a/src/Http/Routing/src/Template/DefaultTemplateBinderFactory.cs
+++ b/src/Http/Routing/src/Template/DefaultTemplateBinderFactory.cs
@@ -7,82 +7,81 @@ using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.ObjectPool;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+internal sealed class DefaultTemplateBinderFactory : TemplateBinderFactory
{
- internal sealed class DefaultTemplateBinderFactory : TemplateBinderFactory
+ private readonly ParameterPolicyFactory _policyFactory;
+ private readonly ObjectPool<UriBuildingContext> _pool;
+
+ public DefaultTemplateBinderFactory(
+ ParameterPolicyFactory policyFactory,
+ ObjectPool<UriBuildingContext> pool)
{
- private readonly ParameterPolicyFactory _policyFactory;
- private readonly ObjectPool<UriBuildingContext> _pool;
+ if (policyFactory == null)
+ {
+ throw new ArgumentNullException(nameof(policyFactory));
+ }
- public DefaultTemplateBinderFactory(
- ParameterPolicyFactory policyFactory,
- ObjectPool<UriBuildingContext> pool)
+ if (pool == null)
{
- if (policyFactory == null)
- {
- throw new ArgumentNullException(nameof(policyFactory));
- }
+ throw new ArgumentNullException(nameof(pool));
+ }
- if (pool == null)
- {
- throw new ArgumentNullException(nameof(pool));
- }
+ _policyFactory = policyFactory;
+ _pool = pool;
- _policyFactory = policyFactory;
- _pool = pool;
+ }
+ public override TemplateBinder Create(RouteTemplate template, RouteValueDictionary defaults)
+ {
+ if (template == null)
+ {
+ throw new ArgumentNullException(nameof(template));
}
- public override TemplateBinder Create(RouteTemplate template, RouteValueDictionary defaults)
+ if (defaults == null)
{
- if (template == null)
- {
- throw new ArgumentNullException(nameof(template));
- }
+ throw new ArgumentNullException(nameof(defaults));
+ }
- if (defaults == null)
- {
- throw new ArgumentNullException(nameof(defaults));
- }
+ return new TemplateBinder(UrlEncoder.Default, _pool, template, defaults);
+ }
- return new TemplateBinder(UrlEncoder.Default, _pool, template, defaults);
+ public override TemplateBinder Create(RoutePattern pattern)
+ {
+ if (pattern == null)
+ {
+ throw new ArgumentNullException(nameof(pattern));
}
- public override TemplateBinder Create(RoutePattern pattern)
+ // Now create the constraints and parameter transformers from the pattern
+ var policies = new List<(string parameterName, IParameterPolicy policy)>();
+ foreach (var kvp in pattern.ParameterPolicies)
{
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ var parameterName = kvp.Key;
- // Now create the constraints and parameter transformers from the pattern
- var policies = new List<(string parameterName, IParameterPolicy policy)>();
- foreach (var kvp in pattern.ParameterPolicies)
- {
- var parameterName = kvp.Key;
+ // It's possible that we don't have an actual route parameter, we need to support that case.
+ var parameter = pattern.GetParameter(parameterName);
- // It's possible that we don't have an actual route parameter, we need to support that case.
- var parameter = pattern.GetParameter(parameterName);
+ // Use the first parameter transformer per parameter
+ var foundTransformer = false;
+ for (var i = 0; i < kvp.Value.Count; i++)
+ {
+ var parameterPolicy = _policyFactory.Create(parameter, kvp.Value[i]);
+ if (!foundTransformer && parameterPolicy is IOutboundParameterTransformer parameterTransformer)
+ {
+ policies.Add((parameterName, parameterTransformer));
+ foundTransformer = true;
+ }
- // Use the first parameter transformer per parameter
- var foundTransformer = false;
- for (var i = 0; i < kvp.Value.Count; i++)
+ if (parameterPolicy is IRouteConstraint constraint)
{
- var parameterPolicy = _policyFactory.Create(parameter, kvp.Value[i]);
- if (!foundTransformer && parameterPolicy is IOutboundParameterTransformer parameterTransformer)
- {
- policies.Add((parameterName, parameterTransformer));
- foundTransformer = true;
- }
-
- if (parameterPolicy is IRouteConstraint constraint)
- {
- policies.Add((parameterName, constraint));
- }
+ policies.Add((parameterName, constraint));
}
}
-
- return new TemplateBinder(UrlEncoder.Default, _pool, pattern, policies);
}
+
+ return new TemplateBinder(UrlEncoder.Default, _pool, pattern, policies);
}
}
diff --git a/src/Http/Routing/src/Template/InlineConstraint.cs b/src/Http/Routing/src/Template/InlineConstraint.cs
index 92f921c7df..d716bcac8c 100644
--- a/src/Http/Routing/src/Template/InlineConstraint.cs
+++ b/src/Http/Routing/src/Template/InlineConstraint.cs
@@ -4,44 +4,43 @@
using System;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// The parsed representation of an inline constraint in a route parameter.
+/// </summary>
+public class InlineConstraint
{
/// <summary>
- /// The parsed representation of an inline constraint in a route parameter.
+ /// Creates a new instance of <see cref="InlineConstraint"/>.
/// </summary>
- public class InlineConstraint
+ /// <param name="constraint">The constraint text.</param>
+ public InlineConstraint(string constraint)
{
- /// <summary>
- /// Creates a new instance of <see cref="InlineConstraint"/>.
- /// </summary>
- /// <param name="constraint">The constraint text.</param>
- public InlineConstraint(string constraint)
+ if (constraint == null)
{
- if (constraint == null)
- {
- throw new ArgumentNullException(nameof(constraint));
- }
-
- Constraint = constraint;
+ throw new ArgumentNullException(nameof(constraint));
}
- /// <summary>
- /// Creates a new <see cref="InlineConstraint"/> instance given a <see cref="RoutePatternParameterPolicyReference"/>.
- /// </summary>
- /// <param name="other">A <see cref="RoutePatternParameterPolicyReference"/> instance.</param>
- public InlineConstraint(RoutePatternParameterPolicyReference other)
- {
- if (other == null)
- {
- throw new ArgumentNullException(nameof(other));
- }
+ Constraint = constraint;
+ }
- Constraint = other.Content!;
+ /// <summary>
+ /// Creates a new <see cref="InlineConstraint"/> instance given a <see cref="RoutePatternParameterPolicyReference"/>.
+ /// </summary>
+ /// <param name="other">A <see cref="RoutePatternParameterPolicyReference"/> instance.</param>
+ public InlineConstraint(RoutePatternParameterPolicyReference other)
+ {
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
}
- /// <summary>
- /// Gets the constraint text.
- /// </summary>
- public string Constraint { get; }
+ Constraint = other.Content!;
}
+
+ /// <summary>
+ /// Gets the constraint text.
+ /// </summary>
+ public string Constraint { get; }
}
diff --git a/src/Http/Routing/src/Template/RoutePrecedence.cs b/src/Http/Routing/src/Template/RoutePrecedence.cs
index 03957d7acd..d8de9bce01 100644
--- a/src/Http/Routing/src/Template/RoutePrecedence.cs
+++ b/src/Http/Routing/src/Template/RoutePrecedence.cs
@@ -8,270 +8,269 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Computes precedence for a route template.
+/// </summary>
+public static class RoutePrecedence
{
/// <summary>
- /// Computes precedence for a route template.
+ /// Compute the precedence for matching a provided url
/// </summary>
- public static class RoutePrecedence
+ /// <example>
+ /// e.g.: /api/template == 1.1
+ /// /api/template/{id} == 1.13
+ /// /api/{id:int} == 1.2
+ /// /api/template/{id:int} == 1.12
+ /// </example>
+ /// <param name="template">The <see cref="RouteTemplate"/> to compute precedence for.</param>
+ /// <returns>A <see cref="decimal"/> representing the route's precedence.</returns>
+ public static decimal ComputeInbound(RouteTemplate template)
{
- /// <summary>
- /// Compute the precedence for matching a provided url
- /// </summary>
- /// <example>
- /// e.g.: /api/template == 1.1
- /// /api/template/{id} == 1.13
- /// /api/{id:int} == 1.2
- /// /api/template/{id:int} == 1.12
- /// </example>
- /// <param name="template">The <see cref="RouteTemplate"/> to compute precedence for.</param>
- /// <returns>A <see cref="decimal"/> representing the route's precedence.</returns>
- public static decimal ComputeInbound(RouteTemplate template)
+ ValidateSegementLength(template.Segments.Count);
+
+ // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
+ // and 4 results in a combined precedence of 2.14 (decimal).
+ var precedence = 0m;
+
+ for (var i = 0; i < template.Segments.Count; i++)
{
- ValidateSegementLength(template.Segments.Count);
+ var segment = template.Segments[i];
- // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
- // and 4 results in a combined precedence of 2.14 (decimal).
- var precedence = 0m;
+ var digit = ComputeInboundPrecedenceDigit(segment);
+ Debug.Assert(digit >= 0 && digit < 10);
- for (var i = 0; i < template.Segments.Count; i++)
- {
- var segment = template.Segments[i];
+ precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
+ }
- var digit = ComputeInboundPrecedenceDigit(segment);
- Debug.Assert(digit >= 0 && digit < 10);
+ return precedence;
+ }
- precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
- }
+ // See description on ComputeInbound(RouteTemplate)
+ internal static decimal ComputeInbound(RoutePattern routePattern)
+ {
+ ValidateSegementLength(routePattern.PathSegments.Count);
- return precedence;
- }
+ var precedence = 0m;
- // See description on ComputeInbound(RouteTemplate)
- internal static decimal ComputeInbound(RoutePattern routePattern)
+ for (var i = 0; i < routePattern.PathSegments.Count; i++)
{
- ValidateSegementLength(routePattern.PathSegments.Count);
+ var segment = routePattern.PathSegments[i];
- var precedence = 0m;
+ var digit = ComputeInboundPrecedenceDigit(routePattern, segment);
+ Debug.Assert(digit >= 0 && digit < 10);
- for (var i = 0; i < routePattern.PathSegments.Count; i++)
- {
- var segment = routePattern.PathSegments[i];
+ precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
+ }
- var digit = ComputeInboundPrecedenceDigit(routePattern, segment);
- Debug.Assert(digit >= 0 && digit < 10);
+ return precedence;
+ }
- precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
- }
+ /// <summary>
+ /// Compute the precedence for generating a url.
+ /// </summary>
+ /// <example>
+ /// e.g.: /api/template == 5.5
+ /// /api/template/{id} == 5.53
+ /// /api/{id:int} == 5.4
+ /// /api/template/{id:int} == 5.54
+ /// </example>
+ /// <param name="template">The <see cref="RouteTemplate"/> to compute precedence for.</param>
+ /// <returns>A <see cref="decimal"/> representing the route's precedence.</returns>
+ public static decimal ComputeOutbound(RouteTemplate template)
+ {
+ ValidateSegementLength(template.Segments.Count);
- return precedence;
- }
+ // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
+ // and 4 results in a combined precedence of 2.14 (decimal).
+ var precedence = 0m;
- /// <summary>
- /// Compute the precedence for generating a url.
- /// </summary>
- /// <example>
- /// e.g.: /api/template == 5.5
- /// /api/template/{id} == 5.53
- /// /api/{id:int} == 5.4
- /// /api/template/{id:int} == 5.54
- /// </example>
- /// <param name="template">The <see cref="RouteTemplate"/> to compute precedence for.</param>
- /// <returns>A <see cref="decimal"/> representing the route's precedence.</returns>
- public static decimal ComputeOutbound(RouteTemplate template)
+ for (var i = 0; i < template.Segments.Count; i++)
{
- ValidateSegementLength(template.Segments.Count);
+ var segment = template.Segments[i];
- // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
- // and 4 results in a combined precedence of 2.14 (decimal).
- var precedence = 0m;
+ var digit = ComputeOutboundPrecedenceDigit(segment);
+ Debug.Assert(digit >= 0 && digit < 10);
- for (var i = 0; i < template.Segments.Count; i++)
- {
- var segment = template.Segments[i];
+ precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
+ }
- var digit = ComputeOutboundPrecedenceDigit(segment);
- Debug.Assert(digit >= 0 && digit < 10);
+ return precedence;
+ }
- precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
- }
+ // see description on ComputeOutbound(RouteTemplate)
+ internal static decimal ComputeOutbound(RoutePattern routePattern)
+ {
+ ValidateSegementLength(routePattern.PathSegments.Count);
- return precedence;
- }
+ // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
+ // and 4 results in a combined precedence of 2.14 (decimal).
+ var precedence = 0m;
- // see description on ComputeOutbound(RouteTemplate)
- internal static decimal ComputeOutbound(RoutePattern routePattern)
+ for (var i = 0; i < routePattern.PathSegments.Count; i++)
{
- ValidateSegementLength(routePattern.PathSegments.Count);
+ var segment = routePattern.PathSegments[i];
- // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1,
- // and 4 results in a combined precedence of 2.14 (decimal).
- var precedence = 0m;
+ var digit = ComputeOutboundPrecedenceDigit(segment);
+ Debug.Assert(digit >= 0 && digit < 10);
- for (var i = 0; i < routePattern.PathSegments.Count; i++)
- {
- var segment = routePattern.PathSegments[i];
-
- var digit = ComputeOutboundPrecedenceDigit(segment);
- Debug.Assert(digit >= 0 && digit < 10);
+ precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
+ }
- precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
- }
+ return precedence;
+ }
- return precedence;
+ private static void ValidateSegementLength(int length)
+ {
+ if (length > 28)
+ {
+ // An OverflowException will be thrown by Math.Pow when greater than 28
+ throw new InvalidOperationException("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.");
}
+ }
- private static void ValidateSegementLength(int length)
+ // Segments have the following order:
+ // 5 - Literal segments
+ // 4 - Multi-part segments && Constrained parameter segments
+ // 3 - Unconstrained parameter segements
+ // 2 - Constrained wildcard parameter segments
+ // 1 - Unconstrained wildcard parameter segments
+ private static int ComputeOutboundPrecedenceDigit(TemplateSegment segment)
+ {
+ if (segment.Parts.Count > 1)
{
- if (length > 28)
- {
- // An OverflowException will be thrown by Math.Pow when greater than 28
- throw new InvalidOperationException("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.");
- }
+ return 4;
}
- // Segments have the following order:
- // 5 - Literal segments
- // 4 - Multi-part segments && Constrained parameter segments
- // 3 - Unconstrained parameter segements
- // 2 - Constrained wildcard parameter segments
- // 1 - Unconstrained wildcard parameter segments
- private static int ComputeOutboundPrecedenceDigit(TemplateSegment segment)
+ var part = segment.Parts[0];
+ if (part.IsLiteral)
{
- if(segment.Parts.Count > 1)
- {
- return 4;
- }
+ return 5;
+ }
+ else
+ {
+ Debug.Assert(part.IsParameter);
+ var digit = part.IsCatchAll ? 1 : 3;
- var part = segment.Parts[0];
- if(part.IsLiteral)
+ if (part.InlineConstraints != null && part.InlineConstraints.Any())
{
- return 5;
+ digit++;
}
- else
- {
- Debug.Assert(part.IsParameter);
- var digit = part.IsCatchAll ? 1 : 3;
- if (part.InlineConstraints != null && part.InlineConstraints.Any())
- {
- digit++;
- }
+ return digit;
+ }
+ }
- return digit;
- }
+ // See description on ComputeOutboundPrecedenceDigit(TemplateSegment segment)
+ private static int ComputeOutboundPrecedenceDigit(RoutePatternPathSegment pathSegment)
+ {
+ if (pathSegment.Parts.Count > 1)
+ {
+ return 4;
}
- // See description on ComputeOutboundPrecedenceDigit(TemplateSegment segment)
- private static int ComputeOutboundPrecedenceDigit(RoutePatternPathSegment pathSegment)
+ var part = pathSegment.Parts[0];
+ if (part.IsLiteral)
{
- if (pathSegment.Parts.Count > 1)
- {
- return 4;
- }
+ return 5;
+ }
+ else if (part is RoutePatternParameterPart parameterPart)
+ {
+ Debug.Assert(parameterPart != null);
+ var digit = parameterPart.IsCatchAll ? 1 : 3;
- var part = pathSegment.Parts[0];
- if (part.IsLiteral)
+ if (parameterPart.ParameterPolicies.Count > 0)
{
- return 5;
+ digit++;
}
- else if (part is RoutePatternParameterPart parameterPart)
- {
- Debug.Assert(parameterPart != null);
- var digit = parameterPart.IsCatchAll ? 1 : 3;
- if (parameterPart.ParameterPolicies.Count > 0)
- {
- digit++;
- }
+ return digit;
+ }
+ else
+ {
+ // Unreachable
+ throw new NotSupportedException();
+ }
+ }
- return digit;
- }
- else
- {
- // Unreachable
- throw new NotSupportedException();
- }
+ // Segments have the following order:
+ // 1 - Literal segments
+ // 2 - Constrained parameter segments / Multi-part segments
+ // 3 - Unconstrained parameter segments
+ // 4 - Constrained wildcard parameter segments
+ // 5 - Unconstrained wildcard parameter segments
+ private static int ComputeInboundPrecedenceDigit(TemplateSegment segment)
+ {
+ if (segment.Parts.Count > 1)
+ {
+ // Multi-part segments should appear after literal segments and along with parameter segments
+ return 2;
}
- // Segments have the following order:
- // 1 - Literal segments
- // 2 - Constrained parameter segments / Multi-part segments
- // 3 - Unconstrained parameter segments
- // 4 - Constrained wildcard parameter segments
- // 5 - Unconstrained wildcard parameter segments
- private static int ComputeInboundPrecedenceDigit(TemplateSegment segment)
+ var part = segment.Parts[0];
+ // Literal segments always go first
+ if (part.IsLiteral)
{
- if (segment.Parts.Count > 1)
- {
- // Multi-part segments should appear after literal segments and along with parameter segments
- return 2;
- }
+ return 1;
+ }
+ else
+ {
+ Debug.Assert(part.IsParameter);
+ var digit = part.IsCatchAll ? 5 : 3;
- var part = segment.Parts[0];
- // Literal segments always go first
- if (part.IsLiteral)
+ // If there is a route constraint for the parameter, reduce order by 1
+ // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
+ if (part.InlineConstraints != null && part.InlineConstraints.Any())
{
- return 1;
+ digit--;
}
- else
- {
- Debug.Assert(part.IsParameter);
- var digit = part.IsCatchAll ? 5 : 3;
- // If there is a route constraint for the parameter, reduce order by 1
- // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
- if (part.InlineConstraints != null && part.InlineConstraints.Any())
- {
- digit--;
- }
-
- return digit;
- }
+ return digit;
}
+ }
- // see description on ComputeInboundPrecedenceDigit(TemplateSegment segment)
- //
- // With a RoutePattern, parameters with a required value are treated as a literal segment
- internal static int ComputeInboundPrecedenceDigit(RoutePattern routePattern, RoutePatternPathSegment pathSegment)
+ // see description on ComputeInboundPrecedenceDigit(TemplateSegment segment)
+ //
+ // With a RoutePattern, parameters with a required value are treated as a literal segment
+ internal static int ComputeInboundPrecedenceDigit(RoutePattern routePattern, RoutePatternPathSegment pathSegment)
+ {
+ if (pathSegment.Parts.Count > 1)
{
- if (pathSegment.Parts.Count > 1)
- {
- // Multi-part segments should appear after literal segments and along with parameter segments
- return 2;
- }
+ // Multi-part segments should appear after literal segments and along with parameter segments
+ return 2;
+ }
- var part = pathSegment.Parts[0];
- // Literal segments always go first
- if (part.IsLiteral)
+ var part = pathSegment.Parts[0];
+ // Literal segments always go first
+ if (part.IsLiteral)
+ {
+ return 1;
+ }
+ else if (part is RoutePatternParameterPart parameterPart)
+ {
+ // Parameter with a required value is matched as a literal
+ if (routePattern.RequiredValues.TryGetValue(parameterPart.Name, out var requiredValue) &&
+ !RouteValueEqualityComparer.Default.Equals(requiredValue, string.Empty))
{
return 1;
}
- else if (part is RoutePatternParameterPart parameterPart)
- {
- // Parameter with a required value is matched as a literal
- if (routePattern.RequiredValues.TryGetValue(parameterPart.Name, out var requiredValue) &&
- !RouteValueEqualityComparer.Default.Equals(requiredValue, string.Empty))
- {
- return 1;
- }
-
- var digit = parameterPart.IsCatchAll ? 5 : 3;
-
- // If there is a route constraint for the parameter, reduce order by 1
- // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
- if (parameterPart.ParameterPolicies.Count > 0)
- {
- digit--;
- }
-
- return digit;
- }
- else
+
+ var digit = parameterPart.IsCatchAll ? 5 : 3;
+
+ // If there is a route constraint for the parameter, reduce order by 1
+ // Constrained parameters end up with order 2, Constrained catch alls end up with order 4
+ if (parameterPart.ParameterPolicies.Count > 0)
{
- // Unreachable
- throw new NotSupportedException();
+ digit--;
}
+
+ return digit;
+ }
+ else
+ {
+ // Unreachable
+ throw new NotSupportedException();
}
}
}
diff --git a/src/Http/Routing/src/Template/RouteTemplate.cs b/src/Http/Routing/src/Template/RouteTemplate.cs
index 0476bf4996..846060e3d1 100644
--- a/src/Http/Routing/src/Template/RouteTemplate.cs
+++ b/src/Http/Routing/src/Template/RouteTemplate.cs
@@ -9,141 +9,140 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Represents the template for a route.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public class RouteTemplate
{
+ private const string SeparatorString = "/";
+
/// <summary>
- /// Represents the template for a route.
+ /// Constructs a new <see cref="RouteTemplate"/> instance given <paramref name="other"/>.
/// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public class RouteTemplate
+ /// <param name="other">A <see cref="RoutePattern"/> instance.</param>
+ public RouteTemplate(RoutePattern other)
{
- private const string SeparatorString = "/";
-
- /// <summary>
- /// Constructs a new <see cref="RouteTemplate"/> instance given <paramref name="other"/>.
- /// </summary>
- /// <param name="other">A <see cref="RoutePattern"/> instance.</param>
- public RouteTemplate(RoutePattern other)
+ if (other == null)
{
- if (other == null)
- {
- throw new ArgumentNullException(nameof(other));
- }
+ throw new ArgumentNullException(nameof(other));
+ }
- // RequiredValues will be ignored. RouteTemplate doesn't support them.
+ // RequiredValues will be ignored. RouteTemplate doesn't support them.
- TemplateText = other.RawText;
- Segments = new List<TemplateSegment>(other.PathSegments.Select(p => new TemplateSegment(p)));
- Parameters = new List<TemplatePart>();
- for (var i = 0; i < Segments.Count; i++)
+ TemplateText = other.RawText;
+ Segments = new List<TemplateSegment>(other.PathSegments.Select(p => new TemplateSegment(p)));
+ Parameters = new List<TemplatePart>();
+ for (var i = 0; i < Segments.Count; i++)
+ {
+ var segment = Segments[i];
+ for (var j = 0; j < segment.Parts.Count; j++)
{
- var segment = Segments[i];
- for (var j = 0; j < segment.Parts.Count; j++)
+ var part = segment.Parts[j];
+ if (part.IsParameter)
{
- var part = segment.Parts[j];
- if (part.IsParameter)
- {
- Parameters.Add(part);
- }
+ Parameters.Add(part);
}
}
}
+ }
- /// <summary>
- /// Constructs a a new <see cref="RouteTemplate" /> instance given the <paramref name="template"/> string
- /// and a list of <paramref name="segments"/>. Computes the parameters in the route template.
- /// </summary>
- /// <param name="template">A string representation of the route template.</param>
- /// <param name="segments">A list of <see cref="TemplateSegment"/>.</param>
- public RouteTemplate(string template, List<TemplateSegment> segments)
+ /// <summary>
+ /// Constructs a a new <see cref="RouteTemplate" /> instance given the <paramref name="template"/> string
+ /// and a list of <paramref name="segments"/>. Computes the parameters in the route template.
+ /// </summary>
+ /// <param name="template">A string representation of the route template.</param>
+ /// <param name="segments">A list of <see cref="TemplateSegment"/>.</param>
+ public RouteTemplate(string template, List<TemplateSegment> segments)
+ {
+ if (segments == null)
{
- if (segments == null)
- {
- throw new ArgumentNullException(nameof(segments));
- }
+ throw new ArgumentNullException(nameof(segments));
+ }
- TemplateText = template;
+ TemplateText = template;
- Segments = segments;
+ Segments = segments;
- Parameters = new List<TemplatePart>();
- for (var i = 0; i < segments.Count; i++)
+ Parameters = new List<TemplatePart>();
+ for (var i = 0; i < segments.Count; i++)
+ {
+ var segment = Segments[i];
+ for (var j = 0; j < segment.Parts.Count; j++)
{
- var segment = Segments[i];
- for (var j = 0; j < segment.Parts.Count; j++)
+ var part = segment.Parts[j];
+ if (part.IsParameter)
{
- var part = segment.Parts[j];
- if (part.IsParameter)
- {
- Parameters.Add(part);
- }
+ Parameters.Add(part);
}
}
}
+ }
- /// <summary>
- /// Gets the string representation of the route template.
- /// </summary>
- public string? TemplateText { get; }
-
- /// <summary>
- /// Gets the list of <see cref="TemplatePart"/> that represent that parameters defined in the route template.
- /// </summary>
- public IList<TemplatePart> Parameters { get; }
-
- /// <summary>
- /// Gets the list of <see cref="TemplateSegment"/> that compromise the route template.
- /// </summary>
- public IList<TemplateSegment> Segments { get; }
-
- /// <summary>
- /// Gets the <see cref="TemplateSegment"/> at a given index.
- /// </summary>
- /// <param name="index">The index of the element to retrieve.</param>
- /// <returns>A <see cref="TemplateSegment"/> instance.</returns>
- public TemplateSegment? GetSegment(int index)
- {
- if (index < 0)
- {
- throw new IndexOutOfRangeException();
- }
+ /// <summary>
+ /// Gets the string representation of the route template.
+ /// </summary>
+ public string? TemplateText { get; }
- return index >= Segments.Count ? null : Segments[index];
- }
+ /// <summary>
+ /// Gets the list of <see cref="TemplatePart"/> that represent that parameters defined in the route template.
+ /// </summary>
+ public IList<TemplatePart> Parameters { get; }
- private string DebuggerToString()
+ /// <summary>
+ /// Gets the list of <see cref="TemplateSegment"/> that compromise the route template.
+ /// </summary>
+ public IList<TemplateSegment> Segments { get; }
+
+ /// <summary>
+ /// Gets the <see cref="TemplateSegment"/> at a given index.
+ /// </summary>
+ /// <param name="index">The index of the element to retrieve.</param>
+ /// <returns>A <see cref="TemplateSegment"/> instance.</returns>
+ public TemplateSegment? GetSegment(int index)
+ {
+ if (index < 0)
{
- return string.Join(SeparatorString, Segments.Select(s => s.DebuggerToString()));
+ throw new IndexOutOfRangeException();
}
- /// <summary>
- /// Gets the parameter matching the given name.
- /// </summary>
- /// <param name="name">The name of the parameter to match.</param>
- /// <returns>The matching parameter or <c>null</c> if no parameter matches the given name.</returns>
- public TemplatePart? GetParameter(string name)
+ return index >= Segments.Count ? null : Segments[index];
+ }
+
+ private string DebuggerToString()
+ {
+ return string.Join(SeparatorString, Segments.Select(s => s.DebuggerToString()));
+ }
+
+ /// <summary>
+ /// Gets the parameter matching the given name.
+ /// </summary>
+ /// <param name="name">The name of the parameter to match.</param>
+ /// <returns>The matching parameter or <c>null</c> if no parameter matches the given name.</returns>
+ public TemplatePart? GetParameter(string name)
+ {
+ for (var i = 0; i < Parameters.Count; i++)
{
- for (var i = 0; i < Parameters.Count; i++)
+ var parameter = Parameters[i];
+ if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
{
- var parameter = Parameters[i];
- if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
- {
- return parameter;
- }
+ return parameter;
}
-
- return null;
}
- /// <summary>
- /// Converts the <see cref="RouteTemplate"/> to the equivalent
- /// <see cref="RoutePattern"/>
- /// </summary>
- /// <returns>A <see cref="RoutePattern"/>.</returns>
- public RoutePattern ToRoutePattern()
- {
- var segments = Segments.Select(s => s.ToRoutePatternPathSegment());
- return RoutePatternFactory.Pattern(TemplateText, segments);
- }
+ return null;
+ }
+
+ /// <summary>
+ /// Converts the <see cref="RouteTemplate"/> to the equivalent
+ /// <see cref="RoutePattern"/>
+ /// </summary>
+ /// <returns>A <see cref="RoutePattern"/>.</returns>
+ public RoutePattern ToRoutePattern()
+ {
+ var segments = Segments.Select(s => s.ToRoutePatternPathSegment());
+ return RoutePatternFactory.Pattern(TemplateText, segments);
}
}
diff --git a/src/Http/Routing/src/Template/TemplateBinder.cs b/src/Http/Routing/src/Template/TemplateBinder.cs
index ccedd90527..03bc4c6710 100644
--- a/src/Http/Routing/src/Template/TemplateBinder.cs
+++ b/src/Http/Routing/src/Template/TemplateBinder.cs
@@ -13,752 +13,751 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.ObjectPool;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Supports processing and binding parameter values in a route template.
+/// </summary>
+public class TemplateBinder
{
+ private readonly UrlEncoder _urlEncoder;
+ private readonly ObjectPool<UriBuildingContext> _pool;
+
+ private readonly (string parameterName, IRouteConstraint constraint)[] _constraints;
+ private readonly RouteValueDictionary? _defaults;
+ private readonly KeyValuePair<string, object?>[] _filters;
+ private readonly (string parameterName, IOutboundParameterTransformer transformer)[] _parameterTransformers;
+ private readonly RoutePattern _pattern;
+ private readonly string[] _requiredKeys;
+
+ // A pre-allocated template for the 'known' route values that this template binder uses.
+ //
+ // We always make a copy of this and operate on the copy, so that we don't mutate shared state.
+ private readonly KeyValuePair<string, object?>[] _slots;
+
/// <summary>
- /// Supports processing and binding parameter values in a route template.
+ /// Creates a new instance of <see cref="TemplateBinder"/>.
/// </summary>
- public class TemplateBinder
+ /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
+ /// <param name="pool">The <see cref="ObjectPool{T}"/>.</param>
+ /// <param name="template">The <see cref="RouteTemplate"/> to bind values to.</param>
+ /// <param name="defaults">The default values for <paramref name="template"/>.</param>
+ internal TemplateBinder(
+ UrlEncoder urlEncoder,
+ ObjectPool<UriBuildingContext> pool,
+ RouteTemplate template,
+ RouteValueDictionary defaults)
+ : this(urlEncoder, pool, template?.ToRoutePattern()!, defaults, requiredKeys: null, parameterPolicies: null)
{
- private readonly UrlEncoder _urlEncoder;
- private readonly ObjectPool<UriBuildingContext> _pool;
-
- private readonly (string parameterName, IRouteConstraint constraint)[] _constraints;
- private readonly RouteValueDictionary? _defaults;
- private readonly KeyValuePair<string, object?>[] _filters;
- private readonly (string parameterName, IOutboundParameterTransformer transformer)[] _parameterTransformers;
- private readonly RoutePattern _pattern;
- private readonly string[] _requiredKeys;
-
- // A pre-allocated template for the 'known' route values that this template binder uses.
- //
- // We always make a copy of this and operate on the copy, so that we don't mutate shared state.
- private readonly KeyValuePair<string, object?>[] _slots;
-
- /// <summary>
- /// Creates a new instance of <see cref="TemplateBinder"/>.
- /// </summary>
- /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
- /// <param name="pool">The <see cref="ObjectPool{T}"/>.</param>
- /// <param name="template">The <see cref="RouteTemplate"/> to bind values to.</param>
- /// <param name="defaults">The default values for <paramref name="template"/>.</param>
- internal TemplateBinder(
- UrlEncoder urlEncoder,
- ObjectPool<UriBuildingContext> pool,
- RouteTemplate template,
- RouteValueDictionary defaults)
- : this(urlEncoder, pool, template?.ToRoutePattern()!, defaults, requiredKeys: null, parameterPolicies: null)
- {
- }
-
- /// <summary>
- /// Creates a new instance of <see cref="TemplateBinder"/>.
- /// </summary>
- /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
- /// <param name="pool">The <see cref="ObjectPool{T}"/>.</param>
- /// <param name="pattern">The <see cref="RoutePattern"/> to bind values to.</param>
- /// <param name="defaults">The default values for <paramref name="pattern"/>. Optional.</param>
- /// <param name="requiredKeys">Keys used to determine if the ambient values apply. Optional.</param>
- /// <param name="parameterPolicies">
- /// A list of (<see cref="string"/>, <see cref="IParameterPolicy"/>) pairs to evaluate when producing a URI.
- /// </param>
- internal TemplateBinder(
- UrlEncoder urlEncoder,
- ObjectPool<UriBuildingContext> pool,
- RoutePattern pattern,
- RouteValueDictionary? defaults,
- IEnumerable<string>? requiredKeys,
- IEnumerable<(string parameterName, IParameterPolicy policy)>? parameterPolicies)
- {
- if (urlEncoder == null)
- {
- throw new ArgumentNullException(nameof(urlEncoder));
- }
+ }
- if (pool == null)
- {
- throw new ArgumentNullException(nameof(pool));
- }
+ /// <summary>
+ /// Creates a new instance of <see cref="TemplateBinder"/>.
+ /// </summary>
+ /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
+ /// <param name="pool">The <see cref="ObjectPool{T}"/>.</param>
+ /// <param name="pattern">The <see cref="RoutePattern"/> to bind values to.</param>
+ /// <param name="defaults">The default values for <paramref name="pattern"/>. Optional.</param>
+ /// <param name="requiredKeys">Keys used to determine if the ambient values apply. Optional.</param>
+ /// <param name="parameterPolicies">
+ /// A list of (<see cref="string"/>, <see cref="IParameterPolicy"/>) pairs to evaluate when producing a URI.
+ /// </param>
+ internal TemplateBinder(
+ UrlEncoder urlEncoder,
+ ObjectPool<UriBuildingContext> pool,
+ RoutePattern pattern,
+ RouteValueDictionary? defaults,
+ IEnumerable<string>? requiredKeys,
+ IEnumerable<(string parameterName, IParameterPolicy policy)>? parameterPolicies)
+ {
+ if (urlEncoder == null)
+ {
+ throw new ArgumentNullException(nameof(urlEncoder));
+ }
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ if (pool == null)
+ {
+ throw new ArgumentNullException(nameof(pool));
+ }
- _urlEncoder = urlEncoder;
- _pool = pool;
- _pattern = pattern;
- _defaults = defaults;
- _requiredKeys = requiredKeys?.ToArray() ?? Array.Empty<string>();
+ if (pattern == null)
+ {
+ throw new ArgumentNullException(nameof(pattern));
+ }
- // Any default that doesn't have a corresponding parameter is a 'filter' and if a value
- // is provided for that 'filter' it must match the value in defaults.
- var filters = new RouteValueDictionary(_defaults);
- for (var i = 0; i < pattern.Parameters.Count; i++)
- {
- filters.Remove(pattern.Parameters[i].Name);
- }
- _filters = filters.ToArray();
+ _urlEncoder = urlEncoder;
+ _pool = pool;
+ _pattern = pattern;
+ _defaults = defaults;
+ _requiredKeys = requiredKeys?.ToArray() ?? Array.Empty<string>();
- _constraints = parameterPolicies
- ?.Where(p => p.policy is IRouteConstraint)
- .Select(p => (p.parameterName, (IRouteConstraint)p.policy))
- .ToArray() ?? Array.Empty<(string, IRouteConstraint)>();
- _parameterTransformers = parameterPolicies
- ?.Where(p => p.policy is IOutboundParameterTransformer)
- .Select(p => (p.parameterName, (IOutboundParameterTransformer)p.policy))
- .ToArray() ?? Array.Empty<(string, IOutboundParameterTransformer)>();
+ // Any default that doesn't have a corresponding parameter is a 'filter' and if a value
+ // is provided for that 'filter' it must match the value in defaults.
+ var filters = new RouteValueDictionary(_defaults);
+ for (var i = 0; i < pattern.Parameters.Count; i++)
+ {
+ filters.Remove(pattern.Parameters[i].Name);
+ }
+ _filters = filters.ToArray();
+
+ _constraints = parameterPolicies
+ ?.Where(p => p.policy is IRouteConstraint)
+ .Select(p => (p.parameterName, (IRouteConstraint)p.policy))
+ .ToArray() ?? Array.Empty<(string, IRouteConstraint)>();
+ _parameterTransformers = parameterPolicies
+ ?.Where(p => p.policy is IOutboundParameterTransformer)
+ .Select(p => (p.parameterName, (IOutboundParameterTransformer)p.policy))
+ .ToArray() ?? Array.Empty<(string, IOutboundParameterTransformer)>();
+
+ _slots = AssignSlots(_pattern, _filters);
+ }
- _slots = AssignSlots(_pattern, _filters);
+ internal TemplateBinder(
+ UrlEncoder urlEncoder,
+ ObjectPool<UriBuildingContext> pool,
+ RoutePattern pattern,
+ IEnumerable<(string parameterName, IParameterPolicy policy)> parameterPolicies)
+ {
+ if (urlEncoder == null)
+ {
+ throw new ArgumentNullException(nameof(urlEncoder));
}
- internal TemplateBinder(
- UrlEncoder urlEncoder,
- ObjectPool<UriBuildingContext> pool,
- RoutePattern pattern,
- IEnumerable<(string parameterName, IParameterPolicy policy)> parameterPolicies)
+ if (pool == null)
{
- if (urlEncoder == null)
- {
- throw new ArgumentNullException(nameof(urlEncoder));
- }
+ throw new ArgumentNullException(nameof(pool));
+ }
- if (pool == null)
- {
- throw new ArgumentNullException(nameof(pool));
- }
+ if (pattern == null)
+ {
+ throw new ArgumentNullException(nameof(pattern));
+ }
- if (pattern == null)
- {
- throw new ArgumentNullException(nameof(pattern));
- }
+ // Parameter policies can be null.
- // Parameter policies can be null.
+ _urlEncoder = urlEncoder;
+ _pool = pool;
+ _pattern = pattern;
+ _defaults = new RouteValueDictionary(pattern.Defaults);
+ _requiredKeys = pattern.RequiredValues.Keys.ToArray();
- _urlEncoder = urlEncoder;
- _pool = pool;
- _pattern = pattern;
- _defaults = new RouteValueDictionary(pattern.Defaults);
- _requiredKeys = pattern.RequiredValues.Keys.ToArray();
+ // Any default that doesn't have a corresponding parameter is a 'filter' and if a value
+ // is provided for that 'filter' it must match the value in defaults.
+ var filters = new RouteValueDictionary(_defaults);
+ for (var i = 0; i < pattern.Parameters.Count; i++)
+ {
+ filters.Remove(pattern.Parameters[i].Name);
+ }
+ _filters = filters.ToArray();
+
+ _constraints = parameterPolicies
+ ?.Where(p => p.policy is IRouteConstraint)
+ .Select(p => (p.parameterName, (IRouteConstraint)p.policy))
+ .ToArray() ?? Array.Empty<(string, IRouteConstraint)>();
+ _parameterTransformers = parameterPolicies
+ ?.Where(p => p.policy is IOutboundParameterTransformer)
+ .Select(p => (p.parameterName, (IOutboundParameterTransformer)p.policy))
+ .ToArray() ?? Array.Empty<(string, IOutboundParameterTransformer)>();
+
+ _slots = AssignSlots(_pattern, _filters);
+ }
- // Any default that doesn't have a corresponding parameter is a 'filter' and if a value
- // is provided for that 'filter' it must match the value in defaults.
- var filters = new RouteValueDictionary(_defaults);
- for (var i = 0; i < pattern.Parameters.Count; i++)
+ /// <summary>
+ /// Generates the parameter values in the route.
+ /// </summary>
+ /// <param name="ambientValues">The values associated with the current request.</param>
+ /// <param name="values">The route values to process.</param>
+ /// <returns>A <see cref="TemplateValuesResult"/> instance. Can be null.</returns>
+ public TemplateValuesResult? GetValues(RouteValueDictionary? ambientValues, RouteValueDictionary values)
+ {
+ // Make a new copy of the slots array, we'll use this as 'scratch' space
+ // and then the RVD will take ownership of it.
+ var slots = new KeyValuePair<string, object?>[_slots.Length];
+ Array.Copy(_slots, 0, slots, 0, slots.Length);
+
+ // Keeping track of the number of 'values' we've processed can be used to avoid doing
+ // some expensive 'merge' operations later.
+ var valueProcessedCount = 0;
+
+ // Start by copying all of the values out of the 'values' and into the slots. There's no success
+ // case where we *don't* use all of the 'values' so there's no reason not to do this up front
+ // to avoid visiting the values dictionary again and again.
+ for (var i = 0; i < slots.Length; i++)
+ {
+ var key = slots[i].Key;
+ if (values.TryGetValue(key, out var value))
{
- filters.Remove(pattern.Parameters[i].Name);
+ // We will need to know later if the value in the 'values' was an null value.
+ // This affects how we process ambient values. Since the 'slots' are initialized
+ // with null values, we use the null-object-pattern to track 'explicit null', which means that
+ // null means omitted.
+ value = IsRoutePartNonEmpty(value) ? value : SentinullValue.Instance;
+ slots[i] = new KeyValuePair<string, object?>(key, value);
+
+ // Track the count of processed values - this allows a fast path later.
+ valueProcessedCount++;
}
- _filters = filters.ToArray();
-
- _constraints = parameterPolicies
- ?.Where(p => p.policy is IRouteConstraint)
- .Select(p => (p.parameterName, (IRouteConstraint)p.policy))
- .ToArray() ?? Array.Empty<(string, IRouteConstraint)>();
- _parameterTransformers = parameterPolicies
- ?.Where(p => p.policy is IOutboundParameterTransformer)
- .Select(p => (p.parameterName, (IOutboundParameterTransformer)p.policy))
- .ToArray() ?? Array.Empty<(string, IOutboundParameterTransformer)>();
-
- _slots = AssignSlots(_pattern, _filters);
- }
-
- /// <summary>
- /// Generates the parameter values in the route.
- /// </summary>
- /// <param name="ambientValues">The values associated with the current request.</param>
- /// <param name="values">The route values to process.</param>
- /// <returns>A <see cref="TemplateValuesResult"/> instance. Can be null.</returns>
- public TemplateValuesResult? GetValues(RouteValueDictionary? ambientValues, RouteValueDictionary values)
- {
- // Make a new copy of the slots array, we'll use this as 'scratch' space
- // and then the RVD will take ownership of it.
- var slots = new KeyValuePair<string, object?>[_slots.Length];
- Array.Copy(_slots, 0, slots, 0, slots.Length);
-
- // Keeping track of the number of 'values' we've processed can be used to avoid doing
- // some expensive 'merge' operations later.
- var valueProcessedCount = 0;
-
- // Start by copying all of the values out of the 'values' and into the slots. There's no success
- // case where we *don't* use all of the 'values' so there's no reason not to do this up front
- // to avoid visiting the values dictionary again and again.
- for (var i = 0; i < slots.Length; i++)
+ }
+
+ // In Endpoint Routing, patterns can have logical parameters that appear 'to the left' of
+ // the route template. This governs whether or not the template can be selected (they act like
+ // filters), and whether the remaining ambient values should be used.
+ // should be used.
+ // For example, in case of MVC it flattens out a route template like below
+ // {controller}/{action}/{id?}
+ // to
+ // Products/Index/{id?},
+ // defaults: new { controller = "Products", action = "Index" },
+ // requiredValues: new { controller = "Products", action = "Index" }
+ // In the above example, "controller" and "action" are no longer parameters.
+ var copyAmbientValues = ambientValues != null;
+ if (copyAmbientValues)
+ {
+ var requiredKeys = _requiredKeys;
+ for (var i = 0; i < requiredKeys.Length; i++)
{
- var key = slots[i].Key;
- if (values.TryGetValue(key, out var value))
+ // For each required key, the values and ambient values need to have the same value.
+ var key = requiredKeys[i];
+ var hasExplicitValue = values.TryGetValue(key, out var value);
+
+ if (ambientValues == null || !ambientValues.TryGetValue(key, out var ambientValue))
{
- // We will need to know later if the value in the 'values' was an null value.
- // This affects how we process ambient values. Since the 'slots' are initialized
- // with null values, we use the null-object-pattern to track 'explicit null', which means that
- // null means omitted.
- value = IsRoutePartNonEmpty(value) ? value : SentinullValue.Instance;
- slots[i] = new KeyValuePair<string, object?>(key, value);
-
- // Track the count of processed values - this allows a fast path later.
- valueProcessedCount++;
+ ambientValue = null;
}
- }
- // In Endpoint Routing, patterns can have logical parameters that appear 'to the left' of
- // the route template. This governs whether or not the template can be selected (they act like
- // filters), and whether the remaining ambient values should be used.
- // should be used.
- // For example, in case of MVC it flattens out a route template like below
- // {controller}/{action}/{id?}
- // to
- // Products/Index/{id?},
- // defaults: new { controller = "Products", action = "Index" },
- // requiredValues: new { controller = "Products", action = "Index" }
- // In the above example, "controller" and "action" are no longer parameters.
- var copyAmbientValues = ambientValues != null;
- if (copyAmbientValues)
- {
- var requiredKeys = _requiredKeys;
- for (var i = 0; i < requiredKeys.Length; i++)
+ // For now, only check ambient values with required values that don't have a parameter
+ // Ambient values for parameters are processed below
+ var hasParameter = _pattern.GetParameter(key) != null;
+ if (!hasParameter)
{
- // For each required key, the values and ambient values need to have the same value.
- var key = requiredKeys[i];
- var hasExplicitValue = values.TryGetValue(key, out var value);
-
- if (ambientValues == null || !ambientValues.TryGetValue(key, out var ambientValue))
+ if (!_pattern.RequiredValues.TryGetValue(key, out var requiredValue))
{
- ambientValue = null;
+ throw new InvalidOperationException($"Unable to find required value '{key}' on route pattern.");
}
- // For now, only check ambient values with required values that don't have a parameter
- // Ambient values for parameters are processed below
- var hasParameter = _pattern.GetParameter(key) != null;
- if (!hasParameter)
+ if (!RoutePartsEqual(ambientValue, _pattern.RequiredValues[key]) &&
+ !RoutePattern.IsRequiredValueAny(_pattern.RequiredValues[key]))
{
- if (!_pattern.RequiredValues.TryGetValue(key, out var requiredValue))
- {
- throw new InvalidOperationException($"Unable to find required value '{key}' on route pattern.");
- }
-
- if (!RoutePartsEqual(ambientValue, _pattern.RequiredValues[key]) &&
- !RoutePattern.IsRequiredValueAny(_pattern.RequiredValues[key]))
- {
- copyAmbientValues = false;
- break;
- }
+ copyAmbientValues = false;
+ break;
+ }
- if (hasExplicitValue && !RoutePartsEqual(value, ambientValue))
- {
- copyAmbientValues = false;
- break;
- }
+ if (hasExplicitValue && !RoutePartsEqual(value, ambientValue))
+ {
+ copyAmbientValues = false;
+ break;
}
}
}
+ }
- // We can now process the rest of the parameters (from left to right) and copy the ambient
- // values as long as the conditions are met.
- //
- // Find out which entries in the URI are valid for the URI we want to generate.
- // If the URI had ordered parameters a="1", b="2", c="3" and the new values
- // specified that b="9", then we need to invalidate everything after it. The new
- // values should then be a="1", b="9", c=<no value>.
- //
- // We also handle the case where a parameter is optional but has no value - we shouldn't
- // accept additional parameters that appear *after* that parameter.
- var parameters = _pattern.Parameters;
- var parameterCount = _pattern.Parameters.Count;
- for (var i = 0; i < parameterCount; i++)
- {
- var key = slots[i].Key;
- var value = slots[i].Value;
+ // We can now process the rest of the parameters (from left to right) and copy the ambient
+ // values as long as the conditions are met.
+ //
+ // Find out which entries in the URI are valid for the URI we want to generate.
+ // If the URI had ordered parameters a="1", b="2", c="3" and the new values
+ // specified that b="9", then we need to invalidate everything after it. The new
+ // values should then be a="1", b="9", c=<no value>.
+ //
+ // We also handle the case where a parameter is optional but has no value - we shouldn't
+ // accept additional parameters that appear *after* that parameter.
+ var parameters = _pattern.Parameters;
+ var parameterCount = _pattern.Parameters.Count;
+ for (var i = 0; i < parameterCount; i++)
+ {
+ var key = slots[i].Key;
+ var value = slots[i].Value;
- // Whether or not the value was explicitly provided is signficant when comparing
- // ambient values. Remember that we're using a special sentinel value so that we
- // can tell the difference between an omitted value and an explicitly specified null.
- var hasExplicitValue = value != null;
+ // Whether or not the value was explicitly provided is signficant when comparing
+ // ambient values. Remember that we're using a special sentinel value so that we
+ // can tell the difference between an omitted value and an explicitly specified null.
+ var hasExplicitValue = value != null;
- var hasAmbientValue = false;
- var ambientValue = (object?)null;
+ var hasAmbientValue = false;
+ var ambientValue = (object?)null;
- var parameter = parameters[i];
+ var parameter = parameters[i];
- // We are copying **all** ambient values
- if (copyAmbientValues)
+ // We are copying **all** ambient values
+ if (copyAmbientValues)
+ {
+ hasAmbientValue = ambientValues != null && ambientValues.TryGetValue(key, out ambientValue);
+ if (hasExplicitValue && hasAmbientValue && !RoutePartsEqual(ambientValue, value))
{
- hasAmbientValue = ambientValues != null && ambientValues.TryGetValue(key, out ambientValue);
- if (hasExplicitValue && hasAmbientValue && !RoutePartsEqual(ambientValue, value))
- {
- // Stop copying current values when we find one that doesn't match
- copyAmbientValues = false;
- }
-
- if (!hasExplicitValue &&
- !hasAmbientValue &&
- _defaults?.ContainsKey(parameter.Name) != true)
- {
- // This is an unsatisfied parameter value and there are no defaults. We might still
- // be able to generate a URL but we should stop 'accepting' ambient values.
- //
- // This might be a case like:
- // template: a/{b?}/{c?}
- // ambient: { c = 17 }
- // values: { }
- //
- // We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
- // we can't use it.
- //
- // In the example above we should fall into this block for 'b'.
- copyAmbientValues = false;
- }
+ // Stop copying current values when we find one that doesn't match
+ copyAmbientValues = false;
}
- // This might be an ambient value that matches a required value. We want to use these even if we're
- // not bulk-copying ambient values.
- //
- // This comes up in a case like the following:
- // ambient-values: { page = "/DeleteUser", area = "Admin", }
- // values: { controller = "Home", action = "Index", }
- // pattern: {area}/{controller}/{action}/{id?}
- // required-values: { area = "Admin", controller = "Home", action = "Index", page = (string)null, }
- //
- // OR in plain English... when linking from a page in an area to an action in the same area, it should
- // be possible to use the area as an ambient value.
- if (!copyAmbientValues && !hasExplicitValue && _pattern.RequiredValues.TryGetValue(key, out var requiredValue))
+ if (!hasExplicitValue &&
+ !hasAmbientValue &&
+ _defaults?.ContainsKey(parameter.Name) != true)
{
- hasAmbientValue = ambientValues != null && ambientValues.TryGetValue(key, out ambientValue);
- if (hasAmbientValue &&
- (RoutePartsEqual(requiredValue, ambientValue) || RoutePattern.IsRequiredValueAny(requiredValue)))
- {
- // Treat this an an explicit value to *force it*.
- slots[i] = new KeyValuePair<string, object?>(key, ambientValue);
- hasExplicitValue = true;
- value = ambientValue;
- }
+ // This is an unsatisfied parameter value and there are no defaults. We might still
+ // be able to generate a URL but we should stop 'accepting' ambient values.
+ //
+ // This might be a case like:
+ // template: a/{b?}/{c?}
+ // ambient: { c = 17 }
+ // values: { }
+ //
+ // We can still generate a URL from this ("/a") but we shouldn't accept 'c' because
+ // we can't use it.
+ //
+ // In the example above we should fall into this block for 'b'.
+ copyAmbientValues = false;
}
+ }
- // If the parameter is a match, add it to the list of values we will use for URI generation
- if (hasExplicitValue && !ReferenceEquals(value, SentinullValue.Instance))
- {
- // Already has a value in the list, do nothing
- }
- else if (copyAmbientValues && hasAmbientValue)
+ // This might be an ambient value that matches a required value. We want to use these even if we're
+ // not bulk-copying ambient values.
+ //
+ // This comes up in a case like the following:
+ // ambient-values: { page = "/DeleteUser", area = "Admin", }
+ // values: { controller = "Home", action = "Index", }
+ // pattern: {area}/{controller}/{action}/{id?}
+ // required-values: { area = "Admin", controller = "Home", action = "Index", page = (string)null, }
+ //
+ // OR in plain English... when linking from a page in an area to an action in the same area, it should
+ // be possible to use the area as an ambient value.
+ if (!copyAmbientValues && !hasExplicitValue && _pattern.RequiredValues.TryGetValue(key, out var requiredValue))
+ {
+ hasAmbientValue = ambientValues != null && ambientValues.TryGetValue(key, out ambientValue);
+ if (hasAmbientValue &&
+ (RoutePartsEqual(requiredValue, ambientValue) || RoutePattern.IsRequiredValueAny(requiredValue)))
{
+ // Treat this an an explicit value to *force it*.
slots[i] = new KeyValuePair<string, object?>(key, ambientValue);
+ hasExplicitValue = true;
+ value = ambientValue;
}
- else if (parameter.IsOptional || parameter.IsCatchAll)
- {
- // Value isn't needed for optional or catchall parameters - wipe out the key, so it
- // will be omitted from the RVD.
- slots[i] = default;
- }
- else if (_defaults != null && _defaults.TryGetValue(parameter.Name, out var defaultValue))
- {
+ }
- // Add the default value only if there isn't already a new value for it and
- // only if it actually has a default value.
- slots[i] = new KeyValuePair<string, object?>(key, defaultValue);
- }
- else
- {
- // If we get here, this parameter needs a value, but doesn't have one. This is a
- // failure case.
- return null;
- }
+ // If the parameter is a match, add it to the list of values we will use for URI generation
+ if (hasExplicitValue && !ReferenceEquals(value, SentinullValue.Instance))
+ {
+ // Already has a value in the list, do nothing
}
+ else if (copyAmbientValues && hasAmbientValue)
+ {
+ slots[i] = new KeyValuePair<string, object?>(key, ambientValue);
+ }
+ else if (parameter.IsOptional || parameter.IsCatchAll)
+ {
+ // Value isn't needed for optional or catchall parameters - wipe out the key, so it
+ // will be omitted from the RVD.
+ slots[i] = default;
+ }
+ else if (_defaults != null && _defaults.TryGetValue(parameter.Name, out var defaultValue))
+ {
- // Any default values that don't appear as parameters are treated like filters. Any new values
- // provided must match these defaults.
- var filters = _filters;
- for (var i = 0; i < filters.Length; i++)
+ // Add the default value only if there isn't already a new value for it and
+ // only if it actually has a default value.
+ slots[i] = new KeyValuePair<string, object?>(key, defaultValue);
+ }
+ else
{
- var key = filters[i].Key;
- var value = slots[i + parameterCount].Value;
+ // If we get here, this parameter needs a value, but doesn't have one. This is a
+ // failure case.
+ return null;
+ }
+ }
- // We use a sentinel value here so we can track the different between omission and explicit null.
- // 'real null' means that the value was omitted.
- var hasExplictValue = value != null;
- if (hasExplictValue)
- {
- // If there is a non-parameterized value in the route and there is a
- // new value for it and it doesn't match, this route won't match.
- if (!RoutePartsEqual(value, filters[i].Value))
- {
- return null;
- }
- }
- else
+ // Any default values that don't appear as parameters are treated like filters. Any new values
+ // provided must match these defaults.
+ var filters = _filters;
+ for (var i = 0; i < filters.Length; i++)
+ {
+ var key = filters[i].Key;
+ var value = slots[i + parameterCount].Value;
+
+ // We use a sentinel value here so we can track the different between omission and explicit null.
+ // 'real null' means that the value was omitted.
+ var hasExplictValue = value != null;
+ if (hasExplictValue)
+ {
+ // If there is a non-parameterized value in the route and there is a
+ // new value for it and it doesn't match, this route won't match.
+ if (!RoutePartsEqual(value, filters[i].Value))
{
- // If no value was provided, then blank out this slot so that it doesn't show up in accepted values.
- slots[i + parameterCount] = default;
+ return null;
}
}
+ else
+ {
+ // If no value was provided, then blank out this slot so that it doesn't show up in accepted values.
+ slots[i + parameterCount] = default;
+ }
+ }
- // At this point we've captured all of the 'known' route values, but we have't
- // handled an extra route values that were provided in 'values'. These all
- // need to be included in the accepted values.
- var acceptedValues = RouteValueDictionary.FromArray(slots);
+ // At this point we've captured all of the 'known' route values, but we have't
+ // handled an extra route values that were provided in 'values'. These all
+ // need to be included in the accepted values.
+ var acceptedValues = RouteValueDictionary.FromArray(slots);
- if (valueProcessedCount < values.Count)
+ if (valueProcessedCount < values.Count)
+ {
+ // There are some values in 'value' that are unaccounted for, merge them into
+ // the dictionary.
+ foreach (var kvp in values)
{
- // There are some values in 'value' that are unaccounted for, merge them into
- // the dictionary.
- foreach (var kvp in values)
+ if (!_defaults!.ContainsKey(kvp.Key))
{
- if (!_defaults!.ContainsKey(kvp.Key))
- {
#if RVD_TryAdd
acceptedValues.TryAdd(kvp.Key, kvp.Value);
#else
- if (!acceptedValues.ContainsKey(kvp.Key))
- {
- acceptedValues.Add(kvp.Key, kvp.Value);
- }
-#endif
+ if (!acceptedValues.ContainsKey(kvp.Key))
+ {
+ acceptedValues.Add(kvp.Key, kvp.Value);
}
+#endif
}
}
+ }
- // Currently this copy is required because BindValues will mutate the accepted values :(
- var combinedValues = new RouteValueDictionary(acceptedValues);
-
- // Add any ambient values that don't match parameters - they need to be visible to constraints
- // but they will ignored by link generation.
- CopyNonParameterAmbientValues(
- ambientValues: ambientValues,
- acceptedValues: acceptedValues,
- combinedValues: combinedValues);
+ // Currently this copy is required because BindValues will mutate the accepted values :(
+ var combinedValues = new RouteValueDictionary(acceptedValues);
- return new TemplateValuesResult()
- {
- AcceptedValues = acceptedValues,
- CombinedValues = combinedValues,
- };
- }
-
- // Step 1.5: Process constraints
- /// <summary>
- /// Processes the constraints **if** they were passed in to the TemplateBinder constructor.
- /// </summary>
- /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
- /// <param name="combinedValues">A dictionary that contains the parameters for the route.</param>
- /// <param name="parameterName">The name of the parameter.</param>
- /// <param name="constraint">The constraint object.</param>
- /// <returns><see langword="true"/> if constraints were processed succesfully and false otherwise.</returns>
- public bool TryProcessConstraints(HttpContext? httpContext, RouteValueDictionary combinedValues, out string? parameterName, out IRouteConstraint? constraint)
- {
- var constraints = _constraints;
- for (var i = 0; i < constraints.Length; i++)
- {
- (parameterName, constraint) = constraints[i];
-
- if (!constraint.Match(httpContext, NullRouter.Instance, parameterName, combinedValues, RouteDirection.UrlGeneration))
- {
- return false;
- }
- }
+ // Add any ambient values that don't match parameters - they need to be visible to constraints
+ // but they will ignored by link generation.
+ CopyNonParameterAmbientValues(
+ ambientValues: ambientValues,
+ acceptedValues: acceptedValues,
+ combinedValues: combinedValues);
- parameterName = null;
- constraint = null;
- return true;
- }
+ return new TemplateValuesResult()
+ {
+ AcceptedValues = acceptedValues,
+ CombinedValues = combinedValues,
+ };
+ }
- // Step 2: If the route is a match generate the appropriate URI
- /// <summary>
- /// Returns a string representation of the URI associated with the route.
- /// </summary>
- /// <param name="acceptedValues">A dictionary that contains the parameters for the route.</param>
- /// <returns>The string representation of the route.</returns>
- public string? BindValues(RouteValueDictionary acceptedValues)
+ // Step 1.5: Process constraints
+ /// <summary>
+ /// Processes the constraints **if** they were passed in to the TemplateBinder constructor.
+ /// </summary>
+ /// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
+ /// <param name="combinedValues">A dictionary that contains the parameters for the route.</param>
+ /// <param name="parameterName">The name of the parameter.</param>
+ /// <param name="constraint">The constraint object.</param>
+ /// <returns><see langword="true"/> if constraints were processed succesfully and false otherwise.</returns>
+ public bool TryProcessConstraints(HttpContext? httpContext, RouteValueDictionary combinedValues, out string? parameterName, out IRouteConstraint? constraint)
+ {
+ var constraints = _constraints;
+ for (var i = 0; i < constraints.Length; i++)
{
- var context = _pool.Get();
+ (parameterName, constraint) = constraints[i];
- try
- {
- return TryBindValuesCore(context, acceptedValues) ? context.ToString() : null;
- }
- finally
+ if (!constraint.Match(httpContext, NullRouter.Instance, parameterName, combinedValues, RouteDirection.UrlGeneration))
{
- _pool.Return(context);
+ return false;
}
}
- // Step 2: If the route is a match generate the appropriate URI
- internal bool TryBindValues(
- RouteValueDictionary acceptedValues,
- LinkOptions? options,
- LinkOptions globalOptions,
- out (PathString path, QueryString query) result)
+ parameterName = null;
+ constraint = null;
+ return true;
+ }
+
+ // Step 2: If the route is a match generate the appropriate URI
+ /// <summary>
+ /// Returns a string representation of the URI associated with the route.
+ /// </summary>
+ /// <param name="acceptedValues">A dictionary that contains the parameters for the route.</param>
+ /// <returns>The string representation of the route.</returns>
+ public string? BindValues(RouteValueDictionary acceptedValues)
+ {
+ var context = _pool.Get();
+
+ try
+ {
+ return TryBindValuesCore(context, acceptedValues) ? context.ToString() : null;
+ }
+ finally
{
- var context = _pool.Get();
+ _pool.Return(context);
+ }
+ }
- context.AppendTrailingSlash = options?.AppendTrailingSlash ?? globalOptions.AppendTrailingSlash ?? false;
- context.LowercaseQueryStrings = options?.LowercaseQueryStrings ?? globalOptions.LowercaseQueryStrings ?? false;
- context.LowercaseUrls = options?.LowercaseUrls ?? globalOptions.LowercaseUrls ?? false;
+ // Step 2: If the route is a match generate the appropriate URI
+ internal bool TryBindValues(
+ RouteValueDictionary acceptedValues,
+ LinkOptions? options,
+ LinkOptions globalOptions,
+ out (PathString path, QueryString query) result)
+ {
+ var context = _pool.Get();
- try
- {
- if (TryBindValuesCore(context, acceptedValues))
- {
- result = (context.ToPathString(), context.ToQueryString());
- return true;
- }
+ context.AppendTrailingSlash = options?.AppendTrailingSlash ?? globalOptions.AppendTrailingSlash ?? false;
+ context.LowercaseQueryStrings = options?.LowercaseQueryStrings ?? globalOptions.LowercaseQueryStrings ?? false;
+ context.LowercaseUrls = options?.LowercaseUrls ?? globalOptions.LowercaseUrls ?? false;
- result = default;
- return false;
- }
- finally
+ try
+ {
+ if (TryBindValuesCore(context, acceptedValues))
{
- _pool.Return(context);
+ result = (context.ToPathString(), context.ToQueryString());
+ return true;
}
+
+ result = default;
+ return false;
+ }
+ finally
+ {
+ _pool.Return(context);
}
+ }
- private bool TryBindValuesCore(UriBuildingContext context, RouteValueDictionary acceptedValues)
+ private bool TryBindValuesCore(UriBuildingContext context, RouteValueDictionary acceptedValues)
+ {
+ // If we have any output parameter transformers, allow them a chance to influence the parameter values
+ // before we build the URI.
+ var parameterTransformers = _parameterTransformers;
+ for (var i = 0; i < parameterTransformers.Length; i++)
{
- // If we have any output parameter transformers, allow them a chance to influence the parameter values
- // before we build the URI.
- var parameterTransformers = _parameterTransformers;
- for (var i = 0; i < parameterTransformers.Length; i++)
+ (var parameterName, var transformer) = parameterTransformers[i];
+ if (acceptedValues.TryGetValue(parameterName, out var value))
{
- (var parameterName, var transformer) = parameterTransformers[i];
- if (acceptedValues.TryGetValue(parameterName, out var value))
- {
- acceptedValues[parameterName] = transformer.TransformOutbound(value);
- }
+ acceptedValues[parameterName] = transformer.TransformOutbound(value);
}
+ }
+
+ var segments = _pattern.PathSegments;
+ // Read interface .Count once rather than per iteration
+ var segmentsCount = segments.Count;
+ for (var i = 0; i < segmentsCount; i++)
+ {
+ Debug.Assert(context.BufferState == SegmentState.Beginning);
+ Debug.Assert(context.UriState == SegmentState.Beginning);
- var segments = _pattern.PathSegments;
+ var parts = segments[i].Parts;
// Read interface .Count once rather than per iteration
- var segmentsCount = segments.Count;
- for (var i = 0; i < segmentsCount; i++)
+ var partsCount = parts.Count;
+ for (var j = 0; j < partsCount; j++)
{
- Debug.Assert(context.BufferState == SegmentState.Beginning);
- Debug.Assert(context.UriState == SegmentState.Beginning);
-
- var parts = segments[i].Parts;
- // Read interface .Count once rather than per iteration
- var partsCount = parts.Count;
- for (var j = 0; j < partsCount; j++)
+ var part = parts[j];
+ if (part is RoutePatternLiteralPart literalPart)
{
- var part = parts[j];
- if (part is RoutePatternLiteralPart literalPart)
+ if (!context.Accept(literalPart.Content))
{
- if (!context.Accept(literalPart.Content))
- {
- return false;
- }
+ return false;
+ }
+ }
+ else if (part is RoutePatternSeparatorPart separatorPart)
+ {
+ if (!context.Accept(separatorPart.Content))
+ {
+ return false;
}
- else if (part is RoutePatternSeparatorPart separatorPart)
+ }
+ else if (part is RoutePatternParameterPart parameterPart)
+ {
+ // If it's a parameter, get its value
+ acceptedValues.Remove(parameterPart.Name, out var value);
+
+ var isSameAsDefault = false;
+ if (_defaults != null &&
+ _defaults.TryGetValue(parameterPart.Name, out var defaultValue) &&
+ RoutePartsEqual(value, defaultValue))
{
- if (!context.Accept(separatorPart.Content))
+ isSameAsDefault = true;
+ }
+
+ var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
+ if (isSameAsDefault)
+ {
+ // If the accepted value is the same as the default value buffer it since
+ // we won't necessarily add it to the URI we generate.
+ if (!context.Buffer(converted))
{
return false;
}
}
- else if (part is RoutePatternParameterPart parameterPart)
+ else
{
- // If it's a parameter, get its value
- acceptedValues.Remove(parameterPart.Name, out var value);
-
- var isSameAsDefault = false;
- if (_defaults != null &&
- _defaults.TryGetValue(parameterPart.Name, out var defaultValue) &&
- RoutePartsEqual(value, defaultValue))
+ // If the value is not accepted, it is null or empty value in the
+ // middle of the segment. We accept this if the parameter is an
+ // optional parameter and it is preceded by an optional seperator.
+ // In this case, we need to remove the optional seperator that we
+ // have added to the URI
+ // Example: template = {id}.{format?}. parameters: id=5
+ // In this case after we have generated "5.", we wont find any value
+ // for format, so we remove '.' and generate 5.
+ if (!context.Accept(converted, parameterPart.EncodeSlashes))
{
- isSameAsDefault = true;
- }
-
- var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
- if (isSameAsDefault)
- {
- // If the accepted value is the same as the default value buffer it since
- // we won't necessarily add it to the URI we generate.
- if (!context.Buffer(converted))
+ RoutePatternSeparatorPart? nullablePart;
+ if (j != 0 && parameterPart.IsOptional && (nullablePart = parts[j - 1] as RoutePatternSeparatorPart) != null)
{
- return false;
+ separatorPart = nullablePart;
+ context.Remove(separatorPart.Content);
}
- }
- else
- {
- // If the value is not accepted, it is null or empty value in the
- // middle of the segment. We accept this if the parameter is an
- // optional parameter and it is preceded by an optional seperator.
- // In this case, we need to remove the optional seperator that we
- // have added to the URI
- // Example: template = {id}.{format?}. parameters: id=5
- // In this case after we have generated "5.", we wont find any value
- // for format, so we remove '.' and generate 5.
- if (!context.Accept(converted, parameterPart.EncodeSlashes))
+ else
{
- RoutePatternSeparatorPart? nullablePart;
- if (j != 0 && parameterPart.IsOptional && (nullablePart = parts[j - 1] as RoutePatternSeparatorPart) != null)
- {
- separatorPart = nullablePart;
- context.Remove(separatorPart.Content);
- }
- else
- {
- return false;
- }
+ return false;
}
}
}
}
-
- context.EndSegment();
- }
-
- // Generate the query string from the remaining values
- var wroteFirst = false;
- foreach (var kvp in acceptedValues)
- {
- if (_defaults != null && _defaults.ContainsKey(kvp.Key))
- {
- // This value is a 'filter' we don't need to put it in the query string.
- continue;
- }
-
- var values = kvp.Value as IEnumerable;
- if (values != null && !(values is string))
- {
- foreach (var value in values)
- {
- wroteFirst |= AddQueryKeyValueToContext(context, kvp.Key, value, wroteFirst);
- }
- }
- else
- {
- wroteFirst |= AddQueryKeyValueToContext(context, kvp.Key, kvp.Value, wroteFirst);
- }
}
- return true;
+ context.EndSegment();
}
- private bool AddQueryKeyValueToContext(UriBuildingContext context, string key, object? value, bool wroteFirst)
+ // Generate the query string from the remaining values
+ var wroteFirst = false;
+ foreach (var kvp in acceptedValues)
{
- var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
- if (!string.IsNullOrEmpty(converted))
+ if (_defaults != null && _defaults.ContainsKey(kvp.Key))
{
- if (context.LowercaseQueryStrings)
- {
- key = key.ToLowerInvariant();
- converted = converted.ToLowerInvariant();
- }
-
- context.QueryWriter.Write(wroteFirst ? '&' : '?');
- _urlEncoder.Encode(context.QueryWriter, key);
- context.QueryWriter.Write('=');
- _urlEncoder.Encode(context.QueryWriter, converted);
- return true;
+ // This value is a 'filter' we don't need to put it in the query string.
+ continue;
}
- return false;
- }
- /// <summary>
- /// Compares two objects for equality as parts of a case-insensitive path.
- /// </summary>
- /// <param name="a">An object to compare.</param>
- /// <param name="b">An object to compare.</param>
- /// <returns>True if the object are equal, otherwise false.</returns>
- public static bool RoutePartsEqual(object? a, object? b)
- {
- var sa = a as string ?? (ReferenceEquals(SentinullValue.Instance, a) ? string.Empty : null);
- var sb = b as string ?? (ReferenceEquals(SentinullValue.Instance, b) ? string.Empty : null);
-
- // In case of strings, consider empty and null the same.
- // Since null cannot tell us the type, consider it to be a string if the other value is a string.
- if ((sa == string.Empty && sb == null) || (sb == string.Empty && sa == null))
- {
- return true;
- }
- else if (sa != null && sb != null)
+ var values = kvp.Value as IEnumerable;
+ if (values != null && !(values is string))
{
- // For strings do a case-insensitive comparison
- return string.Equals(sa, sb, StringComparison.OrdinalIgnoreCase);
+ foreach (var value in values)
+ {
+ wroteFirst |= AddQueryKeyValueToContext(context, kvp.Key, value, wroteFirst);
+ }
}
else
{
- if (a != null && b != null)
- {
- // Explicitly call .Equals() in case it is overridden in the type
- return a.Equals(b);
- }
- else
- {
- // At least one of them is null. Return true if they both are
- return a == b;
- }
+ wroteFirst |= AddQueryKeyValueToContext(context, kvp.Key, kvp.Value, wroteFirst);
}
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsRoutePartNonEmpty(object? part)
+ return true;
+ }
+
+ private bool AddQueryKeyValueToContext(UriBuildingContext context, string key, object? value, bool wroteFirst)
+ {
+ var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
+ if (!string.IsNullOrEmpty(converted))
{
- if (part == null)
+ if (context.LowercaseQueryStrings)
{
- return false;
+ key = key.ToLowerInvariant();
+ converted = converted.ToLowerInvariant();
}
- if (ReferenceEquals(SentinullValue.Instance, part))
- {
- return false;
- }
+ context.QueryWriter.Write(wroteFirst ? '&' : '?');
+ _urlEncoder.Encode(context.QueryWriter, key);
+ context.QueryWriter.Write('=');
+ _urlEncoder.Encode(context.QueryWriter, converted);
+ return true;
+ }
+ return false;
+ }
- if (part is string stringPart && stringPart.Length == 0)
- {
- return false;
- }
+ /// <summary>
+ /// Compares two objects for equality as parts of a case-insensitive path.
+ /// </summary>
+ /// <param name="a">An object to compare.</param>
+ /// <param name="b">An object to compare.</param>
+ /// <returns>True if the object are equal, otherwise false.</returns>
+ public static bool RoutePartsEqual(object? a, object? b)
+ {
+ var sa = a as string ?? (ReferenceEquals(SentinullValue.Instance, a) ? string.Empty : null);
+ var sb = b as string ?? (ReferenceEquals(SentinullValue.Instance, b) ? string.Empty : null);
+ // In case of strings, consider empty and null the same.
+ // Since null cannot tell us the type, consider it to be a string if the other value is a string.
+ if ((sa == string.Empty && sb == null) || (sb == string.Empty && sa == null))
+ {
return true;
}
-
- private void CopyNonParameterAmbientValues(
- RouteValueDictionary? ambientValues,
- RouteValueDictionary acceptedValues,
- RouteValueDictionary combinedValues)
+ else if (sa != null && sb != null)
{
- if (ambientValues == null)
+ // For strings do a case-insensitive comparison
+ return string.Equals(sa, sb, StringComparison.OrdinalIgnoreCase);
+ }
+ else
+ {
+ if (a != null && b != null)
{
- return;
+ // Explicitly call .Equals() in case it is overridden in the type
+ return a.Equals(b);
}
-
- foreach (var kvp in ambientValues)
+ else
{
- if (IsRoutePartNonEmpty(kvp.Value))
- {
- var parameter = _pattern.GetParameter(kvp.Key);
- if (parameter == null && !acceptedValues.ContainsKey(kvp.Key))
- {
- combinedValues.Add(kvp.Key, kvp.Value);
- }
- }
+ // At least one of them is null. Return true if they both are
+ return a == b;
}
}
+ }
- private static KeyValuePair<string, object?>[] AssignSlots(RoutePattern pattern, KeyValuePair<string, object?>[] filters)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsRoutePartNonEmpty(object? part)
+ {
+ if (part == null)
{
- var slots = new KeyValuePair<string, object?>[pattern.Parameters.Count + filters.Length];
+ return false;
+ }
- for (var i = 0; i < pattern.Parameters.Count; i++)
- {
- slots[i] = new KeyValuePair<string, object?>(pattern.Parameters[i].Name, null);
- }
+ if (ReferenceEquals(SentinullValue.Instance, part))
+ {
+ return false;
+ }
+
+ if (part is string stringPart && stringPart.Length == 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void CopyNonParameterAmbientValues(
+ RouteValueDictionary? ambientValues,
+ RouteValueDictionary acceptedValues,
+ RouteValueDictionary combinedValues)
+ {
+ if (ambientValues == null)
+ {
+ return;
+ }
- for (var i = 0; i < filters.Length; i++)
+ foreach (var kvp in ambientValues)
+ {
+ if (IsRoutePartNonEmpty(kvp.Value))
{
- slots[i + pattern.Parameters.Count] = new KeyValuePair<string, object?>(filters[i].Key, null);
+ var parameter = _pattern.GetParameter(kvp.Key);
+ if (parameter == null && !acceptedValues.ContainsKey(kvp.Key))
+ {
+ combinedValues.Add(kvp.Key, kvp.Value);
+ }
}
+ }
+ }
+
+ private static KeyValuePair<string, object?>[] AssignSlots(RoutePattern pattern, KeyValuePair<string, object?>[] filters)
+ {
+ var slots = new KeyValuePair<string, object?>[pattern.Parameters.Count + filters.Length];
- return slots;
+ for (var i = 0; i < pattern.Parameters.Count; i++)
+ {
+ slots[i] = new KeyValuePair<string, object?>(pattern.Parameters[i].Name, null);
}
- // This represents an 'explicit null' in the slots array.
- [DebuggerDisplay("explicit null")]
- private class SentinullValue
+ for (var i = 0; i < filters.Length; i++)
{
- public static object Instance = new SentinullValue();
+ slots[i + pattern.Parameters.Count] = new KeyValuePair<string, object?>(filters[i].Key, null);
+ }
- private SentinullValue()
- {
- }
+ return slots;
+ }
- public override string ToString() => string.Empty;
+ // This represents an 'explicit null' in the slots array.
+ [DebuggerDisplay("explicit null")]
+ private class SentinullValue
+ {
+ public static object Instance = new SentinullValue();
+
+ private SentinullValue()
+ {
}
+
+ public override string ToString() => string.Empty;
}
}
diff --git a/src/Http/Routing/src/Template/TemplateBinderFactory.cs b/src/Http/Routing/src/Template/TemplateBinderFactory.cs
index fef822a7da..ad14113ddd 100644
--- a/src/Http/Routing/src/Template/TemplateBinderFactory.cs
+++ b/src/Http/Routing/src/Template/TemplateBinderFactory.cs
@@ -3,27 +3,26 @@
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// A factory used to create <see cref="TemplateBinder"/> instances.
+/// </summary>
+public abstract class TemplateBinderFactory
{
/// <summary>
- /// A factory used to create <see cref="TemplateBinder"/> instances.
+ /// Creates a new <see cref="TemplateBinder"/> from the provided <paramref name="template"/> and
+ /// <paramref name="defaults"/>.
/// </summary>
- public abstract class TemplateBinderFactory
- {
- /// <summary>
- /// Creates a new <see cref="TemplateBinder"/> from the provided <paramref name="template"/> and
- /// <paramref name="defaults"/>.
- /// </summary>
- /// <param name="template">The route template.</param>
- /// <param name="defaults">A collection of extra default values that do not appear in the route template.</param>
- /// <returns>A <see cref="TemplateBinder"/>.</returns>
- public abstract TemplateBinder Create(RouteTemplate template, RouteValueDictionary defaults);
+ /// <param name="template">The route template.</param>
+ /// <param name="defaults">A collection of extra default values that do not appear in the route template.</param>
+ /// <returns>A <see cref="TemplateBinder"/>.</returns>
+ public abstract TemplateBinder Create(RouteTemplate template, RouteValueDictionary defaults);
- /// <summary>
- /// Creates a new <see cref="TemplateBinder"/> from the provided <paramref name="pattern"/>.
- /// </summary>
- /// <param name="pattern">The <see cref="RoutePattern"/>.</param>
- /// <returns>A <see cref="TemplateBinder"/>.</returns>
- public abstract TemplateBinder Create(RoutePattern pattern);
- }
+ /// <summary>
+ /// Creates a new <see cref="TemplateBinder"/> from the provided <paramref name="pattern"/>.
+ /// </summary>
+ /// <param name="pattern">The <see cref="RoutePattern"/>.</param>
+ /// <returns>A <see cref="TemplateBinder"/>.</returns>
+ public abstract TemplateBinder Create(RoutePattern pattern);
}
diff --git a/src/Http/Routing/src/Template/TemplateMatcher.cs b/src/Http/Routing/src/Template/TemplateMatcher.cs
index 657a7f0e97..5cf49d7a96 100644
--- a/src/Http/Routing/src/Template/TemplateMatcher.cs
+++ b/src/Http/Routing/src/Template/TemplateMatcher.cs
@@ -4,93 +4,92 @@
#nullable enable
using System;
-using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Supports matching paths to route templates and extracting parameter values.
+/// </summary>
+public class TemplateMatcher
{
+ // Perf: This is a cache to avoid looking things up in 'Defaults' each request.
+ private readonly bool[] _hasDefaultValue;
+ private readonly object?[] _defaultValues;
+
+ private readonly RoutePatternMatcher _routePatternMatcher;
+
/// <summary>
- /// Supports matching paths to route templates and extracting parameter values.
+ /// Creates a new <see cref="TemplateMatcher"/> instance given a <paramref name="template"/> and <paramref name="defaults"/>.
/// </summary>
- public class TemplateMatcher
+ /// <param name="template">The <see cref="RouteTemplate"/> to compare against.</param>
+ /// <param name="defaults">The default values for parameters in the <paramref name="template"/>.</param>
+ public TemplateMatcher(
+ RouteTemplate template,
+ RouteValueDictionary defaults)
{
- // Perf: This is a cache to avoid looking things up in 'Defaults' each request.
- private readonly bool[] _hasDefaultValue;
- private readonly object?[] _defaultValues;
-
- private readonly RoutePatternMatcher _routePatternMatcher;
-
- /// <summary>
- /// Creates a new <see cref="TemplateMatcher"/> instance given a <paramref name="template"/> and <paramref name="defaults"/>.
- /// </summary>
- /// <param name="template">The <see cref="RouteTemplate"/> to compare against.</param>
- /// <param name="defaults">The default values for parameters in the <paramref name="template"/>.</param>
- public TemplateMatcher(
- RouteTemplate template,
- RouteValueDictionary defaults)
+ if (template == null)
{
- if (template == null)
- {
- throw new ArgumentNullException(nameof(template));
- }
+ throw new ArgumentNullException(nameof(template));
+ }
- Template = template;
- Defaults = defaults ?? new RouteValueDictionary();
+ Template = template;
+ Defaults = defaults ?? new RouteValueDictionary();
- // Perf: cache the default value for each parameter (other than complex segments).
- _hasDefaultValue = new bool[Template.Segments.Count];
- _defaultValues = new object[Template.Segments.Count];
+ // Perf: cache the default value for each parameter (other than complex segments).
+ _hasDefaultValue = new bool[Template.Segments.Count];
+ _defaultValues = new object[Template.Segments.Count];
- for (var i = 0; i < Template.Segments.Count; i++)
+ for (var i = 0; i < Template.Segments.Count; i++)
+ {
+ var segment = Template.Segments[i];
+ if (!segment.IsSimple)
{
- var segment = Template.Segments[i];
- if (!segment.IsSimple)
- {
- continue;
- }
-
- var part = segment.Parts[0];
- if (!part.IsParameter)
- {
- continue;
- }
-
- if (Defaults.TryGetValue(part.Name!, out var value))
- {
- _hasDefaultValue[i] = true;
- _defaultValues[i] = value;
- }
+ continue;
}
- var routePattern = Template.ToRoutePattern();
- _routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults);
- }
+ var part = segment.Parts[0];
+ if (!part.IsParameter)
+ {
+ continue;
+ }
- /// <summary>
- /// Gets the default values for parameters in the <see cref="Template"/>.
- /// </summary>
- public RouteValueDictionary Defaults { get; }
-
- /// <summary>
- /// Gets the <see cref="RouteTemplate"/> to match against.
- /// </summary>
- public RouteTemplate Template { get; }
-
- /// <summary>
- /// Evaluates if the provided <paramref name="path"/> matches the <see cref="Template"/>. Populates
- /// <paramref name="values"/> with parameter values.
- /// </summary>
- /// <param name="path">A <see cref="PathString"/> representing the route to match.</param>
- /// <param name="values">A <see cref="RouteValueDictionary"/> to populate with parameter values.</param>
- /// <returns><see langword="true"/> if <paramref name="path"/> matches <see cref="Template"/>.</returns>
- public bool TryMatch(PathString path, RouteValueDictionary values)
- {
- if (values == null)
+ if (Defaults.TryGetValue(part.Name!, out var value))
{
- throw new ArgumentNullException(nameof(values));
+ _hasDefaultValue[i] = true;
+ _defaultValues[i] = value;
}
+ }
- return _routePatternMatcher.TryMatch(path, values);
+ var routePattern = Template.ToRoutePattern();
+ _routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults);
+ }
+
+ /// <summary>
+ /// Gets the default values for parameters in the <see cref="Template"/>.
+ /// </summary>
+ public RouteValueDictionary Defaults { get; }
+
+ /// <summary>
+ /// Gets the <see cref="RouteTemplate"/> to match against.
+ /// </summary>
+ public RouteTemplate Template { get; }
+
+ /// <summary>
+ /// Evaluates if the provided <paramref name="path"/> matches the <see cref="Template"/>. Populates
+ /// <paramref name="values"/> with parameter values.
+ /// </summary>
+ /// <param name="path">A <see cref="PathString"/> representing the route to match.</param>
+ /// <param name="values">A <see cref="RouteValueDictionary"/> to populate with parameter values.</param>
+ /// <returns><see langword="true"/> if <paramref name="path"/> matches <see cref="Template"/>.</returns>
+ public bool TryMatch(PathString path, RouteValueDictionary values)
+ {
+ if (values == null)
+ {
+ throw new ArgumentNullException(nameof(values));
}
+
+ return _routePatternMatcher.TryMatch(path, values);
}
}
diff --git a/src/Http/Routing/src/Template/TemplateParser.cs b/src/Http/Routing/src/Template/TemplateParser.cs
index 6a059e5356..0f8544db3e 100644
--- a/src/Http/Routing/src/Template/TemplateParser.cs
+++ b/src/Http/Routing/src/Template/TemplateParser.cs
@@ -4,35 +4,34 @@
using System;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Provides methods for parsing route template strings.
+/// </summary>
+public static class TemplateParser
{
/// <summary>
- /// Provides methods for parsing route template strings.
+ /// Creates a <see cref="RouteTemplate"/> for a given <paramref name="routeTemplate"/> string.
/// </summary>
- public static class TemplateParser
+ /// <param name="routeTemplate">A string representation of the route template.</param>
+ /// <returns>A <see cref="RouteTemplate"/> instance.</returns>
+ public static RouteTemplate Parse(string routeTemplate)
{
- /// <summary>
- /// Creates a <see cref="RouteTemplate"/> for a given <paramref name="routeTemplate"/> string.
- /// </summary>
- /// <param name="routeTemplate">A string representation of the route template.</param>
- /// <returns>A <see cref="RouteTemplate"/> instance.</returns>
- public static RouteTemplate Parse(string routeTemplate)
+ if (routeTemplate == null)
{
- if (routeTemplate == null)
- {
- throw new ArgumentNullException(routeTemplate);
- }
+ throw new ArgumentNullException(routeTemplate);
+ }
- try
- {
- var inner = RoutePatternFactory.Parse(routeTemplate);
- return new RouteTemplate(inner);
- }
- catch (RoutePatternException ex)
- {
- // Preserving the existing behavior of this API even though the logic moved.
- throw new ArgumentException(ex.Message, nameof(routeTemplate), ex);
- }
+ try
+ {
+ var inner = RoutePatternFactory.Parse(routeTemplate);
+ return new RouteTemplate(inner);
+ }
+ catch (RoutePatternException ex)
+ {
+ // Preserving the existing behavior of this API even though the logic moved.
+ throw new ArgumentException(ex.Message, nameof(routeTemplate), ex);
}
}
}
diff --git a/src/Http/Routing/src/Template/TemplatePart.cs b/src/Http/Routing/src/Template/TemplatePart.cs
index 9d791d7831..334b3399ba 100644
--- a/src/Http/Routing/src/Template/TemplatePart.cs
+++ b/src/Http/Routing/src/Template/TemplatePart.cs
@@ -8,175 +8,174 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Represents a part of a route template segment.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public class TemplatePart
{
/// <summary>
- /// Represents a part of a route template segment.
+ /// Constructs a new <see cref="TemplatePart"/> instance.
/// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public class TemplatePart
+ public TemplatePart()
{
- /// <summary>
- /// Constructs a new <see cref="TemplatePart"/> instance.
- /// </summary>
- public TemplatePart()
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="TemplatePart"/> instance given a <paramref name="other"/>.
+ /// </summary>
+ /// <param name="other">A <see cref="RoutePatternPart"/> instance representing the route part.</param>
+ public TemplatePart(RoutePatternPart other)
+ {
+ IsLiteral = other.IsLiteral || other.IsSeparator;
+ IsParameter = other.IsParameter;
+
+ if (other.IsLiteral && other is RoutePatternLiteralPart literal)
{
+ Text = literal.Content;
}
-
- /// <summary>
- /// Constructs a new <see cref="TemplatePart"/> instance given a <paramref name="other"/>.
- /// </summary>
- /// <param name="other">A <see cref="RoutePatternPart"/> instance representing the route part.</param>
- public TemplatePart(RoutePatternPart other)
+ else if (other.IsParameter && other is RoutePatternParameterPart parameter)
{
- IsLiteral = other.IsLiteral || other.IsSeparator;
- IsParameter = other.IsParameter;
-
- if (other.IsLiteral && other is RoutePatternLiteralPart literal)
- {
- Text = literal.Content;
- }
- else if (other.IsParameter && other is RoutePatternParameterPart parameter)
- {
- // Text is unused by TemplatePart and assumed to be null when the part is a parameter.
- Name = parameter.Name;
- IsCatchAll = parameter.IsCatchAll;
- IsOptional = parameter.IsOptional;
- DefaultValue = parameter.Default;
- InlineConstraints = parameter.ParameterPolicies?.Select(p => new InlineConstraint(p)) ?? Enumerable.Empty<InlineConstraint>();
- }
- else if (other.IsSeparator && other is RoutePatternSeparatorPart separator)
- {
- Text = separator.Content;
- IsOptionalSeperator = true;
- }
- else
- {
- // Unreachable
- throw new NotSupportedException();
- }
+ // Text is unused by TemplatePart and assumed to be null when the part is a parameter.
+ Name = parameter.Name;
+ IsCatchAll = parameter.IsCatchAll;
+ IsOptional = parameter.IsOptional;
+ DefaultValue = parameter.Default;
+ InlineConstraints = parameter.ParameterPolicies?.Select(p => new InlineConstraint(p)) ?? Enumerable.Empty<InlineConstraint>();
}
-
- /// <summary>
- /// Create a <see cref="TemplatePart"/> representing a literal route part.
- /// </summary>
- /// <param name="text">The text of the literate route part.</param>
- /// <returns>A <see cref="TemplatePart"/> instance.</returns>
- public static TemplatePart CreateLiteral(string text)
+ else if (other.IsSeparator && other is RoutePatternSeparatorPart separator)
{
- return new TemplatePart()
- {
- IsLiteral = true,
- Text = text,
- };
+ Text = separator.Content;
+ IsOptionalSeperator = true;
}
+ else
+ {
+ // Unreachable
+ throw new NotSupportedException();
+ }
+ }
- /// <summary>
- /// Creates a <see cref="TemplatePart"/> representing a parameter part.
- /// </summary>
- /// <param name="name">The name of the parameter.</param>
- /// <param name="isCatchAll"><see langword="true"/> if the parameter is a catch-all parameter.</param>
- /// <param name="isOptional"><see langword="true"/> if the parameter is an optional parameter.</param>
- /// <param name="defaultValue">The default value of the parameter.</param>
- /// <param name="inlineConstraints">A collection of constraints associated with the parameter.</param>
- /// <returns>A <see cref="TemplatePart"/> instance.</returns>
- public static TemplatePart CreateParameter(
- string name,
- bool isCatchAll,
- bool isOptional,
- object? defaultValue,
- IEnumerable<InlineConstraint>? inlineConstraints)
+ /// <summary>
+ /// Create a <see cref="TemplatePart"/> representing a literal route part.
+ /// </summary>
+ /// <param name="text">The text of the literate route part.</param>
+ /// <returns>A <see cref="TemplatePart"/> instance.</returns>
+ public static TemplatePart CreateLiteral(string text)
+ {
+ return new TemplatePart()
{
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ IsLiteral = true,
+ Text = text,
+ };
+ }
- return new TemplatePart()
- {
- IsParameter = true,
- Name = name,
- IsCatchAll = isCatchAll,
- IsOptional = isOptional,
- DefaultValue = defaultValue,
- InlineConstraints = inlineConstraints ?? Enumerable.Empty<InlineConstraint>(),
- };
+ /// <summary>
+ /// Creates a <see cref="TemplatePart"/> representing a parameter part.
+ /// </summary>
+ /// <param name="name">The name of the parameter.</param>
+ /// <param name="isCatchAll"><see langword="true"/> if the parameter is a catch-all parameter.</param>
+ /// <param name="isOptional"><see langword="true"/> if the parameter is an optional parameter.</param>
+ /// <param name="defaultValue">The default value of the parameter.</param>
+ /// <param name="inlineConstraints">A collection of constraints associated with the parameter.</param>
+ /// <returns>A <see cref="TemplatePart"/> instance.</returns>
+ public static TemplatePart CreateParameter(
+ string name,
+ bool isCatchAll,
+ bool isOptional,
+ object? defaultValue,
+ IEnumerable<InlineConstraint>? inlineConstraints)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
}
- /// <summary>
- /// <see langword="true"/> if the route part is is a catch-all part (e.g. /*).
- /// </summary>
- public bool IsCatchAll { get; private set; }
- /// <summary>
- /// <see langword="true"/> if the route part is represents a literal value.
- /// </summary>
- public bool IsLiteral { get; private set; }
- /// <summary>
- /// <see langword="true"/> if the route part represents a parameterized value.
- /// </summary>
- public bool IsParameter { get; private set; }
- /// <summary>
- /// <see langword="true"/> if the route part represents an optional part.
- /// </summary>
- public bool IsOptional { get; private set; }
- /// <summary>
- /// <see langword="true"/> if the route part represents an optional seperator.
- /// </summary>
- public bool IsOptionalSeperator { get; set; }
- /// <summary>
- /// The name of the route parameter. Can be null.
- /// </summary>
- public string? Name { get; private set; }
- /// <summary>
- /// The textual representation of the route parameter. Can be null. Used to represent route seperators and literal parts.
- /// </summary>
- public string? Text { get; private set; }
- /// <summary>
- /// The default value for route parameters. Can be null.
- /// </summary>
- public object? DefaultValue { get; private set; }
- /// <summary>
- /// The constraints associates with a route parameter.
- /// </summary>
- public IEnumerable<InlineConstraint> InlineConstraints { get; private set; } = Enumerable.Empty<InlineConstraint>();
+ return new TemplatePart()
+ {
+ IsParameter = true,
+ Name = name,
+ IsCatchAll = isCatchAll,
+ IsOptional = isOptional,
+ DefaultValue = defaultValue,
+ InlineConstraints = inlineConstraints ?? Enumerable.Empty<InlineConstraint>(),
+ };
+ }
- internal string? DebuggerToString()
+ /// <summary>
+ /// <see langword="true"/> if the route part is is a catch-all part (e.g. /*).
+ /// </summary>
+ public bool IsCatchAll { get; private set; }
+ /// <summary>
+ /// <see langword="true"/> if the route part is represents a literal value.
+ /// </summary>
+ public bool IsLiteral { get; private set; }
+ /// <summary>
+ /// <see langword="true"/> if the route part represents a parameterized value.
+ /// </summary>
+ public bool IsParameter { get; private set; }
+ /// <summary>
+ /// <see langword="true"/> if the route part represents an optional part.
+ /// </summary>
+ public bool IsOptional { get; private set; }
+ /// <summary>
+ /// <see langword="true"/> if the route part represents an optional seperator.
+ /// </summary>
+ public bool IsOptionalSeperator { get; set; }
+ /// <summary>
+ /// The name of the route parameter. Can be null.
+ /// </summary>
+ public string? Name { get; private set; }
+ /// <summary>
+ /// The textual representation of the route parameter. Can be null. Used to represent route seperators and literal parts.
+ /// </summary>
+ public string? Text { get; private set; }
+ /// <summary>
+ /// The default value for route parameters. Can be null.
+ /// </summary>
+ public object? DefaultValue { get; private set; }
+ /// <summary>
+ /// The constraints associates with a route parameter.
+ /// </summary>
+ public IEnumerable<InlineConstraint> InlineConstraints { get; private set; } = Enumerable.Empty<InlineConstraint>();
+
+ internal string? DebuggerToString()
+ {
+ if (IsParameter)
{
- if (IsParameter)
- {
- return "{" + (IsCatchAll ? "*" : string.Empty) + Name + (IsOptional ? "?" : string.Empty) + "}";
- }
- else
- {
- return Text;
- }
+ return "{" + (IsCatchAll ? "*" : string.Empty) + Name + (IsOptional ? "?" : string.Empty) + "}";
}
+ else
+ {
+ return Text;
+ }
+ }
- /// <summary>
- /// Creates a <see cref="RoutePatternPart"/> for the route part designated by the <see cref="TemplatePart"/>.
- /// </summary>
- /// <returns>A <see cref="RoutePatternPart"/> instance.</returns>
- public RoutePatternPart ToRoutePatternPart()
+ /// <summary>
+ /// Creates a <see cref="RoutePatternPart"/> for the route part designated by the <see cref="TemplatePart"/>.
+ /// </summary>
+ /// <returns>A <see cref="RoutePatternPart"/> instance.</returns>
+ public RoutePatternPart ToRoutePatternPart()
+ {
+ if (IsLiteral && IsOptionalSeperator)
+ {
+ return RoutePatternFactory.SeparatorPart(Text!);
+ }
+ else if (IsLiteral)
+ {
+ return RoutePatternFactory.LiteralPart(Text!);
+ }
+ else
{
- if (IsLiteral && IsOptionalSeperator)
- {
- return RoutePatternFactory.SeparatorPart(Text!);
- }
- else if (IsLiteral)
- {
- return RoutePatternFactory.LiteralPart(Text!);
- }
- else
- {
- var kind = IsCatchAll ?
- RoutePatternParameterKind.CatchAll :
- IsOptional ?
- RoutePatternParameterKind.Optional :
- RoutePatternParameterKind.Standard;
+ var kind = IsCatchAll ?
+ RoutePatternParameterKind.CatchAll :
+ IsOptional ?
+ RoutePatternParameterKind.Optional :
+ RoutePatternParameterKind.Standard;
- var constraints = InlineConstraints.Select(c => new RoutePatternParameterPolicyReference(c.Constraint));
- return RoutePatternFactory.ParameterPart(Name!, DefaultValue, kind, constraints);
- }
+ var constraints = InlineConstraints.Select(c => new RoutePatternParameterPolicyReference(c.Constraint));
+ return RoutePatternFactory.ParameterPart(Name!, DefaultValue, kind, constraints);
}
}
}
diff --git a/src/Http/Routing/src/Template/TemplateSegment.cs b/src/Http/Routing/src/Template/TemplateSegment.cs
index 9f92a24787..a02a27d788 100644
--- a/src/Http/Routing/src/Template/TemplateSegment.cs
+++ b/src/Http/Routing/src/Template/TemplateSegment.cs
@@ -7,64 +7,63 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// Represents a segment of a route template.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString()}")]
+public class TemplateSegment
{
/// <summary>
- /// Represents a segment of a route template.
+ /// Constructs a new <see cref="TemplateSegment"/> instance.
+ /// </summary>
+ public TemplateSegment()
+ {
+ Parts = new List<TemplatePart>();
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="TemplateSegment"/> instance given another <see cref="RoutePatternPathSegment"/>.
/// </summary>
- [DebuggerDisplay("{DebuggerToString()}")]
- public class TemplateSegment
+ /// <param name="other">A <see cref="RoutePatternPathSegment"/> instance.</param>
+ public TemplateSegment(RoutePatternPathSegment other)
{
- /// <summary>
- /// Constructs a new <see cref="TemplateSegment"/> instance.
- /// </summary>
- public TemplateSegment()
+ if (other == null)
{
- Parts = new List<TemplatePart>();
+ throw new ArgumentNullException(nameof(other));
}
- /// <summary>
- /// Constructs a new <see cref="TemplateSegment"/> instance given another <see cref="RoutePatternPathSegment"/>.
- /// </summary>
- /// <param name="other">A <see cref="RoutePatternPathSegment"/> instance.</param>
- public TemplateSegment(RoutePatternPathSegment other)
+ var partCount = other.Parts.Count;
+ Parts = new List<TemplatePart>(partCount);
+ for (var i = 0; i < partCount; i++)
{
- if (other == null)
- {
- throw new ArgumentNullException(nameof(other));
- }
-
- var partCount = other.Parts.Count;
- Parts = new List<TemplatePart>(partCount);
- for (var i = 0; i < partCount; i++)
- {
- Parts.Add(new TemplatePart(other.Parts[i]));
- }
+ Parts.Add(new TemplatePart(other.Parts[i]));
}
+ }
- /// <summary>
- /// <see langword="true"/> if the segment contains a single entry.
- /// </summary>
- public bool IsSimple => Parts.Count == 1;
+ /// <summary>
+ /// <see langword="true"/> if the segment contains a single entry.
+ /// </summary>
+ public bool IsSimple => Parts.Count == 1;
- /// <summary>
- /// Gets the list of individual parts in the template segment.
- /// </summary>
- public List<TemplatePart> Parts { get; }
+ /// <summary>
+ /// Gets the list of individual parts in the template segment.
+ /// </summary>
+ public List<TemplatePart> Parts { get; }
- internal string DebuggerToString()
- {
- return string.Join(string.Empty, Parts.Select(p => p.DebuggerToString()));
- }
+ internal string DebuggerToString()
+ {
+ return string.Join(string.Empty, Parts.Select(p => p.DebuggerToString()));
+ }
- /// <summary>
- /// Returns a <see cref="RoutePatternPathSegment"/> for the template segment.
- /// </summary>
- /// <returns>A <see cref="RoutePatternPathSegment"/> instance.</returns>
- public RoutePatternPathSegment ToRoutePatternPathSegment()
- {
- var parts = Parts.Select(p => p.ToRoutePatternPart());
- return RoutePatternFactory.Segment(parts);
- }
+ /// <summary>
+ /// Returns a <see cref="RoutePatternPathSegment"/> for the template segment.
+ /// </summary>
+ /// <returns>A <see cref="RoutePatternPathSegment"/> instance.</returns>
+ public RoutePatternPathSegment ToRoutePatternPathSegment()
+ {
+ var parts = Parts.Select(p => p.ToRoutePatternPart());
+ return RoutePatternFactory.Segment(parts);
}
}
diff --git a/src/Http/Routing/src/Template/TemplateValuesResult.cs b/src/Http/Routing/src/Template/TemplateValuesResult.cs
index 18a1762a6a..bfe086fdcd 100644
--- a/src/Http/Routing/src/Template/TemplateValuesResult.cs
+++ b/src/Http/Routing/src/Template/TemplateValuesResult.cs
@@ -1,29 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+/// <summary>
+/// The values used as inputs for constraints and link generation.
+/// </summary>
+public class TemplateValuesResult
{
/// <summary>
- /// The values used as inputs for constraints and link generation.
+ /// The set of values that will appear in the URL.
/// </summary>
- public class TemplateValuesResult
- {
- /// <summary>
- /// The set of values that will appear in the URL.
- /// </summary>
- public RouteValueDictionary AcceptedValues { get; set; } = default!;
+ public RouteValueDictionary AcceptedValues { get; set; } = default!;
- /// <summary>
- /// The set of values that that were supplied for URL generation.
- /// </summary>
- /// <remarks>
- /// This combines implicit (ambient) values from the <see cref="RouteData"/> of the current request
- /// (if applicable), explictly provided values, and default values for parameters that appear in
- /// the route template.
- ///
- /// Implicit (ambient) values which are invalidated due to changes in values lexically earlier in the
- /// route template are excluded from this set.
- /// </remarks>
- public RouteValueDictionary CombinedValues { get; set; } = default!;
- }
+ /// <summary>
+ /// The set of values that that were supplied for URL generation.
+ /// </summary>
+ /// <remarks>
+ /// This combines implicit (ambient) values from the <see cref="RouteData"/> of the current request
+ /// (if applicable), explictly provided values, and default values for parameters that appear in
+ /// the route template.
+ ///
+ /// Implicit (ambient) values which are invalidated due to changes in values lexically earlier in the
+ /// route template are excluded from this set.
+ /// </remarks>
+ public RouteValueDictionary CombinedValues { get; set; } = default!;
}
diff --git a/src/Http/Routing/src/Tree/InboundMatch.cs b/src/Http/Routing/src/Tree/InboundMatch.cs
index 604b1be6c0..e709c247fe 100644
--- a/src/Http/Routing/src/Tree/InboundMatch.cs
+++ b/src/Http/Routing/src/Tree/InboundMatch.cs
@@ -6,27 +6,26 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// A candidate route to match incoming URLs in a <see cref="TreeRouter"/>.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+public class InboundMatch
{
/// <summary>
- /// A candidate route to match incoming URLs in a <see cref="TreeRouter"/>.
+ /// Gets or sets the <see cref="InboundRouteEntry"/>.
/// </summary>
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- public class InboundMatch
- {
- /// <summary>
- /// Gets or sets the <see cref="InboundRouteEntry"/>.
- /// </summary>
- public InboundRouteEntry Entry { get; set; }
+ public InboundRouteEntry Entry { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="TemplateMatcher"/>.
- /// </summary>
- public TemplateMatcher TemplateMatcher { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="TemplateMatcher"/>.
+ /// </summary>
+ public TemplateMatcher TemplateMatcher { get; set; }
- private string DebuggerToString()
- {
- return TemplateMatcher?.Template?.TemplateText;
- }
+ private string DebuggerToString()
+ {
+ return TemplateMatcher?.Template?.TemplateText;
}
}
diff --git a/src/Http/Routing/src/Tree/InboundRouteEntry.cs b/src/Http/Routing/src/Tree/InboundRouteEntry.cs
index efb52600b7..b6688b7647 100644
--- a/src/Http/Routing/src/Tree/InboundRouteEntry.cs
+++ b/src/Http/Routing/src/Tree/InboundRouteEntry.cs
@@ -6,53 +6,52 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// Used to build an <see cref="TreeRouter"/>. Represents a URL template tha will be used to match incoming
+/// request URLs.
+/// </summary>
+public class InboundRouteEntry
{
/// <summary>
- /// Used to build an <see cref="TreeRouter"/>. Represents a URL template tha will be used to match incoming
- /// request URLs.
+ /// Gets or sets the route constraints.
+ /// </summary>
+ public IDictionary<string, IRouteConstraint> Constraints { get; set; }
+
+ /// <summary>
+ /// Gets or sets the route defaults.
+ /// </summary>
+ public RouteValueDictionary Defaults { get; set; }
+
+ /// <summary>
+ /// Gets or sets the <see cref="IRouter"/> to invoke when this entry matches.
+ /// </summary>
+ public IRouter Handler { get; set; }
+
+ /// <summary>
+ /// Gets or sets the order of the entry.
+ /// </summary>
+ /// <remarks>
+ /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
+ /// </remarks>
+ public int Order { get; set; }
+
+ /// <summary>
+ /// Gets or sets the precedence of the entry.
+ /// </summary>
+ /// <remarks>
+ /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
+ /// </remarks>
+ public decimal Precedence { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the route.
+ /// </summary>
+ public string RouteName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the <see cref="RouteTemplate"/>.
/// </summary>
- public class InboundRouteEntry
- {
- /// <summary>
- /// Gets or sets the route constraints.
- /// </summary>
- public IDictionary<string, IRouteConstraint> Constraints { get; set; }
-
- /// <summary>
- /// Gets or sets the route defaults.
- /// </summary>
- public RouteValueDictionary Defaults { get; set; }
-
- /// <summary>
- /// Gets or sets the <see cref="IRouter"/> to invoke when this entry matches.
- /// </summary>
- public IRouter Handler { get; set; }
-
- /// <summary>
- /// Gets or sets the order of the entry.
- /// </summary>
- /// <remarks>
- /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
- /// </remarks>
- public int Order { get; set; }
-
- /// <summary>
- /// Gets or sets the precedence of the entry.
- /// </summary>
- /// <remarks>
- /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
- /// </remarks>
- public decimal Precedence { get; set; }
-
- /// <summary>
- /// Gets or sets the name of the route.
- /// </summary>
- public string RouteName { get; set; }
-
- /// <summary>
- /// Gets or sets the <see cref="RouteTemplate"/>.
- /// </summary>
- public RouteTemplate RouteTemplate { get; set; }
- }
+ public RouteTemplate RouteTemplate { get; set; }
}
diff --git a/src/Http/Routing/src/Tree/LinkGenerationDecisionTree.cs b/src/Http/Routing/src/Tree/LinkGenerationDecisionTree.cs
index e7eb90bb07..1998da0b1c 100644
--- a/src/Http/Routing/src/Tree/LinkGenerationDecisionTree.cs
+++ b/src/Http/Routing/src/Tree/LinkGenerationDecisionTree.cs
@@ -11,257 +11,256 @@ using System.Text;
using Microsoft.AspNetCore.Routing.DecisionTree;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+// A decision tree that matches link generation entries based on route data.
+[DebuggerDisplay("{DebuggerDisplayString,nq}")]
+internal class LinkGenerationDecisionTree
{
- // A decision tree that matches link generation entries based on route data.
- [DebuggerDisplay("{DebuggerDisplayString,nq}")]
- internal class LinkGenerationDecisionTree
- {
- // Fallback value for cases where the ambient values weren't provided.
- //
- // This is safe because we don't mutate the route values in here.
- private static readonly RouteValueDictionary EmptyAmbientValues = new RouteValueDictionary();
+ // Fallback value for cases where the ambient values weren't provided.
+ //
+ // This is safe because we don't mutate the route values in here.
+ private static readonly RouteValueDictionary EmptyAmbientValues = new RouteValueDictionary();
- private readonly DecisionTreeNode<OutboundMatch> _root;
- private readonly List<OutboundMatch> _conventionalEntries;
+ private readonly DecisionTreeNode<OutboundMatch> _root;
+ private readonly List<OutboundMatch> _conventionalEntries;
- public LinkGenerationDecisionTree(IReadOnlyList<OutboundMatch> entries)
- {
- // We split up the entries into:
- // 1. attribute routes - these go into the tree
- // 2. conventional routes - these are a list
- var attributedEntries = new List<OutboundMatch>();
- _conventionalEntries = new List<OutboundMatch>();
+ public LinkGenerationDecisionTree(IReadOnlyList<OutboundMatch> entries)
+ {
+ // We split up the entries into:
+ // 1. attribute routes - these go into the tree
+ // 2. conventional routes - these are a list
+ var attributedEntries = new List<OutboundMatch>();
+ _conventionalEntries = new List<OutboundMatch>();
- // Anything with a RoutePattern.RequiredValueAny as a RequiredValue is a conventional route.
- // This is because RequiredValueAny acts as a wildcard, whereas an attribute route entry
- // is denormalized to contain an exact set of required values.
- //
- // We will only see conventional routes show up here for endpoint routing.
- for (var i = 0; i < entries.Count; i++)
+ // Anything with a RoutePattern.RequiredValueAny as a RequiredValue is a conventional route.
+ // This is because RequiredValueAny acts as a wildcard, whereas an attribute route entry
+ // is denormalized to contain an exact set of required values.
+ //
+ // We will only see conventional routes show up here for endpoint routing.
+ for (var i = 0; i < entries.Count; i++)
+ {
+ var isAttributeRoute = true;
+ var entry = entries[i];
+ foreach (var kvp in entry.Entry.RequiredLinkValues)
{
- var isAttributeRoute = true;
- var entry = entries[i];
- foreach (var kvp in entry.Entry.RequiredLinkValues)
- {
- if (RoutePattern.IsRequiredValueAny(kvp.Value))
- {
- isAttributeRoute = false;
- break;
- }
- }
-
- if (isAttributeRoute)
- {
- attributedEntries.Add(entry);
- }
- else
+ if (RoutePattern.IsRequiredValueAny(kvp.Value))
{
- _conventionalEntries.Add(entry);
+ isAttributeRoute = false;
+ break;
}
}
- _root = DecisionTreeBuilder<OutboundMatch>.GenerateTree(
- attributedEntries,
- new OutboundMatchClassifier());
+ if (isAttributeRoute)
+ {
+ attributedEntries.Add(entry);
+ }
+ else
+ {
+ _conventionalEntries.Add(entry);
+ }
}
- public IList<OutboundMatchResult> GetMatches(RouteValueDictionary values, RouteValueDictionary ambientValues)
+ _root = DecisionTreeBuilder<OutboundMatch>.GenerateTree(
+ attributedEntries,
+ new OutboundMatchClassifier());
+ }
+
+ public IList<OutboundMatchResult> GetMatches(RouteValueDictionary values, RouteValueDictionary ambientValues)
+ {
+ // Perf: Avoid allocation for List if there aren't any Matches or Criteria
+ if (_root.Matches.Count > 0 || _root.Criteria.Count > 0 || _conventionalEntries.Count > 0)
{
- // Perf: Avoid allocation for List if there aren't any Matches or Criteria
- if (_root.Matches.Count > 0 || _root.Criteria.Count > 0 || _conventionalEntries.Count > 0)
- {
- var results = new List<OutboundMatchResult>();
- Walk(results, values, ambientValues ?? EmptyAmbientValues, _root, isFallbackPath: false);
- ProcessConventionalEntries(results, values, ambientValues ?? EmptyAmbientValues);
- results.Sort(OutboundMatchResultComparer.Instance);
- return results;
- }
+ var results = new List<OutboundMatchResult>();
+ Walk(results, values, ambientValues ?? EmptyAmbientValues, _root, isFallbackPath: false);
+ ProcessConventionalEntries(results, values, ambientValues ?? EmptyAmbientValues);
+ results.Sort(OutboundMatchResultComparer.Instance);
+ return results;
+ }
+
+ return null;
+ }
- return null;
+ // We need to recursively walk the decision tree based on the provided route data
+ // (context.Values + context.AmbientValues) to find all entries that match. This process is
+ // virtually identical to action selection.
+ //
+ // Each entry has a collection of 'required link values' that must be satisfied. These are
+ // key-value pairs that make up the decision tree.
+ //
+ // A 'require link value' is considered satisfied IF:
+ // 1. The value in context.Values matches the required value OR
+ // 2. There is no value in context.Values and the value in context.AmbientValues matches OR
+ // 3. The required value is 'null' and there is no value in context.Values.
+ //
+ // Ex:
+ // entry requires { area = null, controller = Store, action = Buy }
+ // context.Values = { controller = Store, action = Buy }
+ // context.AmbientValues = { area = Help, controller = AboutStore, action = HowToBuyThings }
+ //
+ // In this case the entry is a match. The 'controller' and 'action' are both supplied by context.Values,
+ // and the 'area' is satisfied because there's NOT a value in context.Values. It's OK to ignore ambient
+ // values in link generation.
+ //
+ // If another entry existed like { area = Help, controller = Store, action = Buy }, this would also
+ // match.
+ //
+ // The decision tree uses a tree data structure to execute these rules across all candidates at once.
+ private void Walk(
+ List<OutboundMatchResult> results,
+ RouteValueDictionary values,
+ RouteValueDictionary ambientValues,
+ DecisionTreeNode<OutboundMatch> node,
+ bool isFallbackPath)
+ {
+ // Any entries in node.Matches have had all their required values satisfied, so add them
+ // to the results.
+ var matches = node.Matches;
+ // Read interface .Count once rather than per iteration
+ var matchesCount = matches.Count;
+ for (var i = 0; i < matchesCount; i++)
+ {
+ results.Add(new OutboundMatchResult(matches[i], isFallbackPath));
}
- // We need to recursively walk the decision tree based on the provided route data
- // (context.Values + context.AmbientValues) to find all entries that match. This process is
- // virtually identical to action selection.
- //
- // Each entry has a collection of 'required link values' that must be satisfied. These are
- // key-value pairs that make up the decision tree.
- //
- // A 'require link value' is considered satisfied IF:
- // 1. The value in context.Values matches the required value OR
- // 2. There is no value in context.Values and the value in context.AmbientValues matches OR
- // 3. The required value is 'null' and there is no value in context.Values.
- //
- // Ex:
- // entry requires { area = null, controller = Store, action = Buy }
- // context.Values = { controller = Store, action = Buy }
- // context.AmbientValues = { area = Help, controller = AboutStore, action = HowToBuyThings }
- //
- // In this case the entry is a match. The 'controller' and 'action' are both supplied by context.Values,
- // and the 'area' is satisfied because there's NOT a value in context.Values. It's OK to ignore ambient
- // values in link generation.
- //
- // If another entry existed like { area = Help, controller = Store, action = Buy }, this would also
- // match.
- //
- // The decision tree uses a tree data structure to execute these rules across all candidates at once.
- private void Walk(
- List<OutboundMatchResult> results,
- RouteValueDictionary values,
- RouteValueDictionary ambientValues,
- DecisionTreeNode<OutboundMatch> node,
- bool isFallbackPath)
+ var criteria = node.Criteria;
+ // Read interface .Count once rather than per iteration
+ var criteriaCount = criteria.Count;
+ for (var i = 0; i < criteriaCount; i++)
{
- // Any entries in node.Matches have had all their required values satisfied, so add them
- // to the results.
- var matches = node.Matches;
- // Read interface .Count once rather than per iteration
- var matchesCount = matches.Count;
- for (var i = 0; i < matchesCount; i++)
+ var criterion = criteria[i];
+ var key = criterion.Key;
+
+ if (values.TryGetValue(key, out var value))
{
- results.Add(new OutboundMatchResult(matches[i], isFallbackPath));
+ if (criterion.Branches.TryGetValue(value ?? string.Empty, out var branch))
+ {
+ Walk(results, values, ambientValues, branch, isFallbackPath);
+ }
}
-
- var criteria = node.Criteria;
- // Read interface .Count once rather than per iteration
- var criteriaCount = criteria.Count;
- for (var i = 0; i < criteriaCount; i++)
+ else
{
- var criterion = criteria[i];
- var key = criterion.Key;
-
- if (values.TryGetValue(key, out var value))
+ // If a value wasn't explicitly supplied, match BOTH the ambient value and the empty value
+ // if an ambient value was supplied. The path explored with the empty value is considered
+ // the fallback path.
+ DecisionTreeNode<OutboundMatch> branch;
+ if (ambientValues.TryGetValue(key, out value) &&
+ !criterion.Branches.Comparer.Equals(value, string.Empty))
{
- if (criterion.Branches.TryGetValue(value ?? string.Empty, out var branch))
+ if (criterion.Branches.TryGetValue(value, out branch))
{
Walk(results, values, ambientValues, branch, isFallbackPath);
}
}
- else
- {
- // If a value wasn't explicitly supplied, match BOTH the ambient value and the empty value
- // if an ambient value was supplied. The path explored with the empty value is considered
- // the fallback path.
- DecisionTreeNode<OutboundMatch> branch;
- if (ambientValues.TryGetValue(key, out value) &&
- !criterion.Branches.Comparer.Equals(value, string.Empty))
- {
- if (criterion.Branches.TryGetValue(value, out branch))
- {
- Walk(results, values, ambientValues, branch, isFallbackPath);
- }
- }
- if (criterion.Branches.TryGetValue(string.Empty, out branch))
- {
- Walk(results, values, ambientValues, branch, isFallbackPath: true);
- }
+ if (criterion.Branches.TryGetValue(string.Empty, out branch))
+ {
+ Walk(results, values, ambientValues, branch, isFallbackPath: true);
}
}
}
+ }
- private void ProcessConventionalEntries(
- List<OutboundMatchResult> results,
- RouteValueDictionary values,
- RouteValueDictionary ambientvalues)
+ private void ProcessConventionalEntries(
+ List<OutboundMatchResult> results,
+ RouteValueDictionary values,
+ RouteValueDictionary ambientvalues)
+ {
+ for (var i = 0; i < _conventionalEntries.Count; i++)
{
- for (var i = 0; i < _conventionalEntries.Count; i++)
- {
- results.Add(new OutboundMatchResult(_conventionalEntries[i], isFallbackMatch: false));
- }
+ results.Add(new OutboundMatchResult(_conventionalEntries[i], isFallbackMatch: false));
}
+ }
- private class OutboundMatchClassifier : IClassifier<OutboundMatch>
- {
- public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;
+ private class OutboundMatchClassifier : IClassifier<OutboundMatch>
+ {
+ public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;
- public IDictionary<string, DecisionCriterionValue> GetCriteria(OutboundMatch item)
+ public IDictionary<string, DecisionCriterionValue> GetCriteria(OutboundMatch item)
+ {
+ var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
+ foreach (var kvp in item.Entry.RequiredLinkValues)
{
- var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
- foreach (var kvp in item.Entry.RequiredLinkValues)
- {
- results.Add(kvp.Key, new DecisionCriterionValue(kvp.Value ?? string.Empty));
- }
-
- return results;
+ results.Add(kvp.Key, new DecisionCriterionValue(kvp.Value ?? string.Empty));
}
+
+ return results;
}
+ }
- private class OutboundMatchResultComparer : IComparer<OutboundMatchResult>
- {
- public static readonly OutboundMatchResultComparer Instance = new OutboundMatchResultComparer();
+ private class OutboundMatchResultComparer : IComparer<OutboundMatchResult>
+ {
+ public static readonly OutboundMatchResultComparer Instance = new OutboundMatchResultComparer();
- public int Compare(OutboundMatchResult x, OutboundMatchResult y)
+ public int Compare(OutboundMatchResult x, OutboundMatchResult y)
+ {
+ // For this comparison lower is better.
+ if (x.Match.Entry.Order != y.Match.Entry.Order)
{
- // For this comparison lower is better.
- if (x.Match.Entry.Order != y.Match.Entry.Order)
- {
- return x.Match.Entry.Order.CompareTo(y.Match.Entry.Order);
- }
-
- if (x.Match.Entry.Precedence != y.Match.Entry.Precedence)
- {
- // Reversed because higher is better
- return y.Match.Entry.Precedence.CompareTo(x.Match.Entry.Precedence);
- }
+ return x.Match.Entry.Order.CompareTo(y.Match.Entry.Order);
+ }
- if (x.IsFallbackMatch != y.IsFallbackMatch)
- {
- // A fallback match is worse than a non-fallback
- return x.IsFallbackMatch.CompareTo(y.IsFallbackMatch);
- }
+ if (x.Match.Entry.Precedence != y.Match.Entry.Precedence)
+ {
+ // Reversed because higher is better
+ return y.Match.Entry.Precedence.CompareTo(x.Match.Entry.Precedence);
+ }
- return string.Compare(
- x.Match.Entry.RouteTemplate.TemplateText,
- y.Match.Entry.RouteTemplate.TemplateText,
- StringComparison.Ordinal);
+ if (x.IsFallbackMatch != y.IsFallbackMatch)
+ {
+ // A fallback match is worse than a non-fallback
+ return x.IsFallbackMatch.CompareTo(y.IsFallbackMatch);
}
+
+ return string.Compare(
+ x.Match.Entry.RouteTemplate.TemplateText,
+ y.Match.Entry.RouteTemplate.TemplateText,
+ StringComparison.Ordinal);
}
+ }
- // Example output:
- //
- // => action: Buy => controller: Store => version: V1(Matches: Store/Buy/V1)
- // => action: Buy => controller: Store => version: V2(Matches: Store/Buy/V2)
- // => action: Buy => controller: Store => area: Admin(Matches: Admin/Store/Buy)
- // => action: Buy => controller: Products(Matches: Products/Buy)
- // => action: Cart => controller: Store(Matches: Store/Cart)
- internal string DebuggerDisplayString
+ // Example output:
+ //
+ // => action: Buy => controller: Store => version: V1(Matches: Store/Buy/V1)
+ // => action: Buy => controller: Store => version: V2(Matches: Store/Buy/V2)
+ // => action: Buy => controller: Store => area: Admin(Matches: Admin/Store/Buy)
+ // => action: Buy => controller: Products(Matches: Products/Buy)
+ // => action: Cart => controller: Store(Matches: Store/Cart)
+ internal string DebuggerDisplayString
+ {
+ get
{
- get
- {
- var sb = new StringBuilder();
- var branchStack = new Stack<string>();
- branchStack.Push(string.Empty);
- FlattenTree(branchStack, sb, _root);
- return sb.ToString();
- }
+ var sb = new StringBuilder();
+ var branchStack = new Stack<string>();
+ branchStack.Push(string.Empty);
+ FlattenTree(branchStack, sb, _root);
+ return sb.ToString();
}
+ }
- private void FlattenTree(Stack<string> branchStack, StringBuilder sb, DecisionTreeNode<OutboundMatch> node)
+ private void FlattenTree(Stack<string> branchStack, StringBuilder sb, DecisionTreeNode<OutboundMatch> node)
+ {
+ // leaf node
+ if (node.Criteria.Count == 0)
{
- // leaf node
- if (node.Criteria.Count == 0)
+ var matchesSb = new StringBuilder();
+ foreach (var branch in branchStack)
{
- var matchesSb = new StringBuilder();
- foreach (var branch in branchStack)
- {
- matchesSb.Insert(0, branch);
- }
- sb.Append(matchesSb);
- sb.Append(" (Matches: ");
- sb.AppendJoin(", ", node.Matches.Select(m => m.Entry.RouteTemplate.TemplateText));
- sb.AppendLine(")");
+ matchesSb.Insert(0, branch);
}
+ sb.Append(matchesSb);
+ sb.Append(" (Matches: ");
+ sb.AppendJoin(", ", node.Matches.Select(m => m.Entry.RouteTemplate.TemplateText));
+ sb.AppendLine(")");
+ }
- foreach (var criterion in node.Criteria)
+ foreach (var criterion in node.Criteria)
+ {
+ foreach (var branch in criterion.Branches)
{
- foreach (var branch in criterion.Branches)
- {
- branchStack.Push($" => {criterion.Key}: {branch.Key}");
- FlattenTree(branchStack, sb, branch.Value);
- branchStack.Pop();
- }
+ branchStack.Push($" => {criterion.Key}: {branch.Key}");
+ FlattenTree(branchStack, sb, branch.Value);
+ branchStack.Pop();
}
}
}
diff --git a/src/Http/Routing/src/Tree/OutboundMatch.cs b/src/Http/Routing/src/Tree/OutboundMatch.cs
index e63ca507a1..eef5a421db 100644
--- a/src/Http/Routing/src/Tree/OutboundMatch.cs
+++ b/src/Http/Routing/src/Tree/OutboundMatch.cs
@@ -5,21 +5,20 @@
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// A candidate match for link generation in a <see cref="TreeRouter"/>.
+/// </summary>
+public class OutboundMatch
{
/// <summary>
- /// A candidate match for link generation in a <see cref="TreeRouter"/>.
+ /// Gets or sets the <see cref="OutboundRouteEntry"/>.
/// </summary>
- public class OutboundMatch
- {
- /// <summary>
- /// Gets or sets the <see cref="OutboundRouteEntry"/>.
- /// </summary>
- public OutboundRouteEntry Entry { get; set; }
+ public OutboundRouteEntry Entry { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="TemplateBinder"/>.
- /// </summary>
- public TemplateBinder TemplateBinder { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the <see cref="TemplateBinder"/>.
+ /// </summary>
+ public TemplateBinder TemplateBinder { get; set; }
}
diff --git a/src/Http/Routing/src/Tree/OutboundMatchResult.cs b/src/Http/Routing/src/Tree/OutboundMatchResult.cs
index 36c85ec701..db1d31a9eb 100644
--- a/src/Http/Routing/src/Tree/OutboundMatchResult.cs
+++ b/src/Http/Routing/src/Tree/OutboundMatchResult.cs
@@ -1,18 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+internal readonly struct OutboundMatchResult
{
- internal readonly struct OutboundMatchResult
+ public OutboundMatchResult(OutboundMatch match, bool isFallbackMatch)
{
- public OutboundMatchResult(OutboundMatch match, bool isFallbackMatch)
- {
- Match = match;
- IsFallbackMatch = isFallbackMatch;
- }
+ Match = match;
+ IsFallbackMatch = isFallbackMatch;
+ }
- public OutboundMatch Match { get; }
+ public OutboundMatch Match { get; }
- public bool IsFallbackMatch { get; }
- }
+ public bool IsFallbackMatch { get; }
}
diff --git a/src/Http/Routing/src/Tree/OutboundRouteEntry.cs b/src/Http/Routing/src/Tree/OutboundRouteEntry.cs
index 17979afdd1..3ef79dcf82 100644
--- a/src/Http/Routing/src/Tree/OutboundRouteEntry.cs
+++ b/src/Http/Routing/src/Tree/OutboundRouteEntry.cs
@@ -6,64 +6,63 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// Used to build a <see cref="TreeRouter"/>. Represents a URL template that will be used to generate
+/// outgoing URLs.
+/// </summary>
+public class OutboundRouteEntry
{
/// <summary>
- /// Used to build a <see cref="TreeRouter"/>. Represents a URL template that will be used to generate
- /// outgoing URLs.
+ /// Gets or sets the route constraints.
/// </summary>
- public class OutboundRouteEntry
- {
- /// <summary>
- /// Gets or sets the route constraints.
- /// </summary>
- public IDictionary<string, IRouteConstraint> Constraints { get; set; }
+ public IDictionary<string, IRouteConstraint> Constraints { get; set; }
- /// <summary>
- /// Gets or sets the route defaults.
- /// </summary>
- public RouteValueDictionary Defaults { get; set; }
+ /// <summary>
+ /// Gets or sets the route defaults.
+ /// </summary>
+ public RouteValueDictionary Defaults { get; set; }
- /// <summary>
- /// The <see cref="IRouter"/> to invoke when this entry matches.
- /// </summary>
- public IRouter Handler { get; set; }
+ /// <summary>
+ /// The <see cref="IRouter"/> to invoke when this entry matches.
+ /// </summary>
+ public IRouter Handler { get; set; }
- /// <summary>
- /// Gets or sets the order of the entry.
- /// </summary>
- /// <remarks>
- /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
- /// </remarks>
- public int Order { get; set; }
+ /// <summary>
+ /// Gets or sets the order of the entry.
+ /// </summary>
+ /// <remarks>
+ /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
+ /// </remarks>
+ public int Order { get; set; }
- /// <summary>
- /// Gets or sets the precedence of the template for link generation. A greater value of
- /// <see cref="Precedence"/> means that an entry is considered first.
- /// </summary>
- /// <remarks>
- /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
- /// </remarks>
- public decimal Precedence { get; set; }
+ /// <summary>
+ /// Gets or sets the precedence of the template for link generation. A greater value of
+ /// <see cref="Precedence"/> means that an entry is considered first.
+ /// </summary>
+ /// <remarks>
+ /// Entries are ordered first by <see cref="Order"/> (ascending) then by <see cref="Precedence"/> (descending).
+ /// </remarks>
+ public decimal Precedence { get; set; }
- /// <summary>
- /// Gets or sets the name of the route.
- /// </summary>
- public string RouteName { get; set; }
+ /// <summary>
+ /// Gets or sets the name of the route.
+ /// </summary>
+ public string RouteName { get; set; }
- /// <summary>
- /// Gets or sets the set of values that must be present for link genration.
- /// </summary>
- public RouteValueDictionary RequiredLinkValues { get; set; }
+ /// <summary>
+ /// Gets or sets the set of values that must be present for link genration.
+ /// </summary>
+ public RouteValueDictionary RequiredLinkValues { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="RouteTemplate"/>.
- /// </summary>
- public RouteTemplate RouteTemplate { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="RouteTemplate"/>.
+ /// </summary>
+ public RouteTemplate RouteTemplate { get; set; }
- /// <summary>
- /// Gets or sets the data that is associated with this entry.
- /// </summary>
- public object Data { get; set; }
- }
+ /// <summary>
+ /// Gets or sets the data that is associated with this entry.
+ /// </summary>
+ public object Data { get; set; }
}
diff --git a/src/Http/Routing/src/Tree/TreeEnumerator.cs b/src/Http/Routing/src/Tree/TreeEnumerator.cs
index de825aa810..f326cb3d86 100644
--- a/src/Http/Routing/src/Tree/TreeEnumerator.cs
+++ b/src/Http/Routing/src/Tree/TreeEnumerator.cs
@@ -7,106 +7,105 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+internal struct TreeEnumerator : IEnumerator<UrlMatchingNode>
{
- internal struct TreeEnumerator : IEnumerator<UrlMatchingNode>
+ private readonly Stack<UrlMatchingNode> _stack;
+ private readonly PathTokenizer _tokenizer;
+
+ public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer)
{
- private readonly Stack<UrlMatchingNode> _stack;
- private readonly PathTokenizer _tokenizer;
+ _stack = new Stack<UrlMatchingNode>();
+ _tokenizer = tokenizer;
+ Current = null;
- public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer)
- {
- _stack = new Stack<UrlMatchingNode>();
- _tokenizer = tokenizer;
- Current = null;
+ _stack.Push(root);
+ }
- _stack.Push(root);
- }
+ public UrlMatchingNode Current { get; private set; }
- public UrlMatchingNode Current { get; private set; }
+ object IEnumerator.Current => Current;
- object IEnumerator.Current => Current;
+ public void Dispose()
+ {
+ }
- public void Dispose()
+ public bool MoveNext()
+ {
+ if (_stack == null)
{
+ return false;
}
- public bool MoveNext()
+ while (_stack.Count > 0)
{
- if (_stack == null)
+ var next = _stack.Pop();
+
+ // In case of wild card segment, the request path segment length can be greater
+ // Example:
+ // Template: a/{*path}
+ // Request Url: a/b/c/d
+ if (next.IsCatchAll && next.Matches.Count > 0)
{
- return false;
+ Current = next;
+ return true;
}
-
- while (_stack.Count > 0)
+ // Next template has the same length as the url we are trying to match
+ // The only possible matching segments are either our current matches or
+ // any catch-all segment after this segment in which the catch all is empty.
+ else if (next.Depth == _tokenizer.Count)
{
- var next = _stack.Pop();
-
- // In case of wild card segment, the request path segment length can be greater
- // Example:
- // Template: a/{*path}
- // Request Url: a/b/c/d
- if (next.IsCatchAll && next.Matches.Count > 0)
+ if (next.Matches.Count > 0)
{
Current = next;
return true;
}
- // Next template has the same length as the url we are trying to match
- // The only possible matching segments are either our current matches or
- // any catch-all segment after this segment in which the catch all is empty.
- else if (next.Depth == _tokenizer.Count)
+ else
{
- if (next.Matches.Count > 0)
- {
- Current = next;
- return true;
- }
- else
- {
- // We can stop looking as any other child node from this node will be
- // either a literal, a constrained parameter or a parameter.
- // (Catch alls and constrained catch alls will show up as candidate matches).
- continue;
- }
+ // We can stop looking as any other child node from this node will be
+ // either a literal, a constrained parameter or a parameter.
+ // (Catch alls and constrained catch alls will show up as candidate matches).
+ continue;
}
+ }
- if (next.CatchAlls != null)
- {
- _stack.Push(next.CatchAlls);
- }
+ if (next.CatchAlls != null)
+ {
+ _stack.Push(next.CatchAlls);
+ }
- if (next.ConstrainedCatchAlls != null)
- {
- _stack.Push(next.ConstrainedCatchAlls);
- }
+ if (next.ConstrainedCatchAlls != null)
+ {
+ _stack.Push(next.ConstrainedCatchAlls);
+ }
- if (next.Parameters != null)
- {
- _stack.Push(next.Parameters);
- }
+ if (next.Parameters != null)
+ {
+ _stack.Push(next.Parameters);
+ }
- if (next.ConstrainedParameters != null)
- {
- _stack.Push(next.ConstrainedParameters);
- }
+ if (next.ConstrainedParameters != null)
+ {
+ _stack.Push(next.ConstrainedParameters);
+ }
- if (next.Literals.Count > 0)
+ if (next.Literals.Count > 0)
+ {
+ Debug.Assert(next.Depth < _tokenizer.Count);
+ if (next.Literals.TryGetValue(_tokenizer[next.Depth].Value, out var node))
{
- Debug.Assert(next.Depth < _tokenizer.Count);
- if (next.Literals.TryGetValue(_tokenizer[next.Depth].Value, out var node))
- {
- _stack.Push(node);
- }
+ _stack.Push(node);
}
}
-
- return false;
}
- public void Reset()
- {
- _stack.Clear();
- Current = null;
- }
+ return false;
+ }
+
+ public void Reset()
+ {
+ _stack.Clear();
+ Current = null;
}
}
diff --git a/src/Http/Routing/src/Tree/TreeRouteBuilder.cs b/src/Http/Routing/src/Tree/TreeRouteBuilder.cs
index 41612718d1..a588816a21 100644
--- a/src/Http/Routing/src/Tree/TreeRouteBuilder.cs
+++ b/src/Http/Routing/src/Tree/TreeRouteBuilder.cs
@@ -11,253 +11,252 @@ using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// Builder for <see cref="TreeRouter"/> instances.
+/// </summary>
+public class TreeRouteBuilder
{
+ private readonly ILogger _logger;
+ private readonly ILogger _constraintLogger;
+ private readonly UrlEncoder _urlEncoder;
+ private readonly ObjectPool<UriBuildingContext> _objectPool;
+ private readonly IInlineConstraintResolver _constraintResolver;
+
/// <summary>
- /// Builder for <see cref="TreeRouter"/> instances.
+ /// Initializes a new instance of <see cref="TreeRouteBuilder"/>.
/// </summary>
- public class TreeRouteBuilder
+ /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
+ /// <param name="objectPool">The <see cref="ObjectPool{UrlBuildingContext}"/>.</param>
+ /// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
+ internal TreeRouteBuilder(
+ ILoggerFactory loggerFactory,
+ ObjectPool<UriBuildingContext> objectPool,
+ IInlineConstraintResolver constraintResolver)
{
- private readonly ILogger _logger;
- private readonly ILogger _constraintLogger;
- private readonly UrlEncoder _urlEncoder;
- private readonly ObjectPool<UriBuildingContext> _objectPool;
- private readonly IInlineConstraintResolver _constraintResolver;
-
- /// <summary>
- /// Initializes a new instance of <see cref="TreeRouteBuilder"/>.
- /// </summary>
- /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
- /// <param name="objectPool">The <see cref="ObjectPool{UrlBuildingContext}"/>.</param>
- /// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
- internal TreeRouteBuilder(
- ILoggerFactory loggerFactory,
- ObjectPool<UriBuildingContext> objectPool,
- IInlineConstraintResolver constraintResolver)
+ if (loggerFactory == null)
{
- if (loggerFactory == null)
- {
- throw new ArgumentNullException(nameof(loggerFactory));
- }
+ throw new ArgumentNullException(nameof(loggerFactory));
+ }
- if (objectPool == null)
- {
- throw new ArgumentNullException(nameof(objectPool));
- }
+ if (objectPool == null)
+ {
+ throw new ArgumentNullException(nameof(objectPool));
+ }
- if (constraintResolver == null)
- {
- throw new ArgumentNullException(nameof(constraintResolver));
- }
+ if (constraintResolver == null)
+ {
+ throw new ArgumentNullException(nameof(constraintResolver));
+ }
- _urlEncoder = UrlEncoder.Default;
- _objectPool = objectPool;
- _constraintResolver = constraintResolver;
+ _urlEncoder = UrlEncoder.Default;
+ _objectPool = objectPool;
+ _constraintResolver = constraintResolver;
- _logger = loggerFactory.CreateLogger<TreeRouter>();
- _constraintLogger = loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
- }
+ _logger = loggerFactory.CreateLogger<TreeRouter>();
+ _constraintLogger = loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
+ }
- /// <summary>
- /// Adds a new inbound route to the <see cref="TreeRouter"/>.
- /// </summary>
- /// <param name="handler">The <see cref="IRouter"/> for handling the route.</param>
- /// <param name="routeTemplate">The <see cref="RouteTemplate"/> of the route.</param>
- /// <param name="routeName">The route name.</param>
- /// <param name="order">The route order.</param>
- /// <returns>The <see cref="InboundRouteEntry"/>.</returns>
- public InboundRouteEntry MapInbound(
- IRouter handler,
- RouteTemplate routeTemplate,
- string routeName,
- int order)
+ /// <summary>
+ /// Adds a new inbound route to the <see cref="TreeRouter"/>.
+ /// </summary>
+ /// <param name="handler">The <see cref="IRouter"/> for handling the route.</param>
+ /// <param name="routeTemplate">The <see cref="RouteTemplate"/> of the route.</param>
+ /// <param name="routeName">The route name.</param>
+ /// <param name="order">The route order.</param>
+ /// <returns>The <see cref="InboundRouteEntry"/>.</returns>
+ public InboundRouteEntry MapInbound(
+ IRouter handler,
+ RouteTemplate routeTemplate,
+ string routeName,
+ int order)
+ {
+ if (handler == null)
{
- if (handler == null)
- {
- throw new ArgumentNullException(nameof(handler));
- }
+ throw new ArgumentNullException(nameof(handler));
+ }
- if (routeTemplate == null)
- {
- throw new ArgumentNullException(nameof(routeTemplate));
- }
+ if (routeTemplate == null)
+ {
+ throw new ArgumentNullException(nameof(routeTemplate));
+ }
- var entry = new InboundRouteEntry()
- {
- Handler = handler,
- Order = order,
- Precedence = RoutePrecedence.ComputeInbound(routeTemplate),
- RouteName = routeName,
- RouteTemplate = routeTemplate,
- };
-
- var constraintBuilder = new RouteConstraintBuilder(_constraintResolver, routeTemplate.TemplateText);
- foreach (var parameter in routeTemplate.Parameters)
+ var entry = new InboundRouteEntry()
+ {
+ Handler = handler,
+ Order = order,
+ Precedence = RoutePrecedence.ComputeInbound(routeTemplate),
+ RouteName = routeName,
+ RouteTemplate = routeTemplate,
+ };
+
+ var constraintBuilder = new RouteConstraintBuilder(_constraintResolver, routeTemplate.TemplateText);
+ foreach (var parameter in routeTemplate.Parameters)
+ {
+ if (parameter.InlineConstraints != null)
{
- if (parameter.InlineConstraints != null)
+ if (parameter.IsOptional)
{
- if (parameter.IsOptional)
- {
- constraintBuilder.SetOptional(parameter.Name);
- }
-
- foreach (var constraint in parameter.InlineConstraints)
- {
- constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
- }
+ constraintBuilder.SetOptional(parameter.Name);
}
- }
-
- entry.Constraints = constraintBuilder.Build();
- entry.Defaults = new RouteValueDictionary();
- foreach (var parameter in entry.RouteTemplate.Parameters)
- {
- if (parameter.DefaultValue != null)
+ foreach (var constraint in parameter.InlineConstraints)
{
- entry.Defaults.Add(parameter.Name, parameter.DefaultValue);
+ constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
}
}
-
- InboundEntries.Add(entry);
- return entry;
}
- /// <summary>
- /// Adds a new outbound route to the <see cref="TreeRouter"/>.
- /// </summary>
- /// <param name="handler">The <see cref="IRouter"/> for handling the link generation.</param>
- /// <param name="routeTemplate">The <see cref="RouteTemplate"/> of the route.</param>
- /// <param name="requiredLinkValues">The <see cref="RouteValueDictionary"/> containing the route values.</param>
- /// <param name="routeName">The route name.</param>
- /// <param name="order">The route order.</param>
- /// <returns>The <see cref="OutboundRouteEntry"/>.</returns>
- public OutboundRouteEntry MapOutbound(
- IRouter handler,
- RouteTemplate routeTemplate,
- RouteValueDictionary requiredLinkValues,
- string routeName,
- int order)
+ entry.Constraints = constraintBuilder.Build();
+
+ entry.Defaults = new RouteValueDictionary();
+ foreach (var parameter in entry.RouteTemplate.Parameters)
{
- if (handler == null)
+ if (parameter.DefaultValue != null)
{
- throw new ArgumentNullException(nameof(handler));
+ entry.Defaults.Add(parameter.Name, parameter.DefaultValue);
}
+ }
- if (routeTemplate == null)
- {
- throw new ArgumentNullException(nameof(routeTemplate));
- }
+ InboundEntries.Add(entry);
+ return entry;
+ }
- if (requiredLinkValues == null)
- {
- throw new ArgumentNullException(nameof(requiredLinkValues));
- }
+ /// <summary>
+ /// Adds a new outbound route to the <see cref="TreeRouter"/>.
+ /// </summary>
+ /// <param name="handler">The <see cref="IRouter"/> for handling the link generation.</param>
+ /// <param name="routeTemplate">The <see cref="RouteTemplate"/> of the route.</param>
+ /// <param name="requiredLinkValues">The <see cref="RouteValueDictionary"/> containing the route values.</param>
+ /// <param name="routeName">The route name.</param>
+ /// <param name="order">The route order.</param>
+ /// <returns>The <see cref="OutboundRouteEntry"/>.</returns>
+ public OutboundRouteEntry MapOutbound(
+ IRouter handler,
+ RouteTemplate routeTemplate,
+ RouteValueDictionary requiredLinkValues,
+ string routeName,
+ int order)
+ {
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
- var entry = new OutboundRouteEntry()
- {
- Handler = handler,
- Order = order,
- Precedence = RoutePrecedence.ComputeOutbound(routeTemplate),
- RequiredLinkValues = requiredLinkValues,
- RouteName = routeName,
- RouteTemplate = routeTemplate,
- };
-
- var constraintBuilder = new RouteConstraintBuilder(_constraintResolver, routeTemplate.TemplateText);
- foreach (var parameter in routeTemplate.Parameters)
+ if (routeTemplate == null)
+ {
+ throw new ArgumentNullException(nameof(routeTemplate));
+ }
+
+ if (requiredLinkValues == null)
+ {
+ throw new ArgumentNullException(nameof(requiredLinkValues));
+ }
+
+ var entry = new OutboundRouteEntry()
+ {
+ Handler = handler,
+ Order = order,
+ Precedence = RoutePrecedence.ComputeOutbound(routeTemplate),
+ RequiredLinkValues = requiredLinkValues,
+ RouteName = routeName,
+ RouteTemplate = routeTemplate,
+ };
+
+ var constraintBuilder = new RouteConstraintBuilder(_constraintResolver, routeTemplate.TemplateText);
+ foreach (var parameter in routeTemplate.Parameters)
+ {
+ if (parameter.InlineConstraints != null)
{
- if (parameter.InlineConstraints != null)
+ if (parameter.IsOptional)
{
- if (parameter.IsOptional)
- {
- constraintBuilder.SetOptional(parameter.Name);
- }
-
- foreach (var constraint in parameter.InlineConstraints)
- {
- constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
- }
+ constraintBuilder.SetOptional(parameter.Name);
}
- }
-
- entry.Constraints = constraintBuilder.Build();
- entry.Defaults = new RouteValueDictionary();
- foreach (var parameter in entry.RouteTemplate.Parameters)
- {
- if (parameter.DefaultValue != null)
+ foreach (var constraint in parameter.InlineConstraints)
{
- entry.Defaults.Add(parameter.Name, parameter.DefaultValue);
+ constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
}
}
-
- OutboundEntries.Add(entry);
- return entry;
}
- /// <summary>
- /// Gets the list of <see cref="InboundRouteEntry"/>.
- /// </summary>
- public IList<InboundRouteEntry> InboundEntries { get; } = new List<InboundRouteEntry>();
-
- /// <summary>
- /// Gets the list of <see cref="OutboundRouteEntry"/>.
- /// </summary>
- public IList<OutboundRouteEntry> OutboundEntries { get; } = new List<OutboundRouteEntry>();
-
- /// <summary>
- /// Builds a <see cref="TreeRouter"/> with the <see cref="InboundEntries"/>
- /// and <see cref="OutboundEntries"/> defined in this <see cref="TreeRouteBuilder"/>.
- /// </summary>
- /// <returns>The <see cref="TreeRouter"/>.</returns>
- public TreeRouter Build()
+ entry.Constraints = constraintBuilder.Build();
+
+ entry.Defaults = new RouteValueDictionary();
+ foreach (var parameter in entry.RouteTemplate.Parameters)
{
- return Build(version: 0);
+ if (parameter.DefaultValue != null)
+ {
+ entry.Defaults.Add(parameter.Name, parameter.DefaultValue);
+ }
}
- /// <summary>
- /// Builds a <see cref="TreeRouter"/> with the <see cref="InboundEntries"/>
- /// and <see cref="OutboundEntries"/> defined in this <see cref="TreeRouteBuilder"/>.
- /// </summary>
- /// <param name="version">The version of the <see cref="TreeRouter"/>.</param>
- /// <returns>The <see cref="TreeRouter"/>.</returns>
- public TreeRouter Build(int version)
- {
- // Tree route builder builds a tree for each of the different route orders defined by
- // the user. When a route needs to be matched, the matching algorithm in tree router
- // just iterates over the trees in ascending order when it tries to match the route.
- var trees = new Dictionary<int, UrlMatchingTree>();
+ OutboundEntries.Add(entry);
+ return entry;
+ }
- foreach (var entry in InboundEntries)
- {
- if (!trees.TryGetValue(entry.Order, out var tree))
- {
- tree = new UrlMatchingTree(entry.Order);
- trees.Add(entry.Order, tree);
- }
+ /// <summary>
+ /// Gets the list of <see cref="InboundRouteEntry"/>.
+ /// </summary>
+ public IList<InboundRouteEntry> InboundEntries { get; } = new List<InboundRouteEntry>();
- tree.AddEntry(entry);
- }
+ /// <summary>
+ /// Gets the list of <see cref="OutboundRouteEntry"/>.
+ /// </summary>
+ public IList<OutboundRouteEntry> OutboundEntries { get; } = new List<OutboundRouteEntry>();
- return new TreeRouter(
- trees.Values.OrderBy(tree => tree.Order).ToArray(),
- OutboundEntries,
- _urlEncoder,
- _objectPool,
- _logger,
- _constraintLogger,
- version);
- }
+ /// <summary>
+ /// Builds a <see cref="TreeRouter"/> with the <see cref="InboundEntries"/>
+ /// and <see cref="OutboundEntries"/> defined in this <see cref="TreeRouteBuilder"/>.
+ /// </summary>
+ /// <returns>The <see cref="TreeRouter"/>.</returns>
+ public TreeRouter Build()
+ {
+ return Build(version: 0);
+ }
+
+ /// <summary>
+ /// Builds a <see cref="TreeRouter"/> with the <see cref="InboundEntries"/>
+ /// and <see cref="OutboundEntries"/> defined in this <see cref="TreeRouteBuilder"/>.
+ /// </summary>
+ /// <param name="version">The version of the <see cref="TreeRouter"/>.</param>
+ /// <returns>The <see cref="TreeRouter"/>.</returns>
+ public TreeRouter Build(int version)
+ {
+ // Tree route builder builds a tree for each of the different route orders defined by
+ // the user. When a route needs to be matched, the matching algorithm in tree router
+ // just iterates over the trees in ascending order when it tries to match the route.
+ var trees = new Dictionary<int, UrlMatchingTree>();
- /// <summary>
- /// Removes all <see cref="InboundEntries"/> and <see cref="OutboundEntries"/> from this
- /// <see cref="TreeRouteBuilder"/>.
- /// </summary>
- public void Clear()
+ foreach (var entry in InboundEntries)
{
- InboundEntries.Clear();
- OutboundEntries.Clear();
+ if (!trees.TryGetValue(entry.Order, out var tree))
+ {
+ tree = new UrlMatchingTree(entry.Order);
+ trees.Add(entry.Order, tree);
+ }
+
+ tree.AddEntry(entry);
}
+
+ return new TreeRouter(
+ trees.Values.OrderBy(tree => tree.Order).ToArray(),
+ OutboundEntries,
+ _urlEncoder,
+ _objectPool,
+ _logger,
+ _constraintLogger,
+ version);
+ }
+
+ /// <summary>
+ /// Removes all <see cref="InboundEntries"/> and <see cref="OutboundEntries"/> from this
+ /// <see cref="TreeRouteBuilder"/>.
+ /// </summary>
+ public void Clear()
+ {
+ InboundEntries.Clear();
+ OutboundEntries.Clear();
}
}
diff --git a/src/Http/Routing/src/Tree/TreeRouter.cs b/src/Http/Routing/src/Tree/TreeRouter.cs
index 2e70a8bffc..5dc2a2c452 100644
--- a/src/Http/Routing/src/Tree/TreeRouter.cs
+++ b/src/Http/Routing/src/Tree/TreeRouter.cs
@@ -11,317 +11,316 @@ using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// An <see cref="IRouter"/> implementation for attribute routing.
+/// </summary>
+public partial class TreeRouter : IRouter
{
/// <summary>
- /// An <see cref="IRouter"/> implementation for attribute routing.
+ /// Key used by routing and action selection to match an attribute
+ /// route entry to a group of action descriptors.
+ /// </summary>
+ public static readonly string RouteGroupKey = "!__route_group";
+
+ private readonly LinkGenerationDecisionTree _linkGenerationTree;
+ private readonly UrlMatchingTree[] _trees;
+ private readonly IDictionary<string, OutboundMatch> _namedEntries;
+
+ private readonly ILogger _logger;
+ private readonly ILogger _constraintLogger;
+
+ /// <summary>
+ /// Creates a new instance of <see cref="TreeRouter"/>.
/// </summary>
- public partial class TreeRouter : IRouter
+ /// <param name="trees">The list of <see cref="UrlMatchingTree"/> that contains the route entries.</param>
+ /// <param name="linkGenerationEntries">The set of <see cref="OutboundRouteEntry"/>.</param>
+ /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
+ /// <param name="objectPool">The <see cref="ObjectPool{T}"/>.</param>
+ /// <param name="routeLogger">The <see cref="ILogger"/> instance.</param>
+ /// <param name="constraintLogger">The <see cref="ILogger"/> instance used
+ /// in <see cref="RouteConstraintMatcher"/>.</param>
+ /// <param name="version">The version of this route.</param>
+ internal TreeRouter(
+ UrlMatchingTree[] trees,
+ IEnumerable<OutboundRouteEntry> linkGenerationEntries,
+ UrlEncoder urlEncoder,
+ ObjectPool<UriBuildingContext> objectPool,
+ ILogger routeLogger,
+ ILogger constraintLogger,
+ int version)
{
- /// <summary>
- /// Key used by routing and action selection to match an attribute
- /// route entry to a group of action descriptors.
- /// </summary>
- public static readonly string RouteGroupKey = "!__route_group";
-
- private readonly LinkGenerationDecisionTree _linkGenerationTree;
- private readonly UrlMatchingTree[] _trees;
- private readonly IDictionary<string, OutboundMatch> _namedEntries;
-
- private readonly ILogger _logger;
- private readonly ILogger _constraintLogger;
-
- /// <summary>
- /// Creates a new instance of <see cref="TreeRouter"/>.
- /// </summary>
- /// <param name="trees">The list of <see cref="UrlMatchingTree"/> that contains the route entries.</param>
- /// <param name="linkGenerationEntries">The set of <see cref="OutboundRouteEntry"/>.</param>
- /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
- /// <param name="objectPool">The <see cref="ObjectPool{T}"/>.</param>
- /// <param name="routeLogger">The <see cref="ILogger"/> instance.</param>
- /// <param name="constraintLogger">The <see cref="ILogger"/> instance used
- /// in <see cref="RouteConstraintMatcher"/>.</param>
- /// <param name="version">The version of this route.</param>
- internal TreeRouter(
- UrlMatchingTree[] trees,
- IEnumerable<OutboundRouteEntry> linkGenerationEntries,
- UrlEncoder urlEncoder,
- ObjectPool<UriBuildingContext> objectPool,
- ILogger routeLogger,
- ILogger constraintLogger,
- int version)
+ if (trees == null)
{
- if (trees == null)
- {
- throw new ArgumentNullException(nameof(trees));
- }
+ throw new ArgumentNullException(nameof(trees));
+ }
- if (linkGenerationEntries == null)
- {
- throw new ArgumentNullException(nameof(linkGenerationEntries));
- }
+ if (linkGenerationEntries == null)
+ {
+ throw new ArgumentNullException(nameof(linkGenerationEntries));
+ }
- if (urlEncoder == null)
- {
- throw new ArgumentNullException(nameof(urlEncoder));
- }
+ if (urlEncoder == null)
+ {
+ throw new ArgumentNullException(nameof(urlEncoder));
+ }
- if (objectPool == null)
- {
- throw new ArgumentNullException(nameof(objectPool));
- }
+ if (objectPool == null)
+ {
+ throw new ArgumentNullException(nameof(objectPool));
+ }
- if (routeLogger == null)
- {
- throw new ArgumentNullException(nameof(routeLogger));
- }
+ if (routeLogger == null)
+ {
+ throw new ArgumentNullException(nameof(routeLogger));
+ }
- if (constraintLogger == null)
- {
- throw new ArgumentNullException(nameof(constraintLogger));
- }
+ if (constraintLogger == null)
+ {
+ throw new ArgumentNullException(nameof(constraintLogger));
+ }
- _trees = trees;
- _logger = routeLogger;
- _constraintLogger = constraintLogger;
+ _trees = trees;
+ _logger = routeLogger;
+ _constraintLogger = constraintLogger;
- _namedEntries = new Dictionary<string, OutboundMatch>(StringComparer.OrdinalIgnoreCase);
+ _namedEntries = new Dictionary<string, OutboundMatch>(StringComparer.OrdinalIgnoreCase);
- var outboundMatches = new List<OutboundMatch>();
+ var outboundMatches = new List<OutboundMatch>();
- foreach (var entry in linkGenerationEntries)
- {
+ foreach (var entry in linkGenerationEntries)
+ {
- var binder = new TemplateBinder(urlEncoder, objectPool, entry.RouteTemplate, entry.Defaults);
- var outboundMatch = new OutboundMatch() { Entry = entry, TemplateBinder = binder };
- outboundMatches.Add(outboundMatch);
+ var binder = new TemplateBinder(urlEncoder, objectPool, entry.RouteTemplate, entry.Defaults);
+ var outboundMatch = new OutboundMatch() { Entry = entry, TemplateBinder = binder };
+ outboundMatches.Add(outboundMatch);
- // Skip unnamed entries
- if (entry.RouteName == null)
- {
- continue;
- }
+ // Skip unnamed entries
+ if (entry.RouteName == null)
+ {
+ continue;
+ }
- // We only need to keep one OutboundMatch per route template
- // so in case two entries have the same name and the same template we only keep
- // the first entry.
- if (_namedEntries.TryGetValue(entry.RouteName, out var namedMatch) &&
- !string.Equals(
- namedMatch.Entry.RouteTemplate.TemplateText,
- entry.RouteTemplate.TemplateText,
- StringComparison.OrdinalIgnoreCase))
- {
- throw new ArgumentException(
- Resources.FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(entry.RouteName),
- nameof(linkGenerationEntries));
- }
- else if (namedMatch == null)
- {
- _namedEntries.Add(entry.RouteName, outboundMatch);
- }
+ // We only need to keep one OutboundMatch per route template
+ // so in case two entries have the same name and the same template we only keep
+ // the first entry.
+ if (_namedEntries.TryGetValue(entry.RouteName, out var namedMatch) &&
+ !string.Equals(
+ namedMatch.Entry.RouteTemplate.TemplateText,
+ entry.RouteTemplate.TemplateText,
+ StringComparison.OrdinalIgnoreCase))
+ {
+ throw new ArgumentException(
+ Resources.FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(entry.RouteName),
+ nameof(linkGenerationEntries));
}
+ else if (namedMatch == null)
+ {
+ _namedEntries.Add(entry.RouteName, outboundMatch);
+ }
+ }
- // The decision tree will take care of ordering for these entries.
- _linkGenerationTree = new LinkGenerationDecisionTree(outboundMatches.ToArray());
+ // The decision tree will take care of ordering for these entries.
+ _linkGenerationTree = new LinkGenerationDecisionTree(outboundMatches.ToArray());
- Version = version;
- }
+ Version = version;
+ }
- /// <summary>
- /// Gets the version of this route.
- /// </summary>
- public int Version { get; }
+ /// <summary>
+ /// Gets the version of this route.
+ /// </summary>
+ public int Version { get; }
- internal IEnumerable<UrlMatchingTree> MatchingTrees => _trees;
+ internal IEnumerable<UrlMatchingTree> MatchingTrees => _trees;
- /// <inheritdoc />
- public VirtualPathData GetVirtualPath(VirtualPathContext context)
+ /// <inheritdoc />
+ public VirtualPathData GetVirtualPath(VirtualPathContext context)
+ {
+ if (context == null)
{
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
+ throw new ArgumentNullException(nameof(context));
+ }
- // If it's a named route we will try to generate a link directly and
- // if we can't, we will not try to generate it using an unnamed route.
- if (context.RouteName != null)
- {
- return GetVirtualPathForNamedRoute(context);
- }
+ // If it's a named route we will try to generate a link directly and
+ // if we can't, we will not try to generate it using an unnamed route.
+ if (context.RouteName != null)
+ {
+ return GetVirtualPathForNamedRoute(context);
+ }
- // The decision tree will give us back all entries that match the provided route data in the correct
- // order. We just need to iterate them and use the first one that can generate a link.
- var matches = _linkGenerationTree.GetMatches(context.Values, context.AmbientValues);
+ // The decision tree will give us back all entries that match the provided route data in the correct
+ // order. We just need to iterate them and use the first one that can generate a link.
+ var matches = _linkGenerationTree.GetMatches(context.Values, context.AmbientValues);
- if (matches == null)
- {
- return null;
- }
+ if (matches == null)
+ {
+ return null;
+ }
- for (var i = 0; i < matches.Count; i++)
+ for (var i = 0; i < matches.Count; i++)
+ {
+ var path = GenerateVirtualPath(context, matches[i].Match.Entry, matches[i].Match.TemplateBinder);
+ if (path != null)
{
- var path = GenerateVirtualPath(context, matches[i].Match.Entry, matches[i].Match.TemplateBinder);
- if (path != null)
- {
- return path;
- }
+ return path;
}
-
- return null;
}
- /// <inheritdoc />
- public async Task RouteAsync(RouteContext context)
+ return null;
+ }
+
+ /// <inheritdoc />
+ public async Task RouteAsync(RouteContext context)
+ {
+ foreach (var tree in _trees)
{
- foreach (var tree in _trees)
- {
- var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);
- var root = tree.Root;
+ var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);
+ var root = tree.Root;
- var treeEnumerator = new TreeEnumerator(root, tokenizer);
+ var treeEnumerator = new TreeEnumerator(root, tokenizer);
- // Create a snapshot before processing the route. We'll restore this snapshot before running each
- // to restore the state. This is likely an "empty" snapshot, which doesn't allocate.
- var snapshot = context.RouteData.PushState(router: null, values: null, dataTokens: null);
+ // Create a snapshot before processing the route. We'll restore this snapshot before running each
+ // to restore the state. This is likely an "empty" snapshot, which doesn't allocate.
+ var snapshot = context.RouteData.PushState(router: null, values: null, dataTokens: null);
- while (treeEnumerator.MoveNext())
+ while (treeEnumerator.MoveNext())
+ {
+ var node = treeEnumerator.Current;
+ foreach (var item in node.Matches)
{
- var node = treeEnumerator.Current;
- foreach (var item in node.Matches)
+ var entry = item.Entry;
+ var matcher = item.TemplateMatcher;
+
+ try
{
- var entry = item.Entry;
- var matcher = item.TemplateMatcher;
+ if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values))
+ {
+ continue;
+ }
- try
+ if (!RouteConstraintMatcher.Match(
+ entry.Constraints,
+ context.RouteData.Values,
+ context.HttpContext,
+ this,
+ RouteDirection.IncomingRequest,
+ _constraintLogger))
{
- if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values))
- {
- continue;
- }
-
- if (!RouteConstraintMatcher.Match(
- entry.Constraints,
- context.RouteData.Values,
- context.HttpContext,
- this,
- RouteDirection.IncomingRequest,
- _constraintLogger))
- {
- continue;
- }
-
- Log.RequestMatchedRoute(_logger, entry.RouteName, entry.RouteTemplate.TemplateText);
- context.RouteData.Routers.Add(entry.Handler);
-
- await entry.Handler.RouteAsync(context);
- if (context.Handler != null)
- {
- return;
- }
+ continue;
}
- finally
+
+ Log.RequestMatchedRoute(_logger, entry.RouteName, entry.RouteTemplate.TemplateText);
+ context.RouteData.Routers.Add(entry.Handler);
+
+ await entry.Handler.RouteAsync(context);
+ if (context.Handler != null)
+ {
+ return;
+ }
+ }
+ finally
+ {
+ if (context.Handler == null)
{
- if (context.Handler == null)
- {
- // Restore the original values to prevent polluting the route data.
- snapshot.Restore();
- }
+ // Restore the original values to prevent polluting the route data.
+ snapshot.Restore();
}
}
}
}
}
+ }
- private VirtualPathData GetVirtualPathForNamedRoute(VirtualPathContext context)
+ private VirtualPathData GetVirtualPathForNamedRoute(VirtualPathContext context)
+ {
+ if (_namedEntries.TryGetValue(context.RouteName, out var match))
{
- if (_namedEntries.TryGetValue(context.RouteName, out var match))
+ var path = GenerateVirtualPath(context, match.Entry, match.TemplateBinder);
+ if (path != null)
{
- var path = GenerateVirtualPath(context, match.Entry, match.TemplateBinder);
- if (path != null)
- {
- return path;
- }
+ return path;
}
- return null;
}
+ return null;
+ }
- private VirtualPathData GenerateVirtualPath(
- VirtualPathContext context,
- OutboundRouteEntry entry,
- TemplateBinder binder)
+ private VirtualPathData GenerateVirtualPath(
+ VirtualPathContext context,
+ OutboundRouteEntry entry,
+ TemplateBinder binder)
+ {
+ // In attribute the context includes the values that are used to select this entry - typically
+ // these will be the standard 'action', 'controller' and maybe 'area' tokens. However, we don't
+ // want to pass these to the link generation code, or else they will end up as query parameters.
+ //
+ // So, we need to exclude from here any values that are 'required link values', but aren't
+ // parameters in the template.
+ //
+ // Ex:
+ // template: api/Products/{action}
+ // required values: { id = "5", action = "Buy", Controller = "CoolProducts" }
+ //
+ // result: { id = "5", action = "Buy" }
+ var inputValues = new RouteValueDictionary();
+ foreach (var kvp in context.Values)
{
- // In attribute the context includes the values that are used to select this entry - typically
- // these will be the standard 'action', 'controller' and maybe 'area' tokens. However, we don't
- // want to pass these to the link generation code, or else they will end up as query parameters.
- //
- // So, we need to exclude from here any values that are 'required link values', but aren't
- // parameters in the template.
- //
- // Ex:
- // template: api/Products/{action}
- // required values: { id = "5", action = "Buy", Controller = "CoolProducts" }
- //
- // result: { id = "5", action = "Buy" }
- var inputValues = new RouteValueDictionary();
- foreach (var kvp in context.Values)
+ if (entry.RequiredLinkValues.ContainsKey(kvp.Key))
{
- if (entry.RequiredLinkValues.ContainsKey(kvp.Key))
- {
- var parameter = entry.RouteTemplate.GetParameter(kvp.Key);
+ var parameter = entry.RouteTemplate.GetParameter(kvp.Key);
- if (parameter == null)
- {
- continue;
- }
+ if (parameter == null)
+ {
+ continue;
}
-
- inputValues.Add(kvp.Key, kvp.Value);
- }
-
- var bindingResult = binder.GetValues(context.AmbientValues, inputValues);
- if (bindingResult == null)
- {
- // A required parameter in the template didn't get a value.
- return null;
}
- var matched = RouteConstraintMatcher.Match(
- entry.Constraints,
- bindingResult.CombinedValues,
- context.HttpContext,
- this,
- RouteDirection.UrlGeneration,
- _constraintLogger);
+ inputValues.Add(kvp.Key, kvp.Value);
+ }
- if (!matched)
- {
- // A constraint rejected this link.
- return null;
- }
+ var bindingResult = binder.GetValues(context.AmbientValues, inputValues);
+ if (bindingResult == null)
+ {
+ // A required parameter in the template didn't get a value.
+ return null;
+ }
- var pathData = entry.Handler.GetVirtualPath(context);
- if (pathData != null)
- {
- // If path is non-null then the target router short-circuited, we don't expect this
- // in typical MVC scenarios.
- return pathData;
- }
+ var matched = RouteConstraintMatcher.Match(
+ entry.Constraints,
+ bindingResult.CombinedValues,
+ context.HttpContext,
+ this,
+ RouteDirection.UrlGeneration,
+ _constraintLogger);
- var path = binder.BindValues(bindingResult.AcceptedValues);
- if (path == null)
- {
- return null;
- }
+ if (!matched)
+ {
+ // A constraint rejected this link.
+ return null;
+ }
- return new VirtualPathData(this, path);
+ var pathData = entry.Handler.GetVirtualPath(context);
+ if (pathData != null)
+ {
+ // If path is non-null then the target router short-circuited, we don't expect this
+ // in typical MVC scenarios.
+ return pathData;
}
- private static partial class Log
+ var path = binder.BindValues(bindingResult.AcceptedValues);
+ if (path == null)
{
- [LoggerMessage(1, LogLevel.Debug,
- "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'",
- EventName = "RequestMatchedRoute")]
- public static partial void RequestMatchedRoute(ILogger logger, string routeName, string routeTemplate);
+ return null;
}
+
+ return new VirtualPathData(this, path);
+ }
+
+ private static partial class Log
+ {
+ [LoggerMessage(1, LogLevel.Debug,
+ "Request successfully matched the route with name '{RouteName}' and template '{RouteTemplate}'",
+ EventName = "RequestMatchedRoute")]
+ public static partial void RequestMatchedRoute(ILogger logger, string routeName, string routeTemplate);
}
}
diff --git a/src/Http/Routing/src/Tree/UrlMatchingNode.cs b/src/Http/Routing/src/Tree/UrlMatchingNode.cs
index df7ef828c0..d9bee82da9 100644
--- a/src/Http/Routing/src/Tree/UrlMatchingNode.cs
+++ b/src/Http/Routing/src/Tree/UrlMatchingNode.cs
@@ -8,76 +8,75 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// A node in a <see cref="UrlMatchingTree"/>.
+/// </summary>
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+public class UrlMatchingNode
{
/// <summary>
- /// A node in a <see cref="UrlMatchingTree"/>.
+ /// Initializes a new instance of <see cref="UrlMatchingNode"/>.
/// </summary>
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- public class UrlMatchingNode
+ /// <param name="length">The length of the path to this node in the <see cref="UrlMatchingTree"/>.</param>
+ public UrlMatchingNode(int length)
{
- /// <summary>
- /// Initializes a new instance of <see cref="UrlMatchingNode"/>.
- /// </summary>
- /// <param name="length">The length of the path to this node in the <see cref="UrlMatchingTree"/>.</param>
- public UrlMatchingNode(int length)
- {
- Depth = length;
+ Depth = length;
- Matches = new List<InboundMatch>();
- Literals = new Dictionary<string, UrlMatchingNode>(StringComparer.OrdinalIgnoreCase);
- }
+ Matches = new List<InboundMatch>();
+ Literals = new Dictionary<string, UrlMatchingNode>(StringComparer.OrdinalIgnoreCase);
+ }
- /// <summary>
- /// Gets the length of the path to this node in the <see cref="UrlMatchingTree"/>.
- /// </summary>
- public int Depth { get; }
+ /// <summary>
+ /// Gets the length of the path to this node in the <see cref="UrlMatchingTree"/>.
+ /// </summary>
+ public int Depth { get; }
- /// <summary>
- /// Gets or sets a value indicating whether this node represents a catch all segment.
- /// </summary>
- public bool IsCatchAll { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this node represents a catch all segment.
+ /// </summary>
+ public bool IsCatchAll { get; set; }
- /// <summary>
- /// Gets the list of matching route entries associated with this node.
- /// </summary>
- /// <remarks>
- /// These entries are sorted by precedence then template.
- /// </remarks>
- public List<InboundMatch> Matches { get; }
+ /// <summary>
+ /// Gets the list of matching route entries associated with this node.
+ /// </summary>
+ /// <remarks>
+ /// These entries are sorted by precedence then template.
+ /// </remarks>
+ public List<InboundMatch> Matches { get; }
- /// <summary>
- /// Gets the literal segments following this segment.
- /// </summary>
- public Dictionary<string, UrlMatchingNode> Literals { get; }
+ /// <summary>
+ /// Gets the literal segments following this segment.
+ /// </summary>
+ public Dictionary<string, UrlMatchingNode> Literals { get; }
- /// <summary>
- /// Gets or sets the <see cref="UrlMatchingNode"/> representing
- /// parameter segments with constraints following this segment in the <see cref="TreeRouter"/>.
- /// </summary>
- public UrlMatchingNode ConstrainedParameters { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="UrlMatchingNode"/> representing
+ /// parameter segments with constraints following this segment in the <see cref="TreeRouter"/>.
+ /// </summary>
+ public UrlMatchingNode ConstrainedParameters { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="UrlMatchingNode"/> representing
- /// parameter segments following this segment in the <see cref="TreeRouter"/>.
- /// </summary>
- public UrlMatchingNode Parameters { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="UrlMatchingNode"/> representing
+ /// parameter segments following this segment in the <see cref="TreeRouter"/>.
+ /// </summary>
+ public UrlMatchingNode Parameters { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="UrlMatchingNode"/> representing
- /// catch all parameter segments with constraints following this segment in the <see cref="TreeRouter"/>.
- /// </summary>
- public UrlMatchingNode ConstrainedCatchAlls { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="UrlMatchingNode"/> representing
+ /// catch all parameter segments with constraints following this segment in the <see cref="TreeRouter"/>.
+ /// </summary>
+ public UrlMatchingNode ConstrainedCatchAlls { get; set; }
- /// <summary>
- /// Gets or sets the <see cref="UrlMatchingNode"/> representing
- /// catch all parameter segments following this segment in the <see cref="TreeRouter"/>.
- /// </summary>
- public UrlMatchingNode CatchAlls { get; set; }
+ /// <summary>
+ /// Gets or sets the <see cref="UrlMatchingNode"/> representing
+ /// catch all parameter segments following this segment in the <see cref="TreeRouter"/>.
+ /// </summary>
+ public UrlMatchingNode CatchAlls { get; set; }
- private string DebuggerToString()
- {
- return $"Length: {Depth}, Matches: {string.Join(" | ", Matches?.Select(m => $"({m.TemplateMatcher.Template.TemplateText})"))}";
- }
+ private string DebuggerToString()
+ {
+ return $"Length: {Depth}, Matches: {string.Join(" | ", Matches?.Select(m => $"({m.TemplateMatcher.Template.TemplateText})"))}";
}
}
diff --git a/src/Http/Routing/src/Tree/UrlMatchingTree.cs b/src/Http/Routing/src/Tree/UrlMatchingTree.cs
index 3de6e6074e..1c91b637b9 100644
--- a/src/Http/Routing/src/Tree/UrlMatchingTree.cs
+++ b/src/Http/Routing/src/Tree/UrlMatchingTree.cs
@@ -7,191 +7,190 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing.Template;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+/// <summary>
+/// A tree part of a <see cref="TreeRouter"/>.
+/// </summary>
+public class UrlMatchingTree
{
/// <summary>
- /// A tree part of a <see cref="TreeRouter"/>.
+ /// Initializes a new instance of <see cref="UrlMatchingTree"/>.
/// </summary>
- public class UrlMatchingTree
+ /// <param name="order">The order associated with routes in this <see cref="UrlMatchingTree"/>.</param>
+ public UrlMatchingTree(int order)
{
- /// <summary>
- /// Initializes a new instance of <see cref="UrlMatchingTree"/>.
- /// </summary>
- /// <param name="order">The order associated with routes in this <see cref="UrlMatchingTree"/>.</param>
- public UrlMatchingTree(int order)
- {
- Order = order;
- }
+ Order = order;
+ }
- /// <summary>
- /// Gets the order of the routes associated with this <see cref="UrlMatchingTree"/>.
- /// </summary>
- public int Order { get; }
+ /// <summary>
+ /// Gets the order of the routes associated with this <see cref="UrlMatchingTree"/>.
+ /// </summary>
+ public int Order { get; }
- /// <summary>
- /// Gets the root of the <see cref="UrlMatchingTree"/>.
- /// </summary>
- public UrlMatchingNode Root { get; } = new UrlMatchingNode(length: 0);
+ /// <summary>
+ /// Gets the root of the <see cref="UrlMatchingTree"/>.
+ /// </summary>
+ public UrlMatchingNode Root { get; } = new UrlMatchingNode(length: 0);
- internal void AddEntry(InboundRouteEntry entry)
+ internal void AddEntry(InboundRouteEntry entry)
+ {
+ // The url matching tree represents all the routes asociated with a given
+ // order. Each node in the tree represents all the different categories
+ // a segment can have for which there is a defined inbound route entry.
+ // Each node contains a set of Matches that indicate all the routes for which
+ // a URL is a potential match. This list contains the routes with the same
+ // number of segments and the routes with the same number of segments plus an
+ // additional catch all parameter (as it can be empty).
+ // For example, for a set of routes like:
+ // 'Customer/Index/{id}'
+ // '{Controller}/{Action}/{*parameters}'
+ //
+ // The route tree will look like:
+ // Root ->
+ // Literals: Customer ->
+ // Literals: Index ->
+ // Parameters: {id}
+ // Matches: 'Customer/Index/{id}'
+ // Parameters: {Controller} ->
+ // Parameters: {Action} ->
+ // Matches: '{Controller}/{Action}/{*parameters}'
+ // CatchAlls: {*parameters}
+ // Matches: '{Controller}/{Action}/{*parameters}'
+ //
+ // When the tree router tries to match a route, it iterates the list of url matching trees
+ // in ascending order. For each tree it traverses each node starting from the root in the
+ // following order: Literals, constrained parameters, parameters, constrained catch all routes, catch alls.
+ // When it gets to a node of the same length as the route its trying to match, it simply looks at the list of
+ // candidates (which is in precence order) and tries to match the url against it.
+ //
+
+ var current = Root;
+ var matcher = new TemplateMatcher(entry.RouteTemplate, entry.Defaults);
+
+ for (var i = 0; i < entry.RouteTemplate.Segments.Count; i++)
{
- // The url matching tree represents all the routes asociated with a given
- // order. Each node in the tree represents all the different categories
- // a segment can have for which there is a defined inbound route entry.
- // Each node contains a set of Matches that indicate all the routes for which
- // a URL is a potential match. This list contains the routes with the same
- // number of segments and the routes with the same number of segments plus an
- // additional catch all parameter (as it can be empty).
- // For example, for a set of routes like:
- // 'Customer/Index/{id}'
- // '{Controller}/{Action}/{*parameters}'
- //
- // The route tree will look like:
- // Root ->
- // Literals: Customer ->
- // Literals: Index ->
- // Parameters: {id}
- // Matches: 'Customer/Index/{id}'
- // Parameters: {Controller} ->
- // Parameters: {Action} ->
- // Matches: '{Controller}/{Action}/{*parameters}'
- // CatchAlls: {*parameters}
- // Matches: '{Controller}/{Action}/{*parameters}'
- //
- // When the tree router tries to match a route, it iterates the list of url matching trees
- // in ascending order. For each tree it traverses each node starting from the root in the
- // following order: Literals, constrained parameters, parameters, constrained catch all routes, catch alls.
- // When it gets to a node of the same length as the route its trying to match, it simply looks at the list of
- // candidates (which is in precence order) and tries to match the url against it.
- //
-
- var current = Root;
- var matcher = new TemplateMatcher(entry.RouteTemplate, entry.Defaults);
-
- for (var i = 0; i < entry.RouteTemplate.Segments.Count; i++)
+ var segment = entry.RouteTemplate.Segments[i];
+ if (!segment.IsSimple)
{
- var segment = entry.RouteTemplate.Segments[i];
- if (!segment.IsSimple)
+ // Treat complex segments as a constrained parameter
+ if (current.ConstrainedParameters == null)
{
- // Treat complex segments as a constrained parameter
- if (current.ConstrainedParameters == null)
- {
- current.ConstrainedParameters = new UrlMatchingNode(length: i + 1);
- }
-
- current = current.ConstrainedParameters;
- continue;
+ current.ConstrainedParameters = new UrlMatchingNode(length: i + 1);
}
- Debug.Assert(segment.Parts.Count == 1);
- var part = segment.Parts[0];
- if (part.IsLiteral)
- {
- if (!current.Literals.TryGetValue(part.Text, out var next))
- {
- next = new UrlMatchingNode(length: i + 1);
- current.Literals.Add(part.Text, next);
- }
-
- current = next;
- continue;
- }
+ current = current.ConstrainedParameters;
+ continue;
+ }
- // We accept templates that have intermediate optional values, but we ignore
- // those values for route matching. For that reason, we need to add the entry
- // to the list of matches, only if the remaining segments are optional. For example:
- // /{controller}/{action=Index}/{id} will be equivalent to /{controller}/{action}/{id}
- // for the purposes of route matching.
- if (part.IsParameter &&
- RemainingSegmentsAreOptional(entry.RouteTemplate.Segments, i))
+ Debug.Assert(segment.Parts.Count == 1);
+ var part = segment.Parts[0];
+ if (part.IsLiteral)
+ {
+ if (!current.Literals.TryGetValue(part.Text, out var next))
{
- current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
+ next = new UrlMatchingNode(length: i + 1);
+ current.Literals.Add(part.Text, next);
}
- if (part.IsParameter && part.InlineConstraints.Any() && !part.IsCatchAll)
- {
- if (current.ConstrainedParameters == null)
- {
- current.ConstrainedParameters = new UrlMatchingNode(length: i + 1);
- }
+ current = next;
+ continue;
+ }
- current = current.ConstrainedParameters;
- continue;
- }
+ // We accept templates that have intermediate optional values, but we ignore
+ // those values for route matching. For that reason, we need to add the entry
+ // to the list of matches, only if the remaining segments are optional. For example:
+ // /{controller}/{action=Index}/{id} will be equivalent to /{controller}/{action}/{id}
+ // for the purposes of route matching.
+ if (part.IsParameter &&
+ RemainingSegmentsAreOptional(entry.RouteTemplate.Segments, i))
+ {
+ current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
+ }
- if (part.IsParameter && !part.IsCatchAll)
+ if (part.IsParameter && part.InlineConstraints.Any() && !part.IsCatchAll)
+ {
+ if (current.ConstrainedParameters == null)
{
- if (current.Parameters == null)
- {
- current.Parameters = new UrlMatchingNode(length: i + 1);
- }
-
- current = current.Parameters;
- continue;
+ current.ConstrainedParameters = new UrlMatchingNode(length: i + 1);
}
- if (part.IsParameter && part.InlineConstraints.Any() && part.IsCatchAll)
- {
- if (current.ConstrainedCatchAlls == null)
- {
- current.ConstrainedCatchAlls = new UrlMatchingNode(length: i + 1) { IsCatchAll = true };
- }
+ current = current.ConstrainedParameters;
+ continue;
+ }
- current = current.ConstrainedCatchAlls;
- continue;
+ if (part.IsParameter && !part.IsCatchAll)
+ {
+ if (current.Parameters == null)
+ {
+ current.Parameters = new UrlMatchingNode(length: i + 1);
}
- if (part.IsParameter && part.IsCatchAll)
- {
- if (current.CatchAlls == null)
- {
- current.CatchAlls = new UrlMatchingNode(length: i + 1) { IsCatchAll = true };
- }
+ current = current.Parameters;
+ continue;
+ }
- current = current.CatchAlls;
- continue;
+ if (part.IsParameter && part.InlineConstraints.Any() && part.IsCatchAll)
+ {
+ if (current.ConstrainedCatchAlls == null)
+ {
+ current.ConstrainedCatchAlls = new UrlMatchingNode(length: i + 1) { IsCatchAll = true };
}
- Debug.Fail("We shouldn't get here.");
+ current = current.ConstrainedCatchAlls;
+ continue;
}
- current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
- current.Matches.Sort((x, y) =>
+ if (part.IsParameter && part.IsCatchAll)
{
- var result = x.Entry.Precedence.CompareTo(y.Entry.Precedence);
- return result == 0 ? string.Compare(x.Entry.RouteTemplate.TemplateText, y.Entry.RouteTemplate.TemplateText, StringComparison.Ordinal) : result;
- });
+ if (current.CatchAlls == null)
+ {
+ current.CatchAlls = new UrlMatchingNode(length: i + 1) { IsCatchAll = true };
+ }
+
+ current = current.CatchAlls;
+ continue;
+ }
+
+ Debug.Fail("We shouldn't get here.");
}
- private static bool RemainingSegmentsAreOptional(IList<TemplateSegment> segments, int currentParameterIndex)
+ current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
+ current.Matches.Sort((x, y) =>
+ {
+ var result = x.Entry.Precedence.CompareTo(y.Entry.Precedence);
+ return result == 0 ? string.Compare(x.Entry.RouteTemplate.TemplateText, y.Entry.RouteTemplate.TemplateText, StringComparison.Ordinal) : result;
+ });
+ }
+
+ private static bool RemainingSegmentsAreOptional(IList<TemplateSegment> segments, int currentParameterIndex)
+ {
+ for (var i = currentParameterIndex; i < segments.Count; i++)
{
- for (var i = currentParameterIndex; i < segments.Count; i++)
+ if (!segments[i].IsSimple)
{
- if (!segments[i].IsSimple)
- {
- // /{complex}-{segment}
- return false;
- }
+ // /{complex}-{segment}
+ return false;
+ }
- var part = segments[i].Parts[0];
- if (!part.IsParameter)
- {
- // /literal
- return false;
- }
+ var part = segments[i].Parts[0];
+ if (!part.IsParameter)
+ {
+ // /literal
+ return false;
+ }
- var isOptionlCatchAllOrHasDefaultValue = part.IsOptional ||
- part.IsCatchAll ||
- part.DefaultValue != null;
+ var isOptionlCatchAllOrHasDefaultValue = part.IsOptional ||
+ part.IsCatchAll ||
+ part.DefaultValue != null;
- if (!isOptionlCatchAllOrHasDefaultValue)
- {
- // /{parameter}
- return false;
- }
+ if (!isOptionlCatchAllOrHasDefaultValue)
+ {
+ // /{parameter}
+ return false;
}
-
- return true;
}
+
+ return true;
}
}
diff --git a/src/Http/Routing/src/UriBuilderContextPooledObjectPolicy.cs b/src/Http/Routing/src/UriBuilderContextPooledObjectPolicy.cs
index 4a8aeabe21..b9df322028 100644
--- a/src/Http/Routing/src/UriBuilderContextPooledObjectPolicy.cs
+++ b/src/Http/Routing/src/UriBuilderContextPooledObjectPolicy.cs
@@ -4,19 +4,18 @@
using System.Text.Encodings.Web;
using Microsoft.Extensions.ObjectPool;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy<UriBuildingContext>
{
- internal class UriBuilderContextPooledObjectPolicy : IPooledObjectPolicy<UriBuildingContext>
+ public UriBuildingContext Create()
{
- public UriBuildingContext Create()
- {
- return new UriBuildingContext(UrlEncoder.Default);
- }
+ return new UriBuildingContext(UrlEncoder.Default);
+ }
- public bool Return(UriBuildingContext obj)
- {
- obj.Clear();
- return true;
- }
+ public bool Return(UriBuildingContext obj)
+ {
+ obj.Clear();
+ return true;
}
}
diff --git a/src/Http/Routing/src/UriBuildingContext.cs b/src/Http/Routing/src/UriBuildingContext.cs
index 4ac0699d21..a87a0c40c3 100644
--- a/src/Http/Routing/src/UriBuildingContext.cs
+++ b/src/Http/Routing/src/UriBuildingContext.cs
@@ -9,331 +9,330 @@ using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+[DebuggerDisplay("{DebuggerToString(),nq}")]
+internal class UriBuildingContext
{
- [DebuggerDisplay("{DebuggerToString(),nq}")]
- internal class UriBuildingContext
- {
- // Holds the 'accepted' parts of the path.
- private readonly StringBuilder _path;
- private readonly StringBuilder _query;
+ // Holds the 'accepted' parts of the path.
+ private readonly StringBuilder _path;
+ private readonly StringBuilder _query;
- // Holds the 'optional' parts of the path. We need a secondary buffer to handle cases where an optional
- // segment is in the middle of the uri. We don't know if we need to write it out - if it's
- // followed by other optional segments than we will just throw it away.
- private readonly List<BufferValue> _buffer;
- private readonly UrlEncoder _urlEncoder;
+ // Holds the 'optional' parts of the path. We need a secondary buffer to handle cases where an optional
+ // segment is in the middle of the uri. We don't know if we need to write it out - if it's
+ // followed by other optional segments than we will just throw it away.
+ private readonly List<BufferValue> _buffer;
+ private readonly UrlEncoder _urlEncoder;
- private bool _hasEmptySegment;
- private int _lastValueOffset;
+ private bool _hasEmptySegment;
+ private int _lastValueOffset;
- public UriBuildingContext(UrlEncoder urlEncoder)
- {
- _urlEncoder = urlEncoder;
- _path = new StringBuilder();
- _query = new StringBuilder();
- _buffer = new List<BufferValue>();
- PathWriter = new StringWriter(_path);
- QueryWriter = new StringWriter(_query);
- _lastValueOffset = -1;
-
- BufferState = SegmentState.Beginning;
- UriState = SegmentState.Beginning;
- }
+ public UriBuildingContext(UrlEncoder urlEncoder)
+ {
+ _urlEncoder = urlEncoder;
+ _path = new StringBuilder();
+ _query = new StringBuilder();
+ _buffer = new List<BufferValue>();
+ PathWriter = new StringWriter(_path);
+ QueryWriter = new StringWriter(_query);
+ _lastValueOffset = -1;
+
+ BufferState = SegmentState.Beginning;
+ UriState = SegmentState.Beginning;
+ }
- public bool LowercaseUrls { get; set; }
+ public bool LowercaseUrls { get; set; }
- public bool LowercaseQueryStrings { get; set; }
+ public bool LowercaseQueryStrings { get; set; }
- public bool AppendTrailingSlash { get; set; }
+ public bool AppendTrailingSlash { get; set; }
- public SegmentState BufferState { get; private set; }
+ public SegmentState BufferState { get; private set; }
- public SegmentState UriState { get; private set; }
+ public SegmentState UriState { get; private set; }
- public TextWriter PathWriter { get; }
+ public TextWriter PathWriter { get; }
- public TextWriter QueryWriter { get; }
+ public TextWriter QueryWriter { get; }
- public bool Accept(string? value)
- {
- return Accept(value, encodeSlashes: true);
- }
+ public bool Accept(string? value)
+ {
+ return Accept(value, encodeSlashes: true);
+ }
- public bool Accept(string? value, bool encodeSlashes)
+ public bool Accept(string? value, bool encodeSlashes)
+ {
+ if (string.IsNullOrEmpty(value))
{
- if (string.IsNullOrEmpty(value))
+ if (UriState == SegmentState.Inside || BufferState == SegmentState.Inside)
{
- if (UriState == SegmentState.Inside || BufferState == SegmentState.Inside)
- {
- // We can't write an 'empty' part inside a segment
- return false;
- }
- else
- {
- _hasEmptySegment = true;
- return true;
- }
+ // We can't write an 'empty' part inside a segment
+ return false;
}
- else if (_hasEmptySegment)
+ else
{
- // We're trying to write text after an empty segment - this is not allowed.
- return false;
+ _hasEmptySegment = true;
+ return true;
}
+ }
+ else if (_hasEmptySegment)
+ {
+ // We're trying to write text after an empty segment - this is not allowed.
+ return false;
+ }
- // NOTE: this needs to be above all 'EncodeValue' and _path.Append calls
+ // NOTE: this needs to be above all 'EncodeValue' and _path.Append calls
+ if (LowercaseUrls)
+ {
+ value = value.ToLowerInvariant();
+ }
+
+ var buffer = _buffer;
+ for (var i = 0; i < buffer.Count; i++)
+ {
+ var bufferValue = buffer[i].Value;
if (LowercaseUrls)
{
- value = value.ToLowerInvariant();
+ bufferValue = bufferValue.ToLowerInvariant();
}
- var buffer = _buffer;
- for (var i = 0; i < buffer.Count; i++)
+ if (buffer[i].RequiresEncoding)
{
- var bufferValue = buffer[i].Value;
- if (LowercaseUrls)
- {
- bufferValue = bufferValue.ToLowerInvariant();
- }
-
- if (buffer[i].RequiresEncoding)
- {
- EncodeValue(bufferValue);
- }
- else
- {
- _path.Append(bufferValue);
- }
+ EncodeValue(bufferValue);
}
- buffer.Clear();
-
- if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
+ else
{
- if (_path.Length != 0)
- {
- _path.Append('/');
- }
+ _path.Append(bufferValue);
}
+ }
+ buffer.Clear();
- BufferState = SegmentState.Inside;
- UriState = SegmentState.Inside;
-
- _lastValueOffset = _path.Length;
-
- // Allow the first segment to have a leading slash.
- // This prevents the leading slash from PathString segments from being encoded.
- if (_path.Length == 0 && value.Length > 0 && value[0] == '/')
+ if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
+ {
+ if (_path.Length != 0)
{
_path.Append('/');
- EncodeValue(value, 1, value.Length - 1, encodeSlashes);
- }
- else
- {
- EncodeValue(value, encodeSlashes);
}
-
- return true;
}
- public void Remove(string literal)
+ BufferState = SegmentState.Inside;
+ UriState = SegmentState.Inside;
+
+ _lastValueOffset = _path.Length;
+
+ // Allow the first segment to have a leading slash.
+ // This prevents the leading slash from PathString segments from being encoded.
+ if (_path.Length == 0 && value.Length > 0 && value[0] == '/')
+ {
+ _path.Append('/');
+ EncodeValue(value, 1, value.Length - 1, encodeSlashes);
+ }
+ else
{
- Debug.Assert(_lastValueOffset != -1, "Cannot invoke Remove more than once.");
- _path.Length = _lastValueOffset;
- _lastValueOffset = -1;
+ EncodeValue(value, encodeSlashes);
}
- public bool Buffer(string? value)
+ return true;
+ }
+
+ public void Remove(string literal)
+ {
+ Debug.Assert(_lastValueOffset != -1, "Cannot invoke Remove more than once.");
+ _path.Length = _lastValueOffset;
+ _lastValueOffset = -1;
+ }
+
+ public bool Buffer(string? value)
+ {
+ if (string.IsNullOrEmpty(value))
{
- if (string.IsNullOrEmpty(value))
+ if (BufferState == SegmentState.Inside)
{
- if (BufferState == SegmentState.Inside)
- {
- // We can't write an 'empty' part inside a segment
- return false;
- }
- else
- {
- _hasEmptySegment = true;
- return true;
- }
+ // We can't write an 'empty' part inside a segment
+ return false;
}
- else if (_hasEmptySegment)
+ else
{
- // We're trying to write text after an empty segment - this is not allowed.
- return false;
+ _hasEmptySegment = true;
+ return true;
}
+ }
+ else if (_hasEmptySegment)
+ {
+ // We're trying to write text after an empty segment - this is not allowed.
+ return false;
+ }
- if (UriState == SegmentState.Inside)
- {
- // We've already written part of this segment so there's no point in buffering, we need to
- // write out the rest or give up.
- var result = Accept(value);
+ if (UriState == SegmentState.Inside)
+ {
+ // We've already written part of this segment so there's no point in buffering, we need to
+ // write out the rest or give up.
+ var result = Accept(value);
- // We've already checked the conditions that could result in a rejected part, so this should
- // always be true.
- Debug.Assert(result);
+ // We've already checked the conditions that could result in a rejected part, so this should
+ // always be true.
+ Debug.Assert(result);
- return result;
- }
+ return result;
+ }
- if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
+ if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
+ {
+ if (_path.Length != 0 || _buffer.Count != 0)
{
- if (_path.Length != 0 || _buffer.Count != 0)
- {
- _buffer.Add(new BufferValue("/", requiresEncoding: false));
- }
-
- BufferState = SegmentState.Inside;
+ _buffer.Add(new BufferValue("/", requiresEncoding: false));
}
- _buffer.Add(new BufferValue(value, requiresEncoding: true));
- return true;
+ BufferState = SegmentState.Inside;
}
- public void EndSegment()
+ _buffer.Add(new BufferValue(value, requiresEncoding: true));
+ return true;
+ }
+
+ public void EndSegment()
+ {
+ BufferState = SegmentState.Beginning;
+ UriState = SegmentState.Beginning;
+ }
+
+ public void Clear()
+ {
+ _path.Clear();
+ if (_path.Capacity > 128)
{
- BufferState = SegmentState.Beginning;
- UriState = SegmentState.Beginning;
+ // We don't want to retain too much memory if this is getting pooled.
+ _path.Capacity = 128;
}
- public void Clear()
+ _query.Clear();
+ if (_query.Capacity > 128)
{
- _path.Clear();
- if (_path.Capacity > 128)
- {
- // We don't want to retain too much memory if this is getting pooled.
- _path.Capacity = 128;
- }
+ _query.Capacity = 128;
+ }
- _query.Clear();
- if (_query.Capacity > 128)
- {
- _query.Capacity = 128;
- }
+ _buffer.Clear();
+ if (_buffer.Capacity > 8)
+ {
+ _buffer.Capacity = 8;
+ }
- _buffer.Clear();
- if (_buffer.Capacity > 8)
- {
- _buffer.Capacity = 8;
- }
+ _hasEmptySegment = false;
+ _lastValueOffset = -1;
+ BufferState = SegmentState.Beginning;
+ UriState = SegmentState.Beginning;
- _hasEmptySegment = false;
- _lastValueOffset = -1;
- BufferState = SegmentState.Beginning;
- UriState = SegmentState.Beginning;
+ AppendTrailingSlash = false;
+ LowercaseQueryStrings = false;
+ LowercaseUrls = false;
+ }
- AppendTrailingSlash = false;
- LowercaseQueryStrings = false;
- LowercaseUrls = false;
+ // Used by TemplateBinder.BindValues - the legacy code path of IRouter
+ public override string ToString()
+ {
+ // We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
+ if (_path.Length > 0 && _path[0] != '/')
+ {
+ // Normalize generated paths so that they always contain a leading slash.
+ _path.Insert(0, '/');
}
- // Used by TemplateBinder.BindValues - the legacy code path of IRouter
- public override string ToString()
+ return _path.ToString() + _query.ToString();
+ }
+
+ // Used by TemplateBinder.TryBindValues - the new code path of LinkGenerator
+ public PathString ToPathString()
+ {
+ PathString pathString;
+
+ if (_path.Length > 0)
{
- // We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
- if (_path.Length > 0 && _path[0] != '/')
+ if (_path[0] != '/')
{
// Normalize generated paths so that they always contain a leading slash.
_path.Insert(0, '/');
}
- return _path.ToString() + _query.ToString();
- }
-
- // Used by TemplateBinder.TryBindValues - the new code path of LinkGenerator
- public PathString ToPathString()
- {
- PathString pathString;
-
- if (_path.Length > 0)
- {
- if (_path[0] != '/')
- {
- // Normalize generated paths so that they always contain a leading slash.
- _path.Insert(0, '/');
- }
-
- if (AppendTrailingSlash && _path[_path.Length - 1] != '/')
- {
- _path.Append('/');
- }
-
- pathString = new PathString(_path.ToString());
- }
- else
+ if (AppendTrailingSlash && _path[_path.Length - 1] != '/')
{
- pathString = PathString.Empty;
+ _path.Append('/');
}
- return pathString;
+ pathString = new PathString(_path.ToString());
}
-
- // Used by TemplateBinder.TryBindValues - the new code path of LinkGenerator
- public QueryString ToQueryString()
+ else
{
- if (_query.Length > 0 && _query[0] != '?')
- {
- // Normalize generated query so that they always contain a leading ?.
- _query.Insert(0, '?');
- }
-
- return new QueryString(_query.ToString());
+ pathString = PathString.Empty;
}
- private void EncodeValue(string value)
+ return pathString;
+ }
+
+ // Used by TemplateBinder.TryBindValues - the new code path of LinkGenerator
+ public QueryString ToQueryString()
+ {
+ if (_query.Length > 0 && _query[0] != '?')
{
- EncodeValue(value, encodeSlashes: true);
+ // Normalize generated query so that they always contain a leading ?.
+ _query.Insert(0, '?');
}
- private void EncodeValue(string value, bool encodeSlashes)
+ return new QueryString(_query.ToString());
+ }
+
+ private void EncodeValue(string value)
+ {
+ EncodeValue(value, encodeSlashes: true);
+ }
+
+ private void EncodeValue(string value, bool encodeSlashes)
+ {
+ EncodeValue(value, start: 0, characterCount: value.Length, encodeSlashes);
+ }
+
+ // For testing
+ internal void EncodeValue(string value, int start, int characterCount, bool encodeSlashes)
+ {
+ // Just encode everything if its ok to encode slashes
+ if (encodeSlashes)
{
- EncodeValue(value, start: 0, characterCount: value.Length, encodeSlashes);
+ _urlEncoder.Encode(PathWriter, value, start, characterCount);
}
-
- // For testing
- internal void EncodeValue(string value, int start, int characterCount, bool encodeSlashes)
+ else
{
- // Just encode everything if its ok to encode slashes
- if (encodeSlashes)
+ int end;
+ int length = start + characterCount;
+ while ((end = value.IndexOf('/', start, characterCount)) >= 0)
{
- _urlEncoder.Encode(PathWriter, value, start, characterCount);
+ _urlEncoder.Encode(PathWriter, value, start, end - start);
+ _path.Append('/');
+
+ start = end + 1;
+ characterCount = length - start;
}
- else
+
+ if (end < 0 && characterCount >= 0)
{
- int end;
- int length = start + characterCount;
- while ((end = value.IndexOf('/', start, characterCount)) >= 0)
- {
- _urlEncoder.Encode(PathWriter, value, start, end - start);
- _path.Append('/');
-
- start = end + 1;
- characterCount = length - start;
- }
-
- if (end < 0 && characterCount >= 0)
- {
- _urlEncoder.Encode(PathWriter, value, start, length - start);
- }
+ _urlEncoder.Encode(PathWriter, value, start, length - start);
}
}
+ }
- private string DebuggerToString()
- {
- return string.Format(CultureInfo.InvariantCulture, "{{Accepted: '{0}' Buffered: '{1}'}}", _path, string.Join("", _buffer));
- }
+ private string DebuggerToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{{Accepted: '{0}' Buffered: '{1}'}}", _path, string.Join("", _buffer));
+ }
- private readonly struct BufferValue
+ private readonly struct BufferValue
+ {
+ public BufferValue(string value, bool requiresEncoding)
{
- public BufferValue(string value, bool requiresEncoding)
- {
- Value = value;
- RequiresEncoding = requiresEncoding;
- }
+ Value = value;
+ RequiresEncoding = requiresEncoding;
+ }
- public bool RequiresEncoding { get; }
+ public bool RequiresEncoding { get; }
- public string Value { get; }
- }
+ public string Value { get; }
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs b/src/Http/Routing/test/FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs
index 2467bbe41b..0c976d78c3 100644
--- a/src/Http/Routing/test/FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/Benchmarks/EndpointRoutingBenchmarkTest.cs
@@ -11,57 +11,56 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class EndpointRoutingBenchmarkTest : IDisposable
{
- public class EndpointRoutingBenchmarkTest : IDisposable
- {
- private readonly HttpClient _client;
- private readonly IHost _host;
- private readonly TestServer _testServer;
+ private readonly HttpClient _client;
+ private readonly IHost _host;
+ private readonly TestServer _testServer;
- public EndpointRoutingBenchmarkTest()
- {
- // This switch and value are set by benchmark server when running the app for profiling.
- var args = new[] { "--scenarios", "PlaintextEndpointRouting" };
- var hostBuilder = Benchmarks.Program.GetHostBuilder(args);
+ public EndpointRoutingBenchmarkTest()
+ {
+ // This switch and value are set by benchmark server when running the app for profiling.
+ var args = new[] { "--scenarios", "PlaintextEndpointRouting" };
+ var hostBuilder = Benchmarks.Program.GetHostBuilder(args);
- _host = hostBuilder.Build();
+ _host = hostBuilder.Build();
- // Make sure we are using the right startup
- var configuration = _host.Services.GetService<IConfiguration>();
- var startupName = configuration["Startup"];
- Assert.Equal(nameof(Benchmarks.StartupUsingEndpointRouting), startupName);
+ // Make sure we are using the right startup
+ var configuration = _host.Services.GetService<IConfiguration>();
+ var startupName = configuration["Startup"];
+ Assert.Equal(nameof(Benchmarks.StartupUsingEndpointRouting), startupName);
- _testServer = _host.GetTestServer();
- _host.Start();
- _client = _testServer.CreateClient();
- _client.BaseAddress = new Uri("http://localhost");
- }
+ _testServer = _host.GetTestServer();
+ _host.Start();
+ _client = _testServer.CreateClient();
+ _client.BaseAddress = new Uri("http://localhost");
+ }
- [Fact]
- public async Task RouteEndpoint_ReturnsPlaintextResponse()
- {
- // Arrange
- var expectedContentType = "text/plain";
- var expectedContent = "Hello, World!";
+ [Fact]
+ public async Task RouteEndpoint_ReturnsPlaintextResponse()
+ {
+ // Arrange
+ var expectedContentType = "text/plain";
+ var expectedContent = "Hello, World!";
- // Act
- var response = await _client.GetAsync("/plaintext");
+ // Act
+ var response = await _client.GetAsync("/plaintext");
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- Assert.NotNull(response.Content.Headers.ContentType);
- Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
- public void Dispose()
- {
- _testServer.Dispose();
- _client.Dispose();
- _host.Dispose();
- }
+ public void Dispose()
+ {
+ _testServer.Dispose();
+ _client.Dispose();
+ _host.Dispose();
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/Benchmarks/RouterBenchmarkTest.cs b/src/Http/Routing/test/FunctionalTests/Benchmarks/RouterBenchmarkTest.cs
index e05acf80a0..b27f426012 100644
--- a/src/Http/Routing/test/FunctionalTests/Benchmarks/RouterBenchmarkTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/Benchmarks/RouterBenchmarkTest.cs
@@ -5,64 +5,63 @@ using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class RouterBenchmarkTest : IDisposable
{
- public class RouterBenchmarkTest : IDisposable
- {
- private readonly HttpClient _client;
- private readonly IHost _host;
- private readonly TestServer _testServer;
+ private readonly HttpClient _client;
+ private readonly IHost _host;
+ private readonly TestServer _testServer;
- public RouterBenchmarkTest()
- {
- // This switch and value are set by benchmark server when running the app for profiling.
- var args = new[] { "--scenarios", "PlaintextRouting" };
- var hostBuilder = Benchmarks.Program.GetHostBuilder(args);
+ public RouterBenchmarkTest()
+ {
+ // This switch and value are set by benchmark server when running the app for profiling.
+ var args = new[] { "--scenarios", "PlaintextRouting" };
+ var hostBuilder = Benchmarks.Program.GetHostBuilder(args);
- _host = hostBuilder.Build();
+ _host = hostBuilder.Build();
- // Make sure we are using the right startup
- var configuration = _host.Services.GetService<IConfiguration>();
- var startupName = configuration["Startup"];
- Assert.Equal(nameof(Benchmarks.StartupUsingRouter), startupName);
+ // Make sure we are using the right startup
+ var configuration = _host.Services.GetService<IConfiguration>();
+ var startupName = configuration["Startup"];
+ Assert.Equal(nameof(Benchmarks.StartupUsingRouter), startupName);
- _testServer = _host.GetTestServer();
- _host.Start();
- _client = _testServer.CreateClient();
- _client.BaseAddress = new Uri("http://localhost");
- }
+ _testServer = _host.GetTestServer();
+ _host.Start();
+ _client = _testServer.CreateClient();
+ _client.BaseAddress = new Uri("http://localhost");
+ }
- [Fact]
- public async Task RouteHandlerWritesResponse()
- {
- // Arrange
- var expectedContentType = "text/plain";
- var expectedContent = "Hello, World!";
+ [Fact]
+ public async Task RouteHandlerWritesResponse()
+ {
+ // Arrange
+ var expectedContentType = "text/plain";
+ var expectedContent = "Hello, World!";
- // Act
- var response = await _client.GetAsync("/plaintext");
+ // Act
+ var response = await _client.GetAsync("/plaintext");
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- Assert.NotNull(response.Content.Headers.ContentType);
- Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
- public void Dispose()
- {
- _testServer.Dispose();
- _client.Dispose();
- _host.Dispose();
- }
+ public void Dispose()
+ {
+ _testServer.Dispose();
+ _client.Dispose();
+ _host.Dispose();
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/EndpointRoutingIntegrationTest.cs b/src/Http/Routing/test/FunctionalTests/EndpointRoutingIntegrationTest.cs
index 0d6f2f4fa7..9c87e84e31 100644
--- a/src/Http/Routing/test/FunctionalTests/EndpointRoutingIntegrationTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/EndpointRoutingIntegrationTest.cs
@@ -13,301 +13,300 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class EndpointRoutingIntegrationTest
{
- public class EndpointRoutingIntegrationTest
+ private static readonly RequestDelegate TestDelegate = async context => await Task.Yield();
+ private static readonly string AuthErrorMessage = "Endpoint / contains authorization metadata, but a middleware was not found that supports authorization." +
+ Environment.NewLine +
+ "Configure your application startup by adding app.UseAuthorization() in the application startup code. " +
+ "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.";
+
+ private static readonly string CORSErrorMessage = "Endpoint / contains CORS metadata, but a middleware was not found that supports CORS." +
+ Environment.NewLine +
+ "Configure your application startup by adding app.UseCors() in the application startup code. " +
+ "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.";
+
+ [Fact]
+ public async Task AuthorizationMiddleware_WhenNoAuthMetadataIsConfigured()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseAuthorization();
+ app.UseEndpoints(b => b.Map("/", TestDelegate));
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddAuthorization();
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var response = await server.CreateRequest("/").SendAsync("GET");
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ [Fact]
+ public async Task AuthorizationMiddleware_WhenEndpointIsNotFound()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseAuthorization();
+ app.UseEndpoints(b => b.Map("/", TestDelegate));
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddAuthorization();
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var response = await server.CreateRequest("/not-found").SendAsync("GET");
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task AuthorizationMiddleware_WithAuthorizedEndpoint()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseAuthorization();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var response = await server.CreateRequest("/").SendAsync("GET");
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ [Fact]
+ public async Task AuthorizationMiddleware_NotConfigured_Throws()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
+
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
+ Assert.Equal(AuthErrorMessage, ex.Message);
+ }
+
+ [Fact]
+ public async Task AuthorizationMiddleware_NotConfigured_WhenEndpointIsNotFound()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var response = await server.CreateRequest("/not-found").SendAsync("GET");
+
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task AuthorizationMiddleware_ConfiguredBeforeRouting_Throws()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseAuthorization();
+ app.UseRouting();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
+ Assert.Equal(AuthErrorMessage, ex.Message);
+ }
+
+ [Fact]
+ public async Task AuthorizationMiddleware_ConfiguredAfterRouting_Throws()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
+ app.UseAuthorization();
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
+ Assert.Equal(AuthErrorMessage, ex.Message);
+ }
+
+ [Fact]
+ public async Task CorsMiddleware_WithCorsEndpoint()
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseCors();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireCors(policy => policy.AllowAnyOrigin()));
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddCors();
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var response = await server.CreateRequest("/").SendAsync("PUT");
+
+ response.EnsureSuccessStatusCode();
+ }
+
+ [Fact]
+ public async Task CorsMiddleware_ConfiguredBeforeRouting_Throws()
{
- private static readonly RequestDelegate TestDelegate = async context => await Task.Yield();
- private static readonly string AuthErrorMessage = "Endpoint / contains authorization metadata, but a middleware was not found that supports authorization." +
- Environment.NewLine +
- "Configure your application startup by adding app.UseAuthorization() in the application startup code. " +
- "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.";
-
- private static readonly string CORSErrorMessage = "Endpoint / contains CORS metadata, but a middleware was not found that supports CORS." +
- Environment.NewLine +
- "Configure your application startup by adding app.UseCors() in the application startup code. " +
- "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.";
-
- [Fact]
- public async Task AuthorizationMiddleware_WhenNoAuthMetadataIsConfigured()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseAuthorization();
- app.UseEndpoints(b => b.Map("/", TestDelegate));
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddAuthorization();
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var response = await server.CreateRequest("/").SendAsync("GET");
-
- response.EnsureSuccessStatusCode();
- }
-
- [Fact]
- public async Task AuthorizationMiddleware_WhenEndpointIsNotFound()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseAuthorization();
- app.UseEndpoints(b => b.Map("/", TestDelegate));
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddAuthorization();
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var response = await server.CreateRequest("/not-found").SendAsync("GET");
-
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Fact]
- public async Task AuthorizationMiddleware_WithAuthorizedEndpoint()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseAuthorization();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var response = await server.CreateRequest("/").SendAsync("GET");
-
- response.EnsureSuccessStatusCode();
- }
-
- [Fact]
- public async Task AuthorizationMiddleware_NotConfigured_Throws()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
-
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
- Assert.Equal(AuthErrorMessage, ex.Message);
- }
-
- [Fact]
- public async Task AuthorizationMiddleware_NotConfigured_WhenEndpointIsNotFound()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var response = await server.CreateRequest("/not-found").SendAsync("GET");
-
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Fact]
- public async Task AuthorizationMiddleware_ConfiguredBeforeRouting_Throws()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseAuthorization();
- app.UseRouting();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
- Assert.Equal(AuthErrorMessage, ex.Message);
- }
-
- [Fact]
- public async Task AuthorizationMiddleware_ConfiguredAfterRouting_Throws()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
- app.UseAuthorization();
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
- Assert.Equal(AuthErrorMessage, ex.Message);
- }
-
- [Fact]
- public async Task CorsMiddleware_WithCorsEndpoint()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseCors();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireCors(policy => policy.AllowAnyOrigin()));
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddCors();
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var response = await server.CreateRequest("/").SendAsync("PUT");
-
- response.EnsureSuccessStatusCode();
- }
-
- [Fact]
- public async Task CorsMiddleware_ConfiguredBeforeRouting_Throws()
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseCors();
- app.UseRouting();
- app.UseEndpoints(b => b.Map("/", TestDelegate).RequireCors(policy => policy.AllowAnyOrigin()));
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddCors();
- services.AddRouting();
- })
- .Build();
-
- using var server = host.GetTestServer();
-
- await host.StartAsync();
-
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
- Assert.Equal(CORSErrorMessage, ex.Message);
- }
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseCors();
+ app.UseRouting();
+ app.UseEndpoints(b => b.Map("/", TestDelegate).RequireCors(policy => policy.AllowAnyOrigin()));
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddCors();
+ services.AddRouting();
+ })
+ .Build();
+
+ using var server = host.GetTestServer();
+
+ await host.StartAsync();
+
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
+ Assert.Equal(CORSErrorMessage, ex.Message);
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/EndpointRoutingSampleTest.cs b/src/Http/Routing/test/FunctionalTests/EndpointRoutingSampleTest.cs
index 36594c7120..a3cb971969 100644
--- a/src/Http/Routing/test/FunctionalTests/EndpointRoutingSampleTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/EndpointRoutingSampleTest.cs
@@ -10,230 +10,229 @@ using Microsoft.Extensions.Hosting;
using RoutingWebSite;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class EndpointRoutingSampleTest : IDisposable
{
- public class EndpointRoutingSampleTest : IDisposable
+ private readonly HttpClient _client;
+ private readonly IHost _host;
+ private readonly TestServer _testServer;
+
+ public EndpointRoutingSampleTest()
+ {
+ var hostBuilder = Program.GetHostBuilder(new[] { Program.EndpointRoutingScenario, });
+ _host = hostBuilder.Build();
+
+ _testServer = _host.GetTestServer();
+ _host.Start();
+
+ _client = _testServer.CreateClient();
+ _client.BaseAddress = new Uri("http://localhost");
+ }
+
+ [Theory]
+ [InlineData("Branch1")]
+ [InlineData("Branch2")]
+ public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
+ {
+ // Arrange
+ var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task MatchesRootPath_AndReturnsPlaintext()
+ {
+ // Arrange
+ var expectedContentType = "text/plain";
+
+ // Act
+ var response = await _client.GetAsync("/");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
+ }
+
+ [Fact]
+ public async Task MatchesStaticRouteTemplate_AndReturnsPlaintext()
+ {
+ // Arrange
+ var expectedContentType = "text/plain";
+ var expectedContent = "Plain text!";
+
+ // Act
+ var response = await _client.GetAsync("/plaintext");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
+
+ [Fact]
+ public async Task MatchesHelloMiddleware_AndReturnsPlaintext()
+ {
+ // Arrange
+ var expectedContentType = "text/plain";
+ var expectedContent = "Hello World";
+
+ // Act
+ var response = await _client.GetAsync("/helloworld");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ Assert.NotNull(response.Content.Headers.ContentType);
+ Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
+
+ [Fact]
+ public async Task MatchesEndpoint_WithSuccessfulConstraintMatch()
+ {
+ // Arrange
+ var expectedContent = "WithConstraints";
+
+ // Act
+ var response = await _client.GetAsync("/withconstraints/555_001");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
+
+ [Fact]
+ public async Task DoesNotMatchEndpoint_IfConstraintMatchFails()
+ {
+ // Arrange & Act
+ var response = await _client.GetAsync("/withconstraints/555");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task MatchesEndpoint_WithSuccessful_OptionalConstraintMatch()
+ {
+ // Arrange
+ var expectedContent = "withoptionalconstraints";
+
+ // Act
+ var response = await _client.GetAsync("/withoptionalconstraints/555_001");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
+
+ [Fact]
+ public async Task MatchesEndpoint_WithSuccessful_OptionalConstraintMatch_NoValueForParameter()
+ {
+ // Arrange
+ var expectedContent = "withoptionalconstraints";
+
+ // Act
+ var response = await _client.GetAsync("/withoptionalconstraints");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedContent, actualContent);
+ }
+
+ [Fact]
+ public async Task DoesNotMatchEndpoint_IfOptionalConstraintMatchFails()
+ {
+ // Arrange & Act
+ var response = await _client.GetAsync("/withoptionalconstraints/555");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("/WithSingleAsteriskCatchAll/a/b/c", "Link: /WithSingleAsteriskCatchAll/a%2Fb%2Fc")]
+ [InlineData("/WithSingleAsteriskCatchAll/a/b b1/c c1", "Link: /WithSingleAsteriskCatchAll/a%2Fb%20b1%2Fc%20c1")]
+ public async Task GeneratesLink_ToEndpointWithSingleAsteriskCatchAllParameter_EncodesValue(
+ string url,
+ string expected)
+ {
+ // Arrange & Act
+ var response = await _client.GetAsync(url);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expected, actualContent);
+ }
+
+ [Theory]
+ [InlineData("/WithDoubleAsteriskCatchAll/a/b/c", "Link: /WithDoubleAsteriskCatchAll/a/b/c")]
+ [InlineData("/WithDoubleAsteriskCatchAll/a/b/c/", "Link: /WithDoubleAsteriskCatchAll/a/b/c/")]
+ [InlineData("/WithDoubleAsteriskCatchAll/a//b/c", "Link: /WithDoubleAsteriskCatchAll/a//b/c")]
+ public async Task GeneratesLink_ToEndpointWithDoubleAsteriskCatchAllParameter_DoesNotEncodeSlashes(
+ string url,
+ string expected)
+ {
+ // Arrange & Act
+ var response = await _client.GetAsync(url);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expected, actualContent);
+ }
+
+ [Fact]
+ public async Task GeneratesLink_ToEndpointWithDoubleAsteriskCatchAllParameter_EncodesContentOtherThanSlashes()
+ {
+ // Arrange & Act
+ var response = await _client.GetAsync("/WithDoubleAsteriskCatchAll/a/b b1/c c1");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Content);
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal("Link: /WithDoubleAsteriskCatchAll/a/b%20b1/c%20c1", actualContent);
+ }
+
+ [Fact]
+ public async Task MapGet_HasConventionMetadata()
+ {
+ // Arrange & Act
+ var response = await _client.GetAsync("/convention");
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var actualContent = await response.Content.ReadAsStringAsync();
+ Assert.Equal("Has metadata", actualContent);
+ }
+
+ public void Dispose()
{
- private readonly HttpClient _client;
- private readonly IHost _host;
- private readonly TestServer _testServer;
-
- public EndpointRoutingSampleTest()
- {
- var hostBuilder = Program.GetHostBuilder(new[] { Program.EndpointRoutingScenario, });
- _host = hostBuilder.Build();
-
- _testServer = _host.GetTestServer();
- _host.Start();
-
- _client = _testServer.CreateClient();
- _client.BaseAddress = new Uri("http://localhost");
- }
-
- [Theory]
- [InlineData("Branch1")]
- [InlineData("Branch2")]
- public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
- {
- // Arrange
- var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
-
- // Act
- var response = await _client.SendAsync(message);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
- }
-
- [Fact]
- public async Task MatchesRootPath_AndReturnsPlaintext()
- {
- // Arrange
- var expectedContentType = "text/plain";
-
- // Act
- var response = await _client.GetAsync("/");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- Assert.NotNull(response.Content.Headers.ContentType);
- Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
- }
-
- [Fact]
- public async Task MatchesStaticRouteTemplate_AndReturnsPlaintext()
- {
- // Arrange
- var expectedContentType = "text/plain";
- var expectedContent = "Plain text!";
-
- // Act
- var response = await _client.GetAsync("/plaintext");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- Assert.NotNull(response.Content.Headers.ContentType);
- Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
-
- [Fact]
- public async Task MatchesHelloMiddleware_AndReturnsPlaintext()
- {
- // Arrange
- var expectedContentType = "text/plain";
- var expectedContent = "Hello World";
-
- // Act
- var response = await _client.GetAsync("/helloworld");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- Assert.NotNull(response.Content.Headers.ContentType);
- Assert.Equal(expectedContentType, response.Content.Headers.ContentType.MediaType);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
-
- [Fact]
- public async Task MatchesEndpoint_WithSuccessfulConstraintMatch()
- {
- // Arrange
- var expectedContent = "WithConstraints";
-
- // Act
- var response = await _client.GetAsync("/withconstraints/555_001");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
-
- [Fact]
- public async Task DoesNotMatchEndpoint_IfConstraintMatchFails()
- {
- // Arrange & Act
- var response = await _client.GetAsync("/withconstraints/555");
-
- // Assert
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Fact]
- public async Task MatchesEndpoint_WithSuccessful_OptionalConstraintMatch()
- {
- // Arrange
- var expectedContent = "withoptionalconstraints";
-
- // Act
- var response = await _client.GetAsync("/withoptionalconstraints/555_001");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
-
- [Fact]
- public async Task MatchesEndpoint_WithSuccessful_OptionalConstraintMatch_NoValueForParameter()
- {
- // Arrange
- var expectedContent = "withoptionalconstraints";
-
- // Act
- var response = await _client.GetAsync("/withoptionalconstraints");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedContent, actualContent);
- }
-
- [Fact]
- public async Task DoesNotMatchEndpoint_IfOptionalConstraintMatchFails()
- {
- // Arrange & Act
- var response = await _client.GetAsync("/withoptionalconstraints/555");
-
- // Assert
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Theory]
- [InlineData("/WithSingleAsteriskCatchAll/a/b/c", "Link: /WithSingleAsteriskCatchAll/a%2Fb%2Fc")]
- [InlineData("/WithSingleAsteriskCatchAll/a/b b1/c c1", "Link: /WithSingleAsteriskCatchAll/a%2Fb%20b1%2Fc%20c1")]
- public async Task GeneratesLink_ToEndpointWithSingleAsteriskCatchAllParameter_EncodesValue(
- string url,
- string expected)
- {
- // Arrange & Act
- var response = await _client.GetAsync(url);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expected, actualContent);
- }
-
- [Theory]
- [InlineData("/WithDoubleAsteriskCatchAll/a/b/c", "Link: /WithDoubleAsteriskCatchAll/a/b/c")]
- [InlineData("/WithDoubleAsteriskCatchAll/a/b/c/", "Link: /WithDoubleAsteriskCatchAll/a/b/c/")]
- [InlineData("/WithDoubleAsteriskCatchAll/a//b/c", "Link: /WithDoubleAsteriskCatchAll/a//b/c")]
- public async Task GeneratesLink_ToEndpointWithDoubleAsteriskCatchAllParameter_DoesNotEncodeSlashes(
- string url,
- string expected)
- {
- // Arrange & Act
- var response = await _client.GetAsync(url);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal(expected, actualContent);
- }
-
- [Fact]
- public async Task GeneratesLink_ToEndpointWithDoubleAsteriskCatchAllParameter_EncodesContentOtherThanSlashes()
- {
- // Arrange & Act
- var response = await _client.GetAsync("/WithDoubleAsteriskCatchAll/a/b b1/c c1");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.NotNull(response.Content);
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal("Link: /WithDoubleAsteriskCatchAll/a/b%20b1/c%20c1", actualContent);
- }
-
- [Fact]
- public async Task MapGet_HasConventionMetadata()
- {
- // Arrange & Act
- var response = await _client.GetAsync("/convention");
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var actualContent = await response.Content.ReadAsStringAsync();
- Assert.Equal("Has metadata", actualContent);
- }
-
- public void Dispose()
- {
- _testServer.Dispose();
- _client.Dispose();
- _host.Dispose();
- }
+ _testServer.Dispose();
+ _client.Dispose();
+ _host.Dispose();
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/HostMatchingTests.cs b/src/Http/Routing/test/FunctionalTests/HostMatchingTests.cs
index 1f9b3787a5..e9f0ebb4e3 100644
--- a/src/Http/Routing/test/FunctionalTests/HostMatchingTests.cs
+++ b/src/Http/Routing/test/FunctionalTests/HostMatchingTests.cs
@@ -8,112 +8,111 @@ using System.Threading.Tasks;
using RoutingWebSite;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class HostMatchingTests : IClassFixture<RoutingTestFixture<UseEndpointRoutingStartup>>
{
- public class HostMatchingTests : IClassFixture<RoutingTestFixture<UseEndpointRoutingStartup>>
+ private readonly RoutingTestFixture<UseEndpointRoutingStartup> _fixture;
+
+ public HostMatchingTests(RoutingTestFixture<UseEndpointRoutingStartup> fixture)
+ {
+ _fixture = fixture;
+ }
+
+ private HttpClient CreateClient(string baseAddress)
+ {
+ var client = _fixture.CreateClient(baseAddress);
+
+ return client;
+ }
+
+ [Theory]
+ [InlineData("http://localhost")]
+ [InlineData("http://localhost:5001")]
+ public async Task Get_CatchAll(string baseAddress)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
+
+ // Act
+ var client = CreateClient(baseAddress);
+ var response = await client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("*:*", responseContent);
+ }
+
+ [Theory]
+ [InlineData("http://9000.0.0.1")]
+ [InlineData("http://9000.0.0.1:8888")]
+ public async Task Get_MatchWildcardDomain(string baseAddress)
{
- private readonly RoutingTestFixture<UseEndpointRoutingStartup> _fixture;
-
- public HostMatchingTests(RoutingTestFixture<UseEndpointRoutingStartup> fixture)
- {
- _fixture = fixture;
- }
-
- private HttpClient CreateClient(string baseAddress)
- {
- var client = _fixture.CreateClient(baseAddress);
-
- return client;
- }
-
- [Theory]
- [InlineData("http://localhost")]
- [InlineData("http://localhost:5001")]
- public async Task Get_CatchAll(string baseAddress)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
-
- // Act
- var client = CreateClient(baseAddress);
- var response = await client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("*:*", responseContent);
- }
-
- [Theory]
- [InlineData("http://9000.0.0.1")]
- [InlineData("http://9000.0.0.1:8888")]
- public async Task Get_MatchWildcardDomain(string baseAddress)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
-
- // Act
- var client = CreateClient(baseAddress);
- var response = await client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("*.0.0.1:*", responseContent);
- }
-
- [Theory]
- [InlineData("http://127.0.0.1")]
- [InlineData("http://127.0.0.1:8888")]
- public async Task Get_MatchDomain(string baseAddress)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
-
- // Act
- var client = CreateClient(baseAddress);
- var response = await client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("127.0.0.1:*", responseContent);
- }
-
- [Theory]
- [InlineData("http://9000.0.0.1:5000")]
- [InlineData("http://9000.0.0.1:5001")]
- public async Task Get_MatchWildcardDomainAndPort(string baseAddress)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
-
- // Act
- var client = CreateClient(baseAddress);
- var response = await client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("*.0.0.1:5000,*.0.0.1:5001", responseContent);
- }
-
- [Theory]
- [InlineData("http://www.contoso.com")]
- [InlineData("http://contoso.com")]
- public async Task Get_MatchWildcardDomainAndSubdomain(string baseAddress)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
-
- // Act
- var client = CreateClient(baseAddress);
- var response = await client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("contoso.com:*,*.contoso.com:*", responseContent);
- }
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
+
+ // Act
+ var client = CreateClient(baseAddress);
+ var response = await client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("*.0.0.1:*", responseContent);
+ }
+
+ [Theory]
+ [InlineData("http://127.0.0.1")]
+ [InlineData("http://127.0.0.1:8888")]
+ public async Task Get_MatchDomain(string baseAddress)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
+
+ // Act
+ var client = CreateClient(baseAddress);
+ var response = await client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("127.0.0.1:*", responseContent);
+ }
+
+ [Theory]
+ [InlineData("http://9000.0.0.1:5000")]
+ [InlineData("http://9000.0.0.1:5001")]
+ public async Task Get_MatchWildcardDomainAndPort(string baseAddress)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
+
+ // Act
+ var client = CreateClient(baseAddress);
+ var response = await client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("*.0.0.1:5000,*.0.0.1:5001", responseContent);
+ }
+
+ [Theory]
+ [InlineData("http://www.contoso.com")]
+ [InlineData("http://contoso.com")]
+ public async Task Get_MatchWildcardDomainAndSubdomain(string baseAddress)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, "api/DomainWildcard");
+
+ // Act
+ var client = CreateClient(baseAddress);
+ var response = await client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("contoso.com:*,*.contoso.com:*", responseContent);
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/MapFallbackTest.cs b/src/Http/Routing/test/FunctionalTests/MapFallbackTest.cs
index 2bafe08b88..151f6a2308 100644
--- a/src/Http/Routing/test/FunctionalTests/MapFallbackTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/MapFallbackTest.cs
@@ -7,100 +7,99 @@ using System.Threading.Tasks;
using RoutingWebSite;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class MapFallbackTest : IClassFixture<RoutingTestFixture<MapFallbackStartup>>
{
- public class MapFallbackTest : IClassFixture<RoutingTestFixture<MapFallbackStartup>>
+ private readonly RoutingTestFixture<MapFallbackStartup> _fixture;
+ private readonly HttpClient _client;
+
+ public MapFallbackTest(RoutingTestFixture<MapFallbackStartup> fixture)
+ {
+ _fixture = fixture;
+ _client = _fixture.CreateClient("http://localhost");
+ }
+
+ [Fact]
+ public async Task Get_HelloWorld()
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, "helloworld");
+
+ // Act
+ var response = await _client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("Hello World", responseContent);
+ }
+
+ [Theory]
+ [InlineData("prefix/favicon.ico")]
+ [InlineData("prefix/content/js/jquery.min.js")]
+ public async Task Get_FallbackWithPattern_FileName(string path)
{
- private readonly RoutingTestFixture<MapFallbackStartup> _fixture;
- private readonly HttpClient _client;
-
- public MapFallbackTest(RoutingTestFixture<MapFallbackStartup> fixture)
- {
- _fixture = fixture;
- _client = _fixture.CreateClient("http://localhost");
- }
-
- [Fact]
- public async Task Get_HelloWorld()
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, "helloworld");
-
- // Act
- var response = await _client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("Hello World", responseContent);
- }
-
- [Theory]
- [InlineData("prefix/favicon.ico")]
- [InlineData("prefix/content/js/jquery.min.js")]
- public async Task Get_FallbackWithPattern_FileName(string path)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, path);
-
- // Act
- var response = await _client.SendAsync(request);
-
- // Assert
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Theory]
- [InlineData("prefix")]
- [InlineData("prefix/")]
- [InlineData("prefix/store")]
- [InlineData("prefix/blog/read/18")]
- public async Task Get_FallbackWithPattern_NonFileName(string path)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, path);
-
- // Act
- var response = await _client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("FallbackCustomPattern", responseContent);
- }
-
- [Theory]
- [InlineData("favicon.ico")]
- [InlineData("content/js/jquery.min.js")]
- public async Task Get_Fallback_FileName(string path)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, path);
-
- // Act
- var response = await _client.SendAsync(request);
-
- // Assert
- Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
- }
-
- [Theory]
- [InlineData("")]
- [InlineData("/")]
- [InlineData("store")]
- [InlineData("blog/read/18")]
- public async Task Get_Fallback_NonFileName(string path)
- {
- // Arrange
- var request = new HttpRequestMessage(HttpMethod.Get, path);
-
- // Act
- var response = await _client.SendAsync(request);
- var responseContent = await response.Content.ReadAsStringAsync();
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal("FallbackDefaultPattern", responseContent);
- }
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, path);
+
+ // Act
+ var response = await _client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("prefix")]
+ [InlineData("prefix/")]
+ [InlineData("prefix/store")]
+ [InlineData("prefix/blog/read/18")]
+ public async Task Get_FallbackWithPattern_NonFileName(string path)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, path);
+
+ // Act
+ var response = await _client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("FallbackCustomPattern", responseContent);
+ }
+
+ [Theory]
+ [InlineData("favicon.ico")]
+ [InlineData("content/js/jquery.min.js")]
+ public async Task Get_Fallback_FileName(string path)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, path);
+
+ // Act
+ var response = await _client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("/")]
+ [InlineData("store")]
+ [InlineData("blog/read/18")]
+ public async Task Get_Fallback_NonFileName(string path)
+ {
+ // Arrange
+ var request = new HttpRequestMessage(HttpMethod.Get, path);
+
+ // Act
+ var response = await _client.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("FallbackDefaultPattern", responseContent);
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/RouteHandlerTest.cs b/src/Http/Routing/test/FunctionalTests/RouteHandlerTest.cs
index 6dc71ddd22..f3eff1389c 100644
--- a/src/Http/Routing/test/FunctionalTests/RouteHandlerTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/RouteHandlerTest.cs
@@ -11,56 +11,55 @@ using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class RouteHandlerTest
{
- public class RouteHandlerTest
+ [Fact]
+ public async Task MapPost_FromBodyWorksWithJsonPayload()
{
- [Fact]
- public async Task MapPost_FromBodyWorksWithJsonPayload()
- {
- using var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .Configure(app =>
- {
- app.UseRouting();
- app.UseEndpoints(b =>
- b.MapPost("/EchoTodo/{id}",
- (int id, Todo todo) => todo with { Id = id }));
- })
- .UseTestServer();
- })
- .ConfigureServices(services =>
- {
- services.AddRouting();
- })
- .Build();
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .Configure(app =>
+ {
+ app.UseRouting();
+ app.UseEndpoints(b =>
+ b.MapPost("/EchoTodo/{id}",
+ (int id, Todo todo) => todo with { Id = id }));
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddRouting();
+ })
+ .Build();
- using var server = host.GetTestServer();
- await host.StartAsync();
- var client = server.CreateClient();
+ using var server = host.GetTestServer();
+ await host.StartAsync();
+ var client = server.CreateClient();
- var todo = new Todo
- {
- Name = "Write tests!"
- };
+ var todo = new Todo
+ {
+ Name = "Write tests!"
+ };
- var response = await client.PostAsJsonAsync("/EchoTodo/42", todo);
- response.EnsureSuccessStatusCode();
+ var response = await client.PostAsJsonAsync("/EchoTodo/42", todo);
+ response.EnsureSuccessStatusCode();
- var echoedTodo = await response.Content.ReadFromJsonAsync<Todo>();
+ var echoedTodo = await response.Content.ReadFromJsonAsync<Todo>();
- Assert.NotNull(echoedTodo);
- Assert.Equal(todo.Name, echoedTodo?.Name);
- Assert.Equal(42, echoedTodo?.Id);
- }
+ Assert.NotNull(echoedTodo);
+ Assert.Equal(todo.Name, echoedTodo?.Name);
+ Assert.Equal(42, echoedTodo?.Id);
+ }
- private record Todo
- {
- public int Id { get; set; }
- public string Name { get; set; } = "Todo";
- public bool IsComplete { get; set; }
- }
+ private record Todo
+ {
+ public int Id { get; set; }
+ public string Name { get; set; } = "Todo";
+ public bool IsComplete { get; set; }
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/RouterSampleTest.cs b/src/Http/Routing/test/FunctionalTests/RouterSampleTest.cs
index 7bfb56af2d..66029737e9 100644
--- a/src/Http/Routing/test/FunctionalTests/RouterSampleTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/RouterSampleTest.cs
@@ -10,97 +10,96 @@ using Microsoft.Extensions.Hosting;
using RoutingWebSite;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class RouterSampleTest : IDisposable
{
- public class RouterSampleTest : IDisposable
+ private readonly HttpClient _client;
+ private readonly IHost _host;
+ private readonly TestServer _testServer;
+
+ public RouterSampleTest()
+ {
+ var hostBuilder = Program.GetHostBuilder(new[] { Program.RouterScenario, });
+ _host = hostBuilder.Build();
+ _testServer = _host.GetTestServer();
+ _host.Start();
+ _client = _testServer.CreateClient();
+ _client.BaseAddress = new Uri("http://localhost");
+ }
+
+ [Theory]
+ [InlineData("Branch1")]
+ [InlineData("Branch2")]
+ public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
+ {
+ // Arrange
+ var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task Routing_CanRouteRequestDelegate_ToSpecificHttpVerb()
+ {
+ // Arrange
+ var message = new HttpRequestMessage(HttpMethod.Get, "api/get/5");
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal($"API Get 5", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task Routing_CanRouteRequest_ToSpecificMiddleware()
+ {
+ // Arrange
+ var message = new HttpRequestMessage(HttpMethod.Get, "api/middleware");
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal($"Middleware!", await response.Content.ReadAsStringAsync());
+ }
+
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("POST")]
+ [InlineData("PUT")]
+ [InlineData("PATCH")]
+ [InlineData("DELETE")]
+ [InlineData("HEAD")]
+ [InlineData("OPTIONS")]
+ public async Task Routing_CanRouteRequest_ToDefaultHandler(string httpVerb)
+ {
+ // Arrange
+ var message = new HttpRequestMessage(new HttpMethod(httpVerb), "api/all/Joe/Duf");
+ var expectedBody = $"Verb = {httpVerb} - Path = /api/all/Joe/Duf - Route values - [name, Joe], [lastName, Duf]";
+
+ // Act
+ var response = await _client.SendAsync(message);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ var body = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expectedBody, body);
+ }
+
+ public void Dispose()
{
- private readonly HttpClient _client;
- private readonly IHost _host;
- private readonly TestServer _testServer;
-
- public RouterSampleTest()
- {
- var hostBuilder = Program.GetHostBuilder(new[] { Program.RouterScenario, });
- _host = hostBuilder.Build();
- _testServer = _host.GetTestServer();
- _host.Start();
- _client = _testServer.CreateClient();
- _client.BaseAddress = new Uri("http://localhost");
- }
-
- [Theory]
- [InlineData("Branch1")]
- [InlineData("Branch2")]
- public async Task Routing_CanRouteRequest_ToBranchRouter(string branch)
- {
- // Arrange
- var message = new HttpRequestMessage(HttpMethod.Get, $"{branch}/api/get/5");
-
- // Act
- var response = await _client.SendAsync(message);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal($"{branch} - API Get 5", await response.Content.ReadAsStringAsync());
- }
-
- [Fact]
- public async Task Routing_CanRouteRequestDelegate_ToSpecificHttpVerb()
- {
- // Arrange
- var message = new HttpRequestMessage(HttpMethod.Get, "api/get/5");
-
- // Act
- var response = await _client.SendAsync(message);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal($"API Get 5", await response.Content.ReadAsStringAsync());
- }
-
- [Fact]
- public async Task Routing_CanRouteRequest_ToSpecificMiddleware()
- {
- // Arrange
- var message = new HttpRequestMessage(HttpMethod.Get, "api/middleware");
-
- // Act
- var response = await _client.SendAsync(message);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal($"Middleware!", await response.Content.ReadAsStringAsync());
- }
-
- [Theory]
- [InlineData("GET")]
- [InlineData("POST")]
- [InlineData("PUT")]
- [InlineData("PATCH")]
- [InlineData("DELETE")]
- [InlineData("HEAD")]
- [InlineData("OPTIONS")]
- public async Task Routing_CanRouteRequest_ToDefaultHandler(string httpVerb)
- {
- // Arrange
- var message = new HttpRequestMessage(new HttpMethod(httpVerb), "api/all/Joe/Duf");
- var expectedBody = $"Verb = {httpVerb} - Path = /api/all/Joe/Duf - Route values - [name, Joe], [lastName, Duf]";
-
- // Act
- var response = await _client.SendAsync(message);
-
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- var body = await response.Content.ReadAsStringAsync();
- Assert.Equal(expectedBody, body);
- }
-
- public void Dispose()
- {
- _testServer.Dispose();
- _client.Dispose();
- _host.Dispose();
- }
+ _testServer.Dispose();
+ _client.Dispose();
+ _host.Dispose();
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/RoutingTestFixture.cs b/src/Http/Routing/test/FunctionalTests/RoutingTestFixture.cs
index f4c0d63741..65f39e90bb 100644
--- a/src/Http/Routing/test/FunctionalTests/RoutingTestFixture.cs
+++ b/src/Http/Routing/test/FunctionalTests/RoutingTestFixture.cs
@@ -7,45 +7,44 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class RoutingTestFixture<TStartup> : IDisposable
{
- public class RoutingTestFixture<TStartup> : IDisposable
+ private readonly TestServer _server;
+
+ public RoutingTestFixture()
+ {
+ var host = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseStartup(typeof(TStartup))
+ .UseTestServer();
+ })
+ .Build();
+
+ _server = host.GetTestServer();
+
+ host.Start();
+
+ Client = _server.CreateClient();
+ Client.BaseAddress = new Uri("http://localhost");
+ }
+
+ public HttpClient Client { get; }
+
+ public HttpClient CreateClient(string baseAddress)
+ {
+ var client = _server.CreateClient();
+ client.BaseAddress = new Uri(baseAddress);
+
+ return client;
+ }
+
+ public void Dispose()
{
- private readonly TestServer _server;
-
- public RoutingTestFixture()
- {
- var host = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .UseStartup(typeof(TStartup))
- .UseTestServer();
- })
- .Build();
-
- _server = host.GetTestServer();
-
- host.Start();
-
- Client = _server.CreateClient();
- Client.BaseAddress = new Uri("http://localhost");
- }
-
- public HttpClient Client { get; }
-
- public HttpClient CreateClient(string baseAddress)
- {
- var client = _server.CreateClient();
- client.BaseAddress = new Uri(baseAddress);
-
- return client;
- }
-
- public void Dispose()
- {
- Client.Dispose();
- _server.Dispose();
- }
+ Client.Dispose();
+ _server.Dispose();
}
}
diff --git a/src/Http/Routing/test/FunctionalTests/WebHostBuilderExtensionsTest.cs b/src/Http/Routing/test/FunctionalTests/WebHostBuilderExtensionsTest.cs
index be75c72910..9d83a978c3 100644
--- a/src/Http/Routing/test/FunctionalTests/WebHostBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/FunctionalTests/WebHostBuilderExtensionsTest.cs
@@ -6,23 +6,23 @@ using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
-using Xunit;
using Microsoft.Extensions.Hosting;
+using Xunit;
-namespace Microsoft.AspNetCore.Routing.FunctionalTests
+namespace Microsoft.AspNetCore.Routing.FunctionalTests;
+
+public class WebHostBuilderExtensionsTest
{
- public class WebHostBuilderExtensionsTest
+ public static TheoryData<Action<IRouteBuilder>, HttpRequestMessage, string> MatchesRequest
{
- public static TheoryData<Action<IRouteBuilder>, HttpRequestMessage, string> MatchesRequest
+ get
{
- get
- {
- return new TheoryData<Action<IRouteBuilder>, HttpRequestMessage, string>()
+ return new TheoryData<Action<IRouteBuilder>, HttpRequestMessage, string>()
{
{
(rb) => rb.MapGet("greeting/{name}", (req, resp, routeData) => resp.WriteAsync($"Hello! {routeData.Values["name"]}")),
@@ -72,38 +72,37 @@ namespace Microsoft.AspNetCore.Routing.FunctionalTests
"James Biography"
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(MatchesRequest))]
- public async Task UseRouter_MapGet_MatchesRequest(Action<IRouteBuilder> routeBuilder, HttpRequestMessage request, string expected)
- {
- // Arrange
- using var host = new HostBuilder()
- .ConfigureWebHost(webhostbuilder =>
- {
- webhostbuilder
- .Configure(app =>
- {
- app.UseRouter(routeBuilder);
- })
- .UseTestServer();
- })
- .ConfigureServices(services => services.AddRouting())
- .Build();
+ [Theory]
+ [MemberData(nameof(MatchesRequest))]
+ public async Task UseRouter_MapGet_MatchesRequest(Action<IRouteBuilder> routeBuilder, HttpRequestMessage request, string expected)
+ {
+ // Arrange
+ using var host = new HostBuilder()
+ .ConfigureWebHost(webhostbuilder =>
+ {
+ webhostbuilder
+ .Configure(app =>
+ {
+ app.UseRouter(routeBuilder);
+ })
+ .UseTestServer();
+ })
+ .ConfigureServices(services => services.AddRouting())
+ .Build();
- var testServer = host.GetTestServer();
- await host.StartAsync();
- var client = testServer.CreateClient();
+ var testServer = host.GetTestServer();
+ await host.StartAsync();
+ var client = testServer.CreateClient();
- // Act
- var response = await client.SendAsync(request);
+ // Act
+ var response = await client.SendAsync(request);
- // Assert
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var actual = await response.Content.ReadAsStringAsync();
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ var actual = await response.Content.ReadAsStringAsync();
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs
index ec9c88e9e1..79f597390e 100644
--- a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs
@@ -16,357 +16,356 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public class EndpointRoutingApplicationBuilderExtensionsTest
{
- public class EndpointRoutingApplicationBuilderExtensionsTest
+ [Fact]
+ public void UseRouting_ServicesNotRegistered_Throws()
{
- [Fact]
- public void UseRouting_ServicesNotRegistered_Throws()
- {
- // Arrange
- var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
-
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => app.UseRouting());
-
- // Assert
- Assert.Equal(
- "Unable to find the required services. " +
- "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
- "inside the call to 'ConfigureServices(...)' in the application startup code.",
- ex.Message);
- }
-
- [Fact]
- public void UseEndpoint_ServicesNotRegistered_Throws()
- {
- // Arrange
- var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
-
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpoints(endpoints => { }));
-
- // Assert
- Assert.Equal(
- "Unable to find the required services. " +
- "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
- "inside the call to 'ConfigureServices(...)' in the application startup code.",
- ex.Message);
- }
-
- [Fact]
- public async Task UseRouting_ServicesRegistered_NoMatch_DoesNotSetFeature()
- {
- // Arrange
- var services = CreateServices();
-
- var app = new ApplicationBuilder(services);
-
- app.UseRouting();
+ // Arrange
+ var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => app.UseRouting());
+
+ // Assert
+ Assert.Equal(
+ "Unable to find the required services. " +
+ "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
+ "inside the call to 'ConfigureServices(...)' in the application startup code.",
+ ex.Message);
+ }
- var appFunc = app.Build();
- var httpContext = new DefaultHttpContext();
+ [Fact]
+ public void UseEndpoint_ServicesNotRegistered_Throws()
+ {
+ // Arrange
+ var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpoints(endpoints => { }));
+
+ // Assert
+ Assert.Equal(
+ "Unable to find the required services. " +
+ "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
+ "inside the call to 'ConfigureServices(...)' in the application startup code.",
+ ex.Message);
+ }
- // Act
- await appFunc(httpContext);
+ [Fact]
+ public async Task UseRouting_ServicesRegistered_NoMatch_DoesNotSetFeature()
+ {
+ // Arrange
+ var services = CreateServices();
- // Assert
- Assert.Null(httpContext.Features.Get<IEndpointFeature>());
- }
+ var app = new ApplicationBuilder(services);
- [Fact]
- public async Task UseRouting_ServicesRegistered_Match_DoesNotSetsFeature()
- {
- // Arrange
- var endpoint = new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("{*p}"),
- 0,
- EndpointMetadataCollection.Empty,
- "Test");
+ app.UseRouting();
- var services = CreateServices();
+ var appFunc = app.Build();
+ var httpContext = new DefaultHttpContext();
- var app = new ApplicationBuilder(services);
+ // Act
+ await appFunc(httpContext);
- app.UseRouting();
+ // Assert
+ Assert.Null(httpContext.Features.Get<IEndpointFeature>());
+ }
- app.UseEndpoints(endpoints =>
- {
- endpoints.DataSources.Add(new DefaultEndpointDataSource(endpoint));
- });
+ [Fact]
+ public async Task UseRouting_ServicesRegistered_Match_DoesNotSetsFeature()
+ {
+ // Arrange
+ var endpoint = new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("{*p}"),
+ 0,
+ EndpointMetadataCollection.Empty,
+ "Test");
- var appFunc = app.Build();
- var httpContext = new DefaultHttpContext();
+ var services = CreateServices();
- // Act
- await appFunc(httpContext);
+ var app = new ApplicationBuilder(services);
- // Assert
- var feature = httpContext.Features.Get<IEndpointFeature>();
- Assert.NotNull(feature);
- Assert.Same(endpoint, httpContext.GetEndpoint());
- }
+ app.UseRouting();
- [Fact]
- public void UseEndpoint_WithoutEndpointRoutingMiddleware_Throws()
+ app.UseEndpoints(endpoints =>
{
- // Arrange
- var services = CreateServices();
+ endpoints.DataSources.Add(new DefaultEndpointDataSource(endpoint));
+ });
- var app = new ApplicationBuilder(services);
+ var appFunc = app.Build();
+ var httpContext = new DefaultHttpContext();
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpoints(endpoints => { }));
+ // Act
+ await appFunc(httpContext);
- // Assert
- Assert.Equal(
- "EndpointRoutingMiddleware matches endpoints setup by EndpointMiddleware and so must be added to the request " +
- "execution pipeline before EndpointMiddleware. " +
- "Please add EndpointRoutingMiddleware by calling 'IApplicationBuilder.UseRouting' " +
- "inside the call to 'Configure(...)' in the application startup code.",
- ex.Message);
- }
+ // Assert
+ var feature = httpContext.Features.Get<IEndpointFeature>();
+ Assert.NotNull(feature);
+ Assert.Same(endpoint, httpContext.GetEndpoint());
+ }
- [Fact]
- public void UseEndpoint_WithApplicationBuilderMismatch_Throws()
- {
- // Arrange
- var services = CreateServices();
+ [Fact]
+ public void UseEndpoint_WithoutEndpointRoutingMiddleware_Throws()
+ {
+ // Arrange
+ var services = CreateServices();
- var app = new ApplicationBuilder(services);
+ var app = new ApplicationBuilder(services);
- app.UseRouting();
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => app.UseEndpoints(endpoints => { }));
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => app.Map("/Test", b => b.UseEndpoints(endpoints => { })));
+ // Assert
+ Assert.Equal(
+ "EndpointRoutingMiddleware matches endpoints setup by EndpointMiddleware and so must be added to the request " +
+ "execution pipeline before EndpointMiddleware. " +
+ "Please add EndpointRoutingMiddleware by calling 'IApplicationBuilder.UseRouting' " +
+ "inside the call to 'Configure(...)' in the application startup code.",
+ ex.Message);
+ }
- // Assert
- Assert.Equal(
- "The EndpointRoutingMiddleware and EndpointMiddleware must be added to the same IApplicationBuilder instance. " +
- "To use Endpoint Routing with 'Map(...)', make sure to call 'IApplicationBuilder.UseRouting' before " +
- "'IApplicationBuilder.UseEndpoints' for each branch of the middleware pipeline.",
- ex.Message);
- }
+ [Fact]
+ public void UseEndpoint_WithApplicationBuilderMismatch_Throws()
+ {
+ // Arrange
+ var services = CreateServices();
- [Fact]
- public async Task UseEndpoint_ServicesRegisteredAndEndpointRoutingRegistered_NoMatch_DoesNotSetFeature()
- {
- // Arrange
- var services = CreateServices();
+ var app = new ApplicationBuilder(services);
- var app = new ApplicationBuilder(services);
+ app.UseRouting();
- app.UseRouting();
- app.UseEndpoints(endpoints => { });
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => app.Map("/Test", b => b.UseEndpoints(endpoints => { })));
- var appFunc = app.Build();
- var httpContext = new DefaultHttpContext();
+ // Assert
+ Assert.Equal(
+ "The EndpointRoutingMiddleware and EndpointMiddleware must be added to the same IApplicationBuilder instance. " +
+ "To use Endpoint Routing with 'Map(...)', make sure to call 'IApplicationBuilder.UseRouting' before " +
+ "'IApplicationBuilder.UseEndpoints' for each branch of the middleware pipeline.",
+ ex.Message);
+ }
- // Act
- await appFunc(httpContext);
+ [Fact]
+ public async Task UseEndpoint_ServicesRegisteredAndEndpointRoutingRegistered_NoMatch_DoesNotSetFeature()
+ {
+ // Arrange
+ var services = CreateServices();
- // Assert
- Assert.Null(httpContext.Features.Get<IEndpointFeature>());
- }
+ var app = new ApplicationBuilder(services);
- [Fact]
- public void UseEndpoints_CallWithBuilder_SetsEndpointDataSource()
- {
- // Arrange
- var matcherEndpointDataSources = new List<EndpointDataSource>();
- var matcherFactoryMock = new Mock<MatcherFactory>();
- matcherFactoryMock
- .Setup(m => m.CreateMatcher(It.IsAny<EndpointDataSource>()))
- .Callback((EndpointDataSource arg) =>
- {
- matcherEndpointDataSources.Add(arg);
- })
- .Returns(new TestMatcher(false));
-
- var services = CreateServices(matcherFactoryMock.Object);
-
- var app = new ApplicationBuilder(services);
-
- // Act
- app.UseRouting();
- app.UseEndpoints(builder =>
- {
- builder.Map("/1", d => null).WithDisplayName("Test endpoint 1");
- builder.Map("/2", d => null).WithDisplayName("Test endpoint 2");
- });
+ app.UseRouting();
+ app.UseEndpoints(endpoints => { });
- app.UseRouting();
- app.UseEndpoints(builder =>
- {
- builder.Map("/3", d => null).WithDisplayName("Test endpoint 3");
- builder.Map("/4", d => null).WithDisplayName("Test endpoint 4");
- });
+ var appFunc = app.Build();
+ var httpContext = new DefaultHttpContext();
- // This triggers the middleware to be created and the matcher factory to be called
- // with the datasource we want to test
- var requestDelegate = app.Build();
- requestDelegate(new DefaultHttpContext());
+ // Act
+ await appFunc(httpContext);
- // Assert
- Assert.Equal(2, matcherEndpointDataSources.Count);
+ // Assert
+ Assert.Null(httpContext.Features.Get<IEndpointFeature>());
+ }
- // each UseRouter has its own data source collection
- Assert.Collection(matcherEndpointDataSources[0].Endpoints,
- e => Assert.Equal("Test endpoint 1", e.DisplayName),
- e => Assert.Equal("Test endpoint 2", e.DisplayName));
+ [Fact]
+ public void UseEndpoints_CallWithBuilder_SetsEndpointDataSource()
+ {
+ // Arrange
+ var matcherEndpointDataSources = new List<EndpointDataSource>();
+ var matcherFactoryMock = new Mock<MatcherFactory>();
+ matcherFactoryMock
+ .Setup(m => m.CreateMatcher(It.IsAny<EndpointDataSource>()))
+ .Callback((EndpointDataSource arg) =>
+ {
+ matcherEndpointDataSources.Add(arg);
+ })
+ .Returns(new TestMatcher(false));
- Assert.Collection(matcherEndpointDataSources[1].Endpoints,
- e => Assert.Equal("Test endpoint 3", e.DisplayName),
- e => Assert.Equal("Test endpoint 4", e.DisplayName));
+ var services = CreateServices(matcherFactoryMock.Object);
- var compositeEndpointBuilder = services.GetRequiredService<EndpointDataSource>();
+ var app = new ApplicationBuilder(services);
- // Global collection has all endpoints
- Assert.Collection(compositeEndpointBuilder.Endpoints,
- e => Assert.Equal("Test endpoint 1", e.DisplayName),
- e => Assert.Equal("Test endpoint 2", e.DisplayName),
- e => Assert.Equal("Test endpoint 3", e.DisplayName),
- e => Assert.Equal("Test endpoint 4", e.DisplayName));
- }
+ // Act
+ app.UseRouting();
+ app.UseEndpoints(builder =>
+ {
+ builder.Map("/1", d => null).WithDisplayName("Test endpoint 1");
+ builder.Map("/2", d => null).WithDisplayName("Test endpoint 2");
+ });
- // Verifies that it's possible to use endpoints and map together.
- [Fact]
- public void UseEndpoints_CallWithBuilder_SetsEndpointDataSource_WithMap()
+ app.UseRouting();
+ app.UseEndpoints(builder =>
{
- // Arrange
- var matcherEndpointDataSources = new List<EndpointDataSource>();
- var matcherFactoryMock = new Mock<MatcherFactory>();
- matcherFactoryMock
- .Setup(m => m.CreateMatcher(It.IsAny<EndpointDataSource>()))
- .Callback((EndpointDataSource arg) =>
- {
- matcherEndpointDataSources.Add(arg);
- })
- .Returns(new TestMatcher(false));
+ builder.Map("/3", d => null).WithDisplayName("Test endpoint 3");
+ builder.Map("/4", d => null).WithDisplayName("Test endpoint 4");
+ });
+
+ // This triggers the middleware to be created and the matcher factory to be called
+ // with the datasource we want to test
+ var requestDelegate = app.Build();
+ requestDelegate(new DefaultHttpContext());
+
+ // Assert
+ Assert.Equal(2, matcherEndpointDataSources.Count);
+
+ // each UseRouter has its own data source collection
+ Assert.Collection(matcherEndpointDataSources[0].Endpoints,
+ e => Assert.Equal("Test endpoint 1", e.DisplayName),
+ e => Assert.Equal("Test endpoint 2", e.DisplayName));
+
+ Assert.Collection(matcherEndpointDataSources[1].Endpoints,
+ e => Assert.Equal("Test endpoint 3", e.DisplayName),
+ e => Assert.Equal("Test endpoint 4", e.DisplayName));
+
+ var compositeEndpointBuilder = services.GetRequiredService<EndpointDataSource>();
+
+ // Global collection has all endpoints
+ Assert.Collection(compositeEndpointBuilder.Endpoints,
+ e => Assert.Equal("Test endpoint 1", e.DisplayName),
+ e => Assert.Equal("Test endpoint 2", e.DisplayName),
+ e => Assert.Equal("Test endpoint 3", e.DisplayName),
+ e => Assert.Equal("Test endpoint 4", e.DisplayName));
+ }
- var services = CreateServices(matcherFactoryMock.Object);
+ // Verifies that it's possible to use endpoints and map together.
+ [Fact]
+ public void UseEndpoints_CallWithBuilder_SetsEndpointDataSource_WithMap()
+ {
+ // Arrange
+ var matcherEndpointDataSources = new List<EndpointDataSource>();
+ var matcherFactoryMock = new Mock<MatcherFactory>();
+ matcherFactoryMock
+ .Setup(m => m.CreateMatcher(It.IsAny<EndpointDataSource>()))
+ .Callback((EndpointDataSource arg) =>
+ {
+ matcherEndpointDataSources.Add(arg);
+ })
+ .Returns(new TestMatcher(false));
- var app = new ApplicationBuilder(services);
+ var services = CreateServices(matcherFactoryMock.Object);
- // Act
- app.UseRouting();
+ var app = new ApplicationBuilder(services);
- app.Map("/foo", b =>
- {
- b.UseRouting();
- b.UseEndpoints(builder =>
- {
- builder.Map("/1", d => null).WithDisplayName("Test endpoint 1");
- builder.Map("/2", d => null).WithDisplayName("Test endpoint 2");
- });
- });
+ // Act
+ app.UseRouting();
- app.UseEndpoints(builder =>
+ app.Map("/foo", b =>
+ {
+ b.UseRouting();
+ b.UseEndpoints(builder =>
{
- builder.Map("/3", d => null).WithDisplayName("Test endpoint 3");
- builder.Map("/4", d => null).WithDisplayName("Test endpoint 4");
+ builder.Map("/1", d => null).WithDisplayName("Test endpoint 1");
+ builder.Map("/2", d => null).WithDisplayName("Test endpoint 2");
});
+ });
- // This triggers the middleware to be created and the matcher factory to be called
- // with the datasource we want to test
- var requestDelegate = app.Build();
- requestDelegate(new DefaultHttpContext());
- requestDelegate(new DefaultHttpContext() { Request = { Path = "/Foo", }, });
-
- // Assert
- Assert.Equal(2, matcherEndpointDataSources.Count);
+ app.UseEndpoints(builder =>
+ {
+ builder.Map("/3", d => null).WithDisplayName("Test endpoint 3");
+ builder.Map("/4", d => null).WithDisplayName("Test endpoint 4");
+ });
+
+ // This triggers the middleware to be created and the matcher factory to be called
+ // with the datasource we want to test
+ var requestDelegate = app.Build();
+ requestDelegate(new DefaultHttpContext());
+ requestDelegate(new DefaultHttpContext() { Request = { Path = "/Foo", }, });
+
+ // Assert
+ Assert.Equal(2, matcherEndpointDataSources.Count);
+
+ // Each UseRouter has its own data source
+ Assert.Collection(matcherEndpointDataSources[1].Endpoints, // app.UseRouter
+ e => Assert.Equal("Test endpoint 1", e.DisplayName),
+ e => Assert.Equal("Test endpoint 2", e.DisplayName));
+
+ Assert.Collection(matcherEndpointDataSources[0].Endpoints, // b.UseRouter
+ e => Assert.Equal("Test endpoint 3", e.DisplayName),
+ e => Assert.Equal("Test endpoint 4", e.DisplayName));
+
+ var compositeEndpointBuilder = services.GetRequiredService<EndpointDataSource>();
+
+ // Global middleware has all endpoints
+ Assert.Collection(compositeEndpointBuilder.Endpoints,
+ e => Assert.Equal("Test endpoint 1", e.DisplayName),
+ e => Assert.Equal("Test endpoint 2", e.DisplayName),
+ e => Assert.Equal("Test endpoint 3", e.DisplayName),
+ e => Assert.Equal("Test endpoint 4", e.DisplayName));
+ }
- // Each UseRouter has its own data source
- Assert.Collection(matcherEndpointDataSources[1].Endpoints, // app.UseRouter
- e => Assert.Equal("Test endpoint 1", e.DisplayName),
- e => Assert.Equal("Test endpoint 2", e.DisplayName));
+ [Fact]
+ public void UseEndpoints_WithGlobalEndpointRouteBuilderHasRoutes()
+ {
+ // Arrange
+ var services = CreateServices();
- Assert.Collection(matcherEndpointDataSources[0].Endpoints, // b.UseRouter
- e => Assert.Equal("Test endpoint 3", e.DisplayName),
- e => Assert.Equal("Test endpoint 4", e.DisplayName));
+ var app = new ApplicationBuilder(services);
- var compositeEndpointBuilder = services.GetRequiredService<EndpointDataSource>();
+ var mockRouteBuilder = new Mock<IEndpointRouteBuilder>();
+ mockRouteBuilder.Setup(m => m.DataSources).Returns(new List<EndpointDataSource>());
- // Global middleware has all endpoints
- Assert.Collection(compositeEndpointBuilder.Endpoints,
- e => Assert.Equal("Test endpoint 1", e.DisplayName),
- e => Assert.Equal("Test endpoint 2", e.DisplayName),
- e => Assert.Equal("Test endpoint 3", e.DisplayName),
- e => Assert.Equal("Test endpoint 4", e.DisplayName));
- }
+ var routeBuilder = mockRouteBuilder.Object;
+ app.Properties.Add("__GlobalEndpointRouteBuilder", routeBuilder);
+ app.UseRouting();
- [Fact]
- public void UseEndpoints_WithGlobalEndpointRouteBuilderHasRoutes()
+ app.UseEndpoints(endpoints =>
{
- // Arrange
- var services = CreateServices();
+ endpoints.Map("/1", d => Task.CompletedTask).WithDisplayName("Test endpoint 1");
+ });
- var app = new ApplicationBuilder(services);
+ var requestDelegate = app.Build();
- var mockRouteBuilder = new Mock<IEndpointRouteBuilder>();
- mockRouteBuilder.Setup(m => m.DataSources).Returns(new List<EndpointDataSource>());
+ var endpointDataSource = Assert.Single(mockRouteBuilder.Object.DataSources);
+ Assert.Collection(endpointDataSource.Endpoints,
+ e => Assert.Equal("Test endpoint 1", e.DisplayName));
- var routeBuilder = mockRouteBuilder.Object;
- app.Properties.Add("__GlobalEndpointRouteBuilder", routeBuilder);
- app.UseRouting();
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.Map("/1", d => Task.CompletedTask).WithDisplayName("Test endpoint 1");
- });
-
- var requestDelegate = app.Build();
+ var routeOptions = app.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
+ Assert.Equal(mockRouteBuilder.Object.DataSources, routeOptions.Value.EndpointDataSources);
+ }
- var endpointDataSource = Assert.Single(mockRouteBuilder.Object.DataSources);
- Assert.Collection(endpointDataSource.Endpoints,
- e => Assert.Equal("Test endpoint 1", e.DisplayName));
+ [Fact]
+ public void UseRouting_SetsEndpointRouteBuilder_IfGlobalOneExists()
+ {
+ // Arrange
+ var services = CreateServices();
- var routeOptions = app.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
- Assert.Equal(mockRouteBuilder.Object.DataSources, routeOptions.Value.EndpointDataSources);
- }
+ var app = new ApplicationBuilder(services);
- [Fact]
- public void UseRouting_SetsEndpointRouteBuilder_IfGlobalOneExists()
- {
- // Arrange
- var services = CreateServices();
+ var routeBuilder = new Mock<IEndpointRouteBuilder>().Object;
+ app.Properties.Add("__GlobalEndpointRouteBuilder", routeBuilder);
+ app.UseRouting();
- var app = new ApplicationBuilder(services);
+ Assert.True(app.Properties.TryGetValue("__EndpointRouteBuilder", out var local));
+ Assert.True(app.Properties.TryGetValue("__GlobalEndpointRouteBuilder", out var global));
+ Assert.Same(local, global);
+ }
- var routeBuilder = new Mock<IEndpointRouteBuilder>().Object;
- app.Properties.Add("__GlobalEndpointRouteBuilder", routeBuilder);
- app.UseRouting();
+ private IServiceProvider CreateServices()
+ {
+ return CreateServices(matcherFactory: null);
+ }
- Assert.True(app.Properties.TryGetValue("__EndpointRouteBuilder", out var local));
- Assert.True(app.Properties.TryGetValue("__GlobalEndpointRouteBuilder", out var global));
- Assert.Same(local, global);
- }
+ private IServiceProvider CreateServices(MatcherFactory matcherFactory)
+ {
+ var services = new ServiceCollection();
- private IServiceProvider CreateServices()
+ if (matcherFactory != null)
{
- return CreateServices(matcherFactory: null);
+ services.AddSingleton<MatcherFactory>(matcherFactory);
}
- private IServiceProvider CreateServices(MatcherFactory matcherFactory)
- {
- var services = new ServiceCollection();
+ services.AddLogging();
+ services.AddOptions();
+ services.AddRouting();
+ var listener = new DiagnosticListener("Microsoft.AspNetCore");
+ services.AddSingleton(listener);
+ services.AddSingleton<DiagnosticSource>(listener);
- if (matcherFactory != null)
- {
- services.AddSingleton<MatcherFactory>(matcherFactory);
- }
-
- services.AddLogging();
- services.AddOptions();
- services.AddRouting();
- var listener = new DiagnosticListener("Microsoft.AspNetCore");
- services.AddSingleton(listener);
- services.AddSingleton<DiagnosticSource>(listener);
+ var serviceProvder = services.BuildServiceProvider();
- var serviceProvder = services.BuildServiceProvider();
-
- return serviceProvder;
- }
+ return serviceProvder;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Builder/RequestDelegateEndpointRouteBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/RequestDelegateEndpointRouteBuilderExtensionsTest.cs
index f9079850ea..40aae08767 100644
--- a/src/Http/Routing/test/UnitTests/Builder/RequestDelegateEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/RequestDelegateEndpointRouteBuilderExtensionsTest.cs
@@ -12,215 +12,214 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public class RequestDelegateEndpointRouteBuilderExtensionsTest
{
- public class RequestDelegateEndpointRouteBuilderExtensionsTest
+ private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
{
- private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
- {
- return Assert.IsType<ModelEndpointDataSource>(Assert.Single(endpointRouteBuilder.DataSources));
- }
+ return Assert.IsType<ModelEndpointDataSource>(Assert.Single(endpointRouteBuilder.DataSources));
+ }
- private RouteEndpointBuilder GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
- {
- return Assert.IsType<RouteEndpointBuilder>(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointBuilders));
- }
+ private RouteEndpointBuilder GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
+ {
+ return Assert.IsType<RouteEndpointBuilder>(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointBuilders));
+ }
- public static object[][] MapMethods
+ public static object[][] MapMethods
+ {
+ get
{
- get
- {
- IEndpointConventionBuilder MapGet(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
- routes.MapGet(template, action);
+ IEndpointConventionBuilder MapGet(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
+ routes.MapGet(template, action);
- IEndpointConventionBuilder MapPost(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
- routes.MapPost(template, action);
+ IEndpointConventionBuilder MapPost(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
+ routes.MapPost(template, action);
- IEndpointConventionBuilder MapPut(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
- routes.MapPut(template, action);
+ IEndpointConventionBuilder MapPut(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
+ routes.MapPut(template, action);
- IEndpointConventionBuilder MapDelete(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
- routes.MapDelete(template, action);
+ IEndpointConventionBuilder MapDelete(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
+ routes.MapDelete(template, action);
- IEndpointConventionBuilder Map(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
- routes.Map(template, action);
+ IEndpointConventionBuilder Map(IEndpointRouteBuilder routes, string template, RequestDelegate action) =>
+ routes.Map(template, action);
- return new object[][]
- {
+ return new object[][]
+ {
new object[] { (Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder>)MapGet },
new object[] { (Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder>)MapPost },
new object[] { (Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder>)MapPut },
new object[] { (Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder>)MapDelete },
new object[] { (Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder>)Map },
- };
- }
+ };
}
+ }
- [Fact]
- public void MapEndpoint_StringPattern_BuildsEndpoint()
- {
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
- RequestDelegate requestDelegate = (d) => null;
+ [Fact]
+ public void MapEndpoint_StringPattern_BuildsEndpoint()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+ RequestDelegate requestDelegate = (d) => null;
- // Act
- var endpointBuilder = builder.Map("/", requestDelegate);
+ // Act
+ var endpointBuilder = builder.Map("/", requestDelegate);
- // Assert
- var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
- Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
- Assert.Equal("/", endpointBuilder1.DisplayName);
- Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
- }
+ Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
+ Assert.Equal("/", endpointBuilder1.DisplayName);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ }
- [Fact]
- public void MapEndpoint_TypedPattern_BuildsEndpoint()
- {
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
- RequestDelegate requestDelegate = (d) => null;
+ [Fact]
+ public void MapEndpoint_TypedPattern_BuildsEndpoint()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+ RequestDelegate requestDelegate = (d) => null;
- // Act
- var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), requestDelegate);
+ // Act
+ var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), requestDelegate);
- // Assert
- var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
- Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
- Assert.Equal("/", endpointBuilder1.DisplayName);
- Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
- }
+ Assert.Equal(requestDelegate, endpointBuilder1.RequestDelegate);
+ Assert.Equal("/", endpointBuilder1.DisplayName);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ }
- [Fact]
- public void MapEndpoint_AttributesCollectedAsMetadata()
- {
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
-
- // Act
- var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), Handle);
-
- // Assert
- var endpointBuilder1 = GetRouteEndpointBuilder(builder);
- Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
- Assert.Equal(2, endpointBuilder1.Metadata.Count);
- Assert.IsType<Attribute1>(endpointBuilder1.Metadata[0]);
- Assert.IsType<Attribute2>(endpointBuilder1.Metadata[1]);
- }
+ [Fact]
+ public void MapEndpoint_AttributesCollectedAsMetadata()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+
+ // Act
+ var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), Handle);
+
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ Assert.Equal(2, endpointBuilder1.Metadata.Count);
+ Assert.IsType<Attribute1>(endpointBuilder1.Metadata[0]);
+ Assert.IsType<Attribute2>(endpointBuilder1.Metadata[1]);
+ }
- [Fact]
- public void MapEndpoint_GeneratedDelegateWorks()
- {
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+ [Fact]
+ public void MapEndpoint_GeneratedDelegateWorks()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
- Expression<RequestDelegate> handler = context => Task.CompletedTask;
+ Expression<RequestDelegate> handler = context => Task.CompletedTask;
- // Act
- var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), handler.Compile());
+ // Act
+ var endpointBuilder = builder.Map(RoutePatternFactory.Parse("/"), handler.Compile());
- // Assert
- var endpointBuilder1 = GetRouteEndpointBuilder(builder);
- Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
- }
+ // Assert
+ var endpointBuilder1 = GetRouteEndpointBuilder(builder);
+ Assert.Equal("/", endpointBuilder1.RoutePattern.RawText);
+ }
- [Fact]
- public void MapEndpoint_PrecedenceOfMetadata_BuilderMetadataReturned()
- {
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+ [Fact]
+ public void MapEndpoint_PrecedenceOfMetadata_BuilderMetadataReturned()
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
- // Act
- var endpointBuilder = builder.MapMethods("/", new[] { "METHOD" }, HandleHttpMetdata);
- endpointBuilder.WithMetadata(new HttpMethodMetadata(new[] { "BUILDER" }));
+ // Act
+ var endpointBuilder = builder.MapMethods("/", new[] { "METHOD" }, HandleHttpMetdata);
+ endpointBuilder.WithMetadata(new HttpMethodMetadata(new[] { "BUILDER" }));
- // Assert
- var dataSource = Assert.Single(builder.DataSources);
- var endpoint = Assert.Single(dataSource.Endpoints);
+ // Assert
+ var dataSource = Assert.Single(builder.DataSources);
+ var endpoint = Assert.Single(dataSource.Endpoints);
- Assert.Equal(3, endpoint.Metadata.Count);
- Assert.Equal("ATTRIBUTE", GetMethod(endpoint.Metadata[0]));
- Assert.Equal("METHOD", GetMethod(endpoint.Metadata[1]));
- Assert.Equal("BUILDER", GetMethod(endpoint.Metadata[2]));
+ Assert.Equal(3, endpoint.Metadata.Count);
+ Assert.Equal("ATTRIBUTE", GetMethod(endpoint.Metadata[0]));
+ Assert.Equal("METHOD", GetMethod(endpoint.Metadata[1]));
+ Assert.Equal("BUILDER", GetMethod(endpoint.Metadata[2]));
- Assert.Equal("BUILDER", endpoint.Metadata.GetMetadata<IHttpMethodMetadata>().HttpMethods.Single());
+ Assert.Equal("BUILDER", endpoint.Metadata.GetMetadata<IHttpMethodMetadata>().HttpMethods.Single());
- string GetMethod(object metadata)
- {
- var httpMethodMetadata = Assert.IsAssignableFrom<IHttpMethodMetadata>(metadata);
- return Assert.Single(httpMethodMetadata.HttpMethods);
- }
- }
-
- [Theory]
- [MemberData(nameof(MapMethods))]
- public void Map_EndpointMetadataNotDuplicated(Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder> map)
+ string GetMethod(object metadata)
{
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+ var httpMethodMetadata = Assert.IsAssignableFrom<IHttpMethodMetadata>(metadata);
+ return Assert.Single(httpMethodMetadata.HttpMethods);
+ }
+ }
- // Act
- var endpointBuilder = map(builder, "/", context => Task.CompletedTask).WithMetadata(new EndpointNameMetadata("MapMe"));
+ [Theory]
+ [MemberData(nameof(MapMethods))]
+ public void Map_EndpointMetadataNotDuplicated(Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder> map)
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
- // Assert
- var ds = GetBuilderEndpointDataSource(builder);
+ // Act
+ var endpointBuilder = map(builder, "/", context => Task.CompletedTask).WithMetadata(new EndpointNameMetadata("MapMe"));
- _ = ds.Endpoints;
- _ = ds.Endpoints;
- _ = ds.Endpoints;
+ // Assert
+ var ds = GetBuilderEndpointDataSource(builder);
- Assert.Single(ds.Endpoints);
- var endpoint = ds.Endpoints.Single();
+ _ = ds.Endpoints;
+ _ = ds.Endpoints;
+ _ = ds.Endpoints;
- Assert.Single(endpoint.Metadata.GetOrderedMetadata<IEndpointNameMetadata>());
- }
+ Assert.Single(ds.Endpoints);
+ var endpoint = ds.Endpoints.Single();
- [Theory]
- [MemberData(nameof(MapMethods))]
- public void AddingMetadataAfterBuildingEndpointThrows(Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder> map)
- {
- // Arrange
- var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
+ Assert.Single(endpoint.Metadata.GetOrderedMetadata<IEndpointNameMetadata>());
+ }
- // Act
- var endpointBuilder = map(builder, "/", context => Task.CompletedTask).WithMetadata(new EndpointNameMetadata("MapMe"));
+ [Theory]
+ [MemberData(nameof(MapMethods))]
+ public void AddingMetadataAfterBuildingEndpointThrows(Func<IEndpointRouteBuilder, string, RequestDelegate, IEndpointConventionBuilder> map)
+ {
+ // Arrange
+ var builder = new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>());
- // Assert
- var ds = GetBuilderEndpointDataSource(builder);
+ // Act
+ var endpointBuilder = map(builder, "/", context => Task.CompletedTask).WithMetadata(new EndpointNameMetadata("MapMe"));
- var endpoint = Assert.Single(ds.Endpoints);
+ // Assert
+ var ds = GetBuilderEndpointDataSource(builder);
- Assert.Single(endpoint.Metadata.GetOrderedMetadata<IEndpointNameMetadata>());
+ var endpoint = Assert.Single(ds.Endpoints);
- Assert.Throws<InvalidOperationException>(() => endpointBuilder.WithMetadata(new RouteNameMetadata("Foo")));
- }
+ Assert.Single(endpoint.Metadata.GetOrderedMetadata<IEndpointNameMetadata>());
- [Attribute1]
- [Attribute2]
- private static Task Handle(HttpContext context) => Task.CompletedTask;
+ Assert.Throws<InvalidOperationException>(() => endpointBuilder.WithMetadata(new RouteNameMetadata("Foo")));
+ }
- [HttpMethod("ATTRIBUTE")]
- private static Task HandleHttpMetdata(HttpContext context) => Task.CompletedTask;
+ [Attribute1]
+ [Attribute2]
+ private static Task Handle(HttpContext context) => Task.CompletedTask;
- private class HttpMethodAttribute : Attribute, IHttpMethodMetadata
- {
- public bool AcceptCorsPreflight => false;
+ [HttpMethod("ATTRIBUTE")]
+ private static Task HandleHttpMetdata(HttpContext context) => Task.CompletedTask;
- public IReadOnlyList<string> HttpMethods { get; }
+ private class HttpMethodAttribute : Attribute, IHttpMethodMetadata
+ {
+ public bool AcceptCorsPreflight => false;
- public HttpMethodAttribute(params string[] httpMethods)
- {
- HttpMethods = httpMethods;
- }
- }
+ public IReadOnlyList<string> HttpMethods { get; }
- private class Attribute1 : Attribute
+ public HttpMethodAttribute(params string[] httpMethods)
{
+ HttpMethods = httpMethods;
}
+ }
- private class Attribute2 : Attribute
- {
- }
+ private class Attribute1 : Attribute
+ {
+ }
+
+ private class Attribute2 : Attribute
+ {
}
}
diff --git a/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs
index e485e0b3fd..e942596cba 100644
--- a/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/RouteHandlerEndpointRouteBuilderExtensionsTest.cs
@@ -10,224 +10,224 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public class RouteHandlerEndpointRouteBuilderExtensionsTest
{
- public class RouteHandlerEndpointRouteBuilderExtensionsTest
+ private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
{
- private ModelEndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder)
- {
- return Assert.IsType<ModelEndpointDataSource>(Assert.Single(endpointRouteBuilder.DataSources));
- }
+ return Assert.IsType<ModelEndpointDataSource>(Assert.Single(endpointRouteBuilder.DataSources));
+ }
- private RouteEndpointBuilder GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
- {
- return Assert.IsType<RouteEndpointBuilder>(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointBuilders));
- }
+ private RouteEndpointBuilder GetRouteEndpointBuilder(IEndpointRouteBuilder endpointRouteBuilder)
+ {
+ return Assert.IsType<RouteEndpointBuilder>(Assert.Single(GetBuilderEndpointDataSource(endpointRouteBuilder).EndpointBuilders));
+ }
- public static object?[]?[] MapMethods
+ public static object?[]?[] MapMethods
+ {
+ get
{
- get
- {
- IEndpointConventionBuilder MapGet(IEndpointRouteBuilder routes, string template, Delegate action) =>
- routes.MapGet(template, action);
+ IEndpointConventionBuilder MapGet(IEndpointRouteBuilder routes, string template, Delegate action) =>
+ routes.MapGet(template, action);
- IEndpointConventionBuilder MapPost(IEndpointRouteBuilder routes, string template, Delegate action) =>
- routes.MapPost(template, action);
+ IEndpointConventionBuilder MapPost(IEndpointRouteBuilder routes, string template, Delegate action) =>
+ routes.MapPost(template, action);
- IEndpointConventionBuilder MapPut(IEndpointRouteBuilder routes, string template, Delegate action) =>
- routes.MapPut(template, action);
+ IEndpointConventionBuilder MapPut(IEndpointRouteBuilder routes, string template, Delegate action) =>
+ routes.MapPut(template, action);
- IEndpointConventionBuilder MapDelete(IEndpointRouteBuilder routes, string template, Delegate action) =>
- routes.MapDelete(template, action);
+ IEndpointConventionBuilder MapDelete(IEndpointRouteBuilder routes, string template, Delegate action) =>
+ routes.MapDelete(template, action);
- IEndpointConventionBuilder MapPatch(IEndpointRouteBuilder routes, string template, Delegate action) =>
- routes.MapPatch(template, action);
+ IEndpointConventionBuilder MapPatch(IEndpointRouteBuilder routes, string template, Delegate action) =>
+ routes.MapPatch(template, action);
- IEndpointConventionBuilder Map(IEndpointRouteBuilder routes, string template, Delegate action) =>
- routes.Map(template, action);
+ IEndpointConventionBuilder Map(IEndpointRouteBuilder routes, string template, Delegate action) =>
+ routes.Map(template, action);
- return new object?[]?[]
- {
+ return new object?[]?[]
+ {
new object?[] { (Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder>)MapGet, "GET" },
new object?[] { (Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder>)MapPost, "POST" },
new object?[] { (Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder>)MapPut, "PUT" },
new object?[] { (Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder>)MapDelete, "DELETE" },
new object?[] { (Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder>)MapPatch, "PATCH" },
new object?[] { (Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder>)Map, null },
- };
- }
+ };
}
+ }
- [Fact]
- public void MapEndpoint_PrecedenceOfMetadata_BuilderMetadataReturned()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ [Fact]
+ public void MapEndpoint_PrecedenceOfMetadata_BuilderMetadataReturned()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- [HttpMethod("ATTRIBUTE")]
- void TestAction()
- {
- }
+ [HttpMethod("ATTRIBUTE")]
+ void TestAction()
+ {
+ }
- var endpointBuilder = builder.MapMethods("/", new[] { "METHOD" }, (Action)TestAction);
- endpointBuilder.WithMetadata(new HttpMethodMetadata(new[] { "BUILDER" }));
+ var endpointBuilder = builder.MapMethods("/", new[] { "METHOD" }, (Action)TestAction);
+ endpointBuilder.WithMetadata(new HttpMethodMetadata(new[] { "BUILDER" }));
- var dataSource = Assert.Single(builder.DataSources);
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = Assert.Single(builder.DataSources);
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var metadataArray = endpoint.Metadata.OfType<IHttpMethodMetadata>().ToArray();
+ var metadataArray = endpoint.Metadata.OfType<IHttpMethodMetadata>().ToArray();
- static string GetMethod(IHttpMethodMetadata metadata) => Assert.Single(metadata.HttpMethods);
+ static string GetMethod(IHttpMethodMetadata metadata) => Assert.Single(metadata.HttpMethods);
- Assert.Equal(3, metadataArray.Length);
- Assert.Equal("ATTRIBUTE", GetMethod(metadataArray[0]));
- Assert.Equal("METHOD", GetMethod(metadataArray[1]));
- Assert.Equal("BUILDER", GetMethod(metadataArray[2]));
+ Assert.Equal(3, metadataArray.Length);
+ Assert.Equal("ATTRIBUTE", GetMethod(metadataArray[0]));
+ Assert.Equal("METHOD", GetMethod(metadataArray[1]));
+ Assert.Equal("BUILDER", GetMethod(metadataArray[2]));
- Assert.Equal("BUILDER", endpoint.Metadata.GetMetadata<IHttpMethodMetadata>()!.HttpMethods.Single());
- }
+ Assert.Equal("BUILDER", endpoint.Metadata.GetMetadata<IHttpMethodMetadata>()!.HttpMethods.Single());
+ }
- [Fact]
- public void MapGet_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapGet("/", () => { });
+ [Fact]
+ public void MapGet_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapGet("/", () => { });
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("GET", method);
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("GET", method);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- [Fact]
- public void MapPatch_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapPatch("/", () => { });
+ [Fact]
+ public void MapPatch_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapPatch("/", () => { });
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("PATCH", method);
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("PATCH", method);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- [Fact]
- public async Task MapGetWithRouteParameter_BuildsEndpointWithRouteSpecificBinding()
+ [Fact]
+ public async Task MapGetWithRouteParameter_BuildsEndpointWithRouteSpecificBinding()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapGet("/{id}", (int? id, HttpContext httpContext) =>
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapGet("/{id}", (int? id, HttpContext httpContext) =>
+ if (id is not null)
{
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- });
+ httpContext.Items["input"] = id;
+ }
+ });
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("GET", method);
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("GET", method);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: GET /{id}", routeEndpointBuilder.DisplayName);
- Assert.Equal("/{id}", routeEndpointBuilder.RoutePattern.RawText);
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: GET /{id}", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/{id}", routeEndpointBuilder.RoutePattern.RawText);
- // Assert that we don't fallback to the query string
- var httpContext = new DefaultHttpContext();
+ // Assert that we don't fallback to the query string
+ var httpContext = new DefaultHttpContext();
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["id"] = "42"
- });
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["id"] = "42"
+ });
- await endpoint.RequestDelegate!(httpContext);
+ await endpoint.RequestDelegate!(httpContext);
- Assert.Null(httpContext.Items["input"]);
- }
+ Assert.Null(httpContext.Items["input"]);
+ }
- [Fact]
- public async Task MapGetWithoutRouteParameter_BuildsEndpointWithQuerySpecificBinding()
+ [Fact]
+ public async Task MapGetWithoutRouteParameter_BuildsEndpointWithQuerySpecificBinding()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapGet("/", (int? id, HttpContext httpContext) =>
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapGet("/", (int? id, HttpContext httpContext) =>
+ if (id is not null)
{
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- });
+ httpContext.Items["input"] = id;
+ }
+ });
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("GET", method);
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("GET", method);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- // Assert that we don't fallback to the route values
- var httpContext = new DefaultHttpContext();
+ // Assert that we don't fallback to the route values
+ var httpContext = new DefaultHttpContext();
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>()
- {
- ["id"] = "41"
- });
- httpContext.Request.RouteValues = new();
- httpContext.Request.RouteValues["id"] = "42";
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>()
+ {
+ ["id"] = "41"
+ });
+ httpContext.Request.RouteValues = new();
+ httpContext.Request.RouteValues["id"] = "42";
- await endpoint.RequestDelegate!(httpContext);
+ await endpoint.RequestDelegate!(httpContext);
- Assert.Equal(41, httpContext.Items["input"]);
- }
+ Assert.Equal(41, httpContext.Items["input"]);
+ }
- [Fact]
- public void MapGet_ThrowsWithImplicitFromBody()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var ex = Assert.Throws<InvalidOperationException>(() => builder.MapGet("/", (Todo todo) => { }));
- Assert.Contains("Body was inferred but the method does not allow inferred body parameters.", ex.Message);
- Assert.Contains("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?", ex.Message);
- }
+ [Fact]
+ public void MapGet_ThrowsWithImplicitFromBody()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ var ex = Assert.Throws<InvalidOperationException>(() => builder.MapGet("/", (Todo todo) => { }));
+ Assert.Contains("Body was inferred but the method does not allow inferred body parameters.", ex.Message);
+ Assert.Contains("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?", ex.Message);
+ }
- [Fact]
- public void MapDelete_ThrowsWithImplicitFromBody()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var ex = Assert.Throws<InvalidOperationException>(() => builder.MapDelete("/", (Todo todo) => { }));
- Assert.Contains("Body was inferred but the method does not allow inferred body parameters.", ex.Message);
- Assert.Contains("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?", ex.Message);
- }
+ [Fact]
+ public void MapDelete_ThrowsWithImplicitFromBody()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ var ex = Assert.Throws<InvalidOperationException>(() => builder.MapDelete("/", (Todo todo) => { }));
+ Assert.Contains("Body was inferred but the method does not allow inferred body parameters.", ex.Message);
+ Assert.Contains("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?", ex.Message);
+ }
- public static object[][] NonImplicitFromBodyMethods
+ public static object[][] NonImplicitFromBodyMethods
+ {
+ get
{
- get
+ return new[]
{
- return new[]
- {
new[] { HttpMethods.Delete },
new[] { HttpMethods.Connect },
new[] { HttpMethods.Trace },
@@ -235,699 +235,698 @@ namespace Microsoft.AspNetCore.Builder
new[] { HttpMethods.Head },
new[] { HttpMethods.Options },
};
- }
- }
-
- [Theory]
- [MemberData(nameof(NonImplicitFromBodyMethods))]
- public void MapVerb_ThrowsWithImplicitFromBody(string method)
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var ex = Assert.Throws<InvalidOperationException>(() => builder.MapMethods("/", new[] { method }, (Todo todo) => { }));
- Assert.Contains("Body was inferred but the method does not allow inferred body parameters.", ex.Message);
- Assert.Contains("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?", ex.Message);
}
+ }
- [Fact]
- public void MapGet_ImplicitFromService()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
- _ = builder.MapGet("/", (TodoService todo) => { });
+ [Theory]
+ [MemberData(nameof(NonImplicitFromBodyMethods))]
+ public void MapVerb_ThrowsWithImplicitFromBody(string method)
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ var ex = Assert.Throws<InvalidOperationException>(() => builder.MapMethods("/", new[] { method }, (Todo todo) => { }));
+ Assert.Contains("Body was inferred but the method does not allow inferred body parameters.", ex.Message);
+ Assert.Contains("Did you mean to register the \"Body (Inferred)\" parameter(s) as a Service or apply the [FromService] or [FromBody] attribute?", ex.Message);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapGet_ImplicitFromService()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
+ _ = builder.MapGet("/", (TodoService todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("GET", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("GET", method);
- [Fact]
- public void MapDelete_ImplicitFromService()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
- _ = builder.MapDelete("/", (TodoService todo) => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapDelete_ImplicitFromService()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
+ _ = builder.MapDelete("/", (TodoService todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("DELETE", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("DELETE", method);
- [Fact]
- public void MapPatch_ImplicitFromService()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
- _ = builder.MapPatch("/", (TodoService todo) => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapPatch_ImplicitFromService()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
+ _ = builder.MapPatch("/", (TodoService todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("PATCH", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("PATCH", method);
- [AttributeUsage(AttributeTargets.Parameter)]
- private class TestFromServiceAttribute : Attribute, IFromServiceMetadata
- { }
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- [Fact]
- public void MapGet_ExplicitFromService()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
- _ = builder.MapGet("/", ([TestFromServiceAttribute] TodoService todo) => { });
+ [AttributeUsage(AttributeTargets.Parameter)]
+ private class TestFromServiceAttribute : Attribute, IFromServiceMetadata
+ { }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapGet_ExplicitFromService()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
+ _ = builder.MapGet("/", ([TestFromServiceAttribute] TodoService todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("GET", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("GET", method);
- [Fact]
- public void MapDelete_ExplicitFromService()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
- _ = builder.MapDelete("/", ([TestFromServiceAttribute] TodoService todo) => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapDelete_ExplicitFromService()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
+ _ = builder.MapDelete("/", ([TestFromServiceAttribute] TodoService todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("DELETE", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("DELETE", method);
- [Fact]
- public void MapPatch_ExplicitFromService()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
- _ = builder.MapPatch("/", ([TestFromServiceAttribute] TodoService todo) => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapPatch_ExplicitFromService()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().AddSingleton<TodoService>().BuildServiceProvider()));
+ _ = builder.MapPatch("/", ([TestFromServiceAttribute] TodoService todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("PATCH", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("PATCH", method);
- [AttributeUsage(AttributeTargets.Parameter)]
- private class TestFromBodyAttribute : Attribute, IFromBodyMetadata
- { }
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- [Fact]
- public void MapGet_ExplicitFromBody_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapGet("/", ([TestFromBody] Todo todo) => { });
+ [AttributeUsage(AttributeTargets.Parameter)]
+ private class TestFromBodyAttribute : Attribute, IFromBodyMetadata
+ { }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapGet_ExplicitFromBody_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapGet("/", ([TestFromBody] Todo todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("GET", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("GET", method);
- [Fact]
- public void MapDelete_ExplicitFromBody_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapDelete("/", ([TestFromBody] Todo todo) => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: GET /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapDelete_ExplicitFromBody_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapDelete("/", ([TestFromBody] Todo todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("DELETE", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("DELETE", method);
- [Fact]
- public void MapPatch_ExplicitFromBody_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapPatch("/", ([TestFromBody] Todo todo) => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapPatch_ExplicitFromBody_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapPatch("/", ([TestFromBody] Todo todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("PATCH", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("PATCH", method);
- [Theory]
- [MemberData(nameof(MapMethods))]
- public void MapVerbDoesNotDuplicateMetadata(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: PATCH /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- map(builder, "/{ID}", () => { }).WithName("Foo");
+ [Theory]
+ [MemberData(nameof(MapMethods))]
+ public void MapVerbDoesNotDuplicateMetadata(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var dataSource = GetBuilderEndpointDataSource(builder);
+ map(builder, "/{ID}", () => { }).WithName("Foo");
- // Access endpoints a couple of times to make sure it gets built
- _ = dataSource.Endpoints;
- _ = dataSource.Endpoints;
- _ = dataSource.Endpoints;
+ var dataSource = GetBuilderEndpointDataSource(builder);
- var endpoint = Assert.Single(dataSource.Endpoints);
+ // Access endpoints a couple of times to make sure it gets built
+ _ = dataSource.Endpoints;
+ _ = dataSource.Endpoints;
+ _ = dataSource.Endpoints;
- var endpointNameMetadata = Assert.Single(endpoint.Metadata.GetOrderedMetadata<IEndpointNameMetadata>());
- var routeNameMetadata = Assert.Single(endpoint.Metadata.GetOrderedMetadata<IRouteNameMetadata>());
- Assert.Equal("Foo", endpointNameMetadata.EndpointName);
- Assert.Equal("Foo", routeNameMetadata.RouteName);
+ var endpoint = Assert.Single(dataSource.Endpoints);
- if (expectedMethod is not null)
- {
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal(expectedMethod, method);
- }
- }
+ var endpointNameMetadata = Assert.Single(endpoint.Metadata.GetOrderedMetadata<IEndpointNameMetadata>());
+ var routeNameMetadata = Assert.Single(endpoint.Metadata.GetOrderedMetadata<IRouteNameMetadata>());
+ Assert.Equal("Foo", endpointNameMetadata.EndpointName);
+ Assert.Equal("Foo", routeNameMetadata.RouteName);
- [Theory]
- [MemberData(nameof(MapMethods))]
- public void AddingMetadataAfterBuildingEndpointThrows(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ if (expectedMethod is not null)
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
-
- var endpointBuilder = map(builder, "/{ID}", () => { });
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal(expectedMethod, method);
+ }
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
+ [Theory]
+ [MemberData(nameof(MapMethods))]
+ public void AddingMetadataAfterBuildingEndpointThrows(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var endpointBuilder = map(builder, "/{ID}", () => { });
- if (expectedMethod is not null)
- {
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal(expectedMethod, method);
- }
+ var dataSource = GetBuilderEndpointDataSource(builder);
- Assert.Throws<InvalidOperationException>(() => endpointBuilder.WithMetadata(new RouteNameMetadata("Foo")));
- }
+ var endpoint = Assert.Single(dataSource.Endpoints);
- [Theory]
- [MemberData(nameof(MapMethods))]
- public async Task MapVerbWithExplicitRouteParameterIsCaseInsensitive(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ if (expectedMethod is not null)
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
-
- map(builder, "/{ID}", ([FromRoute] int? id, HttpContext httpContext) =>
- {
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- });
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal(expectedMethod, method);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ Assert.Throws<InvalidOperationException>(() => endpointBuilder.WithMetadata(new RouteNameMetadata("Foo")));
+ }
- if (expectedMethod is not null)
- {
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal(expectedMethod, method);
- }
+ [Theory]
+ [MemberData(nameof(MapMethods))]
+ public async Task MapVerbWithExplicitRouteParameterIsCaseInsensitive(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- if (expectedMethod is not null)
+ map(builder, "/{ID}", ([FromRoute] int? id, HttpContext httpContext) =>
+ {
+ if (id is not null)
{
- Assert.Equal($"HTTP: {expectedMethod} /{{ID}}", routeEndpointBuilder.DisplayName);
+ httpContext.Items["input"] = id;
}
- Assert.Equal($"/{{ID}}", routeEndpointBuilder.RoutePattern.RawText);
+ });
- var httpContext = new DefaultHttpContext();
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- httpContext.Request.RouteValues["id"] = "13";
-
- await endpoint.RequestDelegate!(httpContext);
-
- Assert.Equal(13, httpContext.Items["input"]);
+ if (expectedMethod is not null)
+ {
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal(expectedMethod, method);
}
- [Theory]
- [MemberData(nameof(MapMethods))]
- public async Task MapVerbWithRouteParameterDoesNotFallbackToQuery(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ if (expectedMethod is not null)
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ Assert.Equal($"HTTP: {expectedMethod} /{{ID}}", routeEndpointBuilder.DisplayName);
+ }
+ Assert.Equal($"/{{ID}}", routeEndpointBuilder.RoutePattern.RawText);
- map(builder, "/{ID}", (int? id, HttpContext httpContext) =>
- {
- if (id is not null)
- {
- httpContext.Items["input"] = id;
- }
- });
-
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
- if (expectedMethod is not null)
- {
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal(expectedMethod, method);
- }
+ var httpContext = new DefaultHttpContext();
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- if (expectedMethod is not null)
- {
- Assert.Equal($"HTTP: {expectedMethod} /{{ID}}", routeEndpointBuilder.DisplayName);
- }
- Assert.Equal($"/{{ID}}", routeEndpointBuilder.RoutePattern.RawText);
+ httpContext.Request.RouteValues["id"] = "13";
- // Assert that we don't fallback to the query string
- var httpContext = new DefaultHttpContext();
+ await endpoint.RequestDelegate!(httpContext);
- httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
- {
- ["id"] = "42"
- });
+ Assert.Equal(13, httpContext.Items["input"]);
+ }
- await endpoint.RequestDelegate!(httpContext);
+ [Theory]
+ [MemberData(nameof(MapMethods))]
+ public async Task MapVerbWithRouteParameterDoesNotFallbackToQuery(Func<IEndpointRouteBuilder, string, Delegate, IEndpointConventionBuilder> map, string expectedMethod)
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- Assert.Null(httpContext.Items["input"]);
- }
+ map(builder, "/{ID}", (int? id, HttpContext httpContext) =>
+ {
+ if (id is not null)
+ {
+ httpContext.Items["input"] = id;
+ }
+ });
- [Fact]
- public void MapGetWithRouteParameter_ThrowsIfRouteParameterDoesNotExist()
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+ if (expectedMethod is not null)
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var ex = Assert.Throws<InvalidOperationException>(() => builder.MapGet("/", ([FromRoute] int id) => { }));
- Assert.Equal("'id' is not a route parameter.", ex.Message);
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal(expectedMethod, method);
}
- [Fact]
- public async Task MapGetWithNamedFromRouteParameter_UsesFromRouteName()
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ if (expectedMethod is not null)
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapGet("/{value}", ([FromRoute(Name = "value")] int id, HttpContext httpContext) =>
- {
- httpContext.Items["value"] = id;
- });
+ Assert.Equal($"HTTP: {expectedMethod} /{{ID}}", routeEndpointBuilder.DisplayName);
+ }
+ Assert.Equal($"/{{ID}}", routeEndpointBuilder.RoutePattern.RawText);
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ // Assert that we don't fallback to the query string
+ var httpContext = new DefaultHttpContext();
- // Assert that we don't fallback to the query string
- var httpContext = new DefaultHttpContext();
+ httpContext.Request.Query = new QueryCollection(new Dictionary<string, StringValues>
+ {
+ ["id"] = "42"
+ });
- httpContext.Request.RouteValues["value"] = "42";
+ await endpoint.RequestDelegate!(httpContext);
- await endpoint.RequestDelegate!(httpContext);
+ Assert.Null(httpContext.Items["input"]);
+ }
- Assert.Equal(42, httpContext.Items["value"]);
- }
+ [Fact]
+ public void MapGetWithRouteParameter_ThrowsIfRouteParameterDoesNotExist()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ var ex = Assert.Throws<InvalidOperationException>(() => builder.MapGet("/", ([FromRoute] int id) => { }));
+ Assert.Equal("'id' is not a route parameter.", ex.Message);
+ }
- [Fact]
- public async Task MapGetWithNamedFromRouteParameter_FailsForParameterName()
+ [Fact]
+ public async Task MapGetWithNamedFromRouteParameter_UsesFromRouteName()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapGet("/{value}", ([FromRoute(Name = "value")] int id, HttpContext httpContext) =>
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapGet("/{value}", ([FromRoute(Name = "value")] int id, HttpContext httpContext) =>
- {
- httpContext.Items["value"] = id;
- });
+ httpContext.Items["value"] = id;
+ });
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- // Assert that we don't fallback to the query string
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider();
+ // Assert that we don't fallback to the query string
+ var httpContext = new DefaultHttpContext();
- httpContext.Request.RouteValues["id"] = "42";
+ httpContext.Request.RouteValues["value"] = "42";
- await endpoint.RequestDelegate!(httpContext);
+ await endpoint.RequestDelegate!(httpContext);
- Assert.Null(httpContext.Items["value"]);
- Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
- }
+ Assert.Equal(42, httpContext.Items["value"]);
+ }
- [Fact]
- public void MapGetWithNamedFromRouteParameter_ThrowsForMismatchedPattern()
+ [Fact]
+ public async Task MapGetWithNamedFromRouteParameter_FailsForParameterName()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapGet("/{value}", ([FromRoute(Name = "value")] int id, HttpContext httpContext) =>
{
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- var ex = Assert.Throws<InvalidOperationException>(() =>builder.MapGet("/{id}", ([FromRoute(Name = "value")] int id, HttpContext httpContext) => { }));
- Assert.Equal("'value' is not a route parameter.", ex.Message);
- }
+ httpContext.Items["value"] = id;
+ });
- [Fact]
- public void MapPost_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapPost("/", () => { });
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ // Assert that we don't fallback to the query string
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider();
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("POST", method);
+ httpContext.Request.RouteValues["id"] = "42";
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: POST /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ await endpoint.RequestDelegate!(httpContext);
- [Fact]
- public void MapPost_BuildsEndpointWithCorrectEndpointMetadata()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapPost("/", [TestConsumesAttribute(typeof(Todo), "application/xml")] (Todo todo) => { });
+ Assert.Null(httpContext.Items["value"]);
+ Assert.Equal(StatusCodes.Status400BadRequest, httpContext.Response.StatusCode);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapGetWithNamedFromRouteParameter_ThrowsForMismatchedPattern()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ var ex = Assert.Throws<InvalidOperationException>(() => builder.MapGet("/{id}", ([FromRoute(Name = "value")] int id, HttpContext httpContext) => { }));
+ Assert.Equal("'value' is not a route parameter.", ex.Message);
+ }
- var endpointMetadata = endpoint.Metadata.GetMetadata<IAcceptsMetadata>();
+ [Fact]
+ public void MapPost_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapPost("/", () => { });
- Assert.NotNull(endpointMetadata);
- Assert.False(endpointMetadata!.IsOptional);
- Assert.Equal(typeof(Todo), endpointMetadata.RequestType);
- Assert.Equal(new[] { "application/xml" }, endpointMetadata.ContentTypes);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- }
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("POST", method);
- [Fact]
- public void MapPut_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapPut("/", () => { });
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: POST /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void MapPost_BuildsEndpointWithCorrectEndpointMetadata()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapPost("/", [TestConsumesAttribute(typeof(Todo), "application/xml")] (Todo todo) => { });
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("PUT", method);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: PUT /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var endpointMetadata = endpoint.Metadata.GetMetadata<IAcceptsMetadata>();
- [Fact]
- public void MapDelete_BuildsEndpointWithCorrectMethod()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapDelete("/", () => { });
+ Assert.NotNull(endpointMetadata);
+ Assert.False(endpointMetadata!.IsOptional);
+ Assert.Equal(typeof(Todo), endpointMetadata.RequestType);
+ Assert.Equal(new[] { "application/xml" }, endpointMetadata.ContentTypes);
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ }
- var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
- Assert.NotNull(methodMetadata);
- var method = Assert.Single(methodMetadata!.HttpMethods);
- Assert.Equal("DELETE", method);
+ [Fact]
+ public void MapPut_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapPut("/", () => { });
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- }
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- [Fact]
- public void MapFallback_BuildsEndpointWithLowestRouteOrder()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapFallback("/", () => { });
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("PUT", method);
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: PUT /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("Fallback /", routeEndpointBuilder.DisplayName);
- Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
- Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
- }
+ [Fact]
+ public void MapDelete_BuildsEndpointWithCorrectMethod()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapDelete("/", () => { });
- [Fact]
- public void MapFallbackWithoutPath_BuildsEndpointWithLowestRouteOrder()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapFallback(() => { });
-
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
-
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Equal("Fallback {*path:nonfile}", routeEndpointBuilder.DisplayName);
- Assert.Equal("{*path:nonfile}", routeEndpointBuilder.RoutePattern.RawText);
- Assert.Single(routeEndpointBuilder.RoutePattern.Parameters);
- Assert.True(routeEndpointBuilder.RoutePattern.Parameters[0].IsCatchAll);
- Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
- }
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- [Fact]
- public void WithTags_CanSetTagsForEndpoint()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- string GetString() => "Foo";
- _ = builder.MapDelete("/", GetString).WithTags("Some", "Test", "Tags");
+ var methodMetadata = endpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
+ Assert.NotNull(methodMetadata);
+ var method = Assert.Single(methodMetadata!.HttpMethods);
+ Assert.Equal("DELETE", method);
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("HTTP: DELETE /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ }
- var tagsMetadata = endpoint.Metadata.GetMetadata<ITagsMetadata>();
- Assert.Equal(new[] { "Some", "Test", "Tags" }, tagsMetadata?.Tags);
- }
+ [Fact]
+ public void MapFallback_BuildsEndpointWithLowestRouteOrder()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapFallback("/", () => { });
- [Fact]
- public void MapMethod_DoesNotEndpointNameForMethodGroupByDefault()
- {
- string GetString() => "Foo";
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
- _ = builder.MapDelete("/", GetString);
-
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
-
- var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>();
- var routeName = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
- var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
- Assert.Null(endpointName);
- Assert.Null(routeName);
- Assert.Equal("HTTP: DELETE / => GetString", routeEndpointBuilder.DisplayName);
- }
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task MapMethod_FlowsThrowOnBadHttpRequest(bool throwOnBadRequest)
- {
- var serviceProvider = new EmptyServiceProvider();
- serviceProvider.RouteHandlerOptions.ThrowOnBadRequest = throwOnBadRequest;
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("Fallback /", routeEndpointBuilder.DisplayName);
+ Assert.Equal("/", routeEndpointBuilder.RoutePattern.RawText);
+ Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
+ }
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider));
- _ = builder.Map("/{id}", (int id) => { });
+ [Fact]
+ public void MapFallbackWithoutPath_BuildsEndpointWithLowestRouteOrder()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapFallback(() => { });
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Equal("Fallback {*path:nonfile}", routeEndpointBuilder.DisplayName);
+ Assert.Equal("{*path:nonfile}", routeEndpointBuilder.RoutePattern.RawText);
+ Assert.Single(routeEndpointBuilder.RoutePattern.Parameters);
+ Assert.True(routeEndpointBuilder.RoutePattern.Parameters[0].IsCatchAll);
+ Assert.Equal(int.MaxValue, routeEndpointBuilder.Order);
+ }
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ [Fact]
+ public void WithTags_CanSetTagsForEndpoint()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ string GetString() => "Foo";
+ _ = builder.MapDelete("/", GetString).WithTags("Some", "Test", "Tags");
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider();
- httpContext.Request.RouteValues["id"] = "invalid!";
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- if (throwOnBadRequest)
- {
- var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => endpoint.RequestDelegate!(httpContext));
- Assert.Equal(400, ex.StatusCode);
- }
- else
- {
- await endpoint.RequestDelegate!(httpContext);
- Assert.Equal(400, httpContext.Response.StatusCode);
- }
- }
+ var tagsMetadata = endpoint.Metadata.GetMetadata<ITagsMetadata>();
+ Assert.Equal(new[] { "Some", "Test", "Tags" }, tagsMetadata?.Tags);
+ }
- [Fact]
- public async Task MapMethod_DefaultsToNotThrowOnBadHttpRequestIfItCannotResolveRouteHandlerOptions()
- {
- var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().BuildServiceProvider()));
+ [Fact]
+ public void MapMethod_DoesNotEndpointNameForMethodGroupByDefault()
+ {
+ string GetString() => "Foo";
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new EmptyServiceProvider()));
+ _ = builder.MapDelete("/", GetString);
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>();
+ var routeName = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
+ var routeEndpointBuilder = GetRouteEndpointBuilder(builder);
+ Assert.Null(endpointName);
+ Assert.Null(routeName);
+ Assert.Equal("HTTP: DELETE / => GetString", routeEndpointBuilder.DisplayName);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task MapMethod_FlowsThrowOnBadHttpRequest(bool throwOnBadRequest)
+ {
+ var serviceProvider = new EmptyServiceProvider();
+ serviceProvider.RouteHandlerOptions.ThrowOnBadRequest = throwOnBadRequest;
- _ = builder.Map("/{id}", (int id) => { });
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(serviceProvider));
+ _ = builder.Map("/{id}", (int id) => { });
- var dataSource = GetBuilderEndpointDataSource(builder);
- // Trigger Endpoint build by calling getter.
- var endpoint = Assert.Single(dataSource.Endpoints);
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider();
- httpContext.Request.RouteValues["id"] = "invalid!";
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider();
+ httpContext.Request.RouteValues["id"] = "invalid!";
+ if (throwOnBadRequest)
+ {
+ var ex = await Assert.ThrowsAsync<BadHttpRequestException>(() => endpoint.RequestDelegate!(httpContext));
+ Assert.Equal(400, ex.StatusCode);
+ }
+ else
+ {
await endpoint.RequestDelegate!(httpContext);
Assert.Equal(400, httpContext.Response.StatusCode);
}
+ }
- class FromRoute : Attribute, IFromRouteMetadata
- {
- public string? Name { get; set; }
- }
+ [Fact]
+ public async Task MapMethod_DefaultsToNotThrowOnBadHttpRequestIfItCannotResolveRouteHandlerOptions()
+ {
+ var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(new ServiceCollection().BuildServiceProvider()));
+
+ _ = builder.Map("/{id}", (int id) => { });
+
+ var dataSource = GetBuilderEndpointDataSource(builder);
+ // Trigger Endpoint build by calling getter.
+ var endpoint = Assert.Single(dataSource.Endpoints);
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider();
+ httpContext.Request.RouteValues["id"] = "invalid!";
- class TestConsumesAttribute : Attribute, IAcceptsMetadata
+ await endpoint.RequestDelegate!(httpContext);
+ Assert.Equal(400, httpContext.Response.StatusCode);
+ }
+
+ class FromRoute : Attribute, IFromRouteMetadata
+ {
+ public string? Name { get; set; }
+ }
+
+ class TestConsumesAttribute : Attribute, IAcceptsMetadata
+ {
+ public TestConsumesAttribute(Type requestType, string contentType, params string[] otherContentTypes)
{
- public TestConsumesAttribute(Type requestType, string contentType, params string[] otherContentTypes)
+ if (contentType == null)
{
- if (contentType == null)
- {
- throw new ArgumentNullException(nameof(contentType));
- }
+ throw new ArgumentNullException(nameof(contentType));
+ }
- var contentTypes = new List<string>()
+ var contentTypes = new List<string>()
{
contentType
};
- for (var i = 0; i < otherContentTypes.Length; i++)
- {
- contentTypes.Add(otherContentTypes[i]);
- }
-
- _requestType = requestType;
- _contentTypes = contentTypes;
+ for (var i = 0; i < otherContentTypes.Length; i++)
+ {
+ contentTypes.Add(otherContentTypes[i]);
}
- IReadOnlyList<string> IAcceptsMetadata.ContentTypes => _contentTypes;
- Type? IAcceptsMetadata.RequestType => _requestType;
+ _requestType = requestType;
+ _contentTypes = contentTypes;
+ }
- bool IAcceptsMetadata.IsOptional => false;
+ IReadOnlyList<string> IAcceptsMetadata.ContentTypes => _contentTypes;
+ Type? IAcceptsMetadata.RequestType => _requestType;
- Type? _requestType;
+ bool IAcceptsMetadata.IsOptional => false;
- List<string> _contentTypes = new();
- }
+ Type? _requestType;
- class Todo
- {
+ List<string> _contentTypes = new();
+ }
- }
+ class Todo
+ {
- // Here to more easily disambiguate when ToDo is
- // intended to be validated as an implicit service in tests
- class TodoService
- {
+ }
- }
+ // Here to more easily disambiguate when ToDo is
+ // intended to be validated as an implicit service in tests
+ class TodoService
+ {
- private class HttpMethodAttribute : Attribute, IHttpMethodMetadata
- {
- public bool AcceptCorsPreflight => false;
+ }
- public IReadOnlyList<string> HttpMethods { get; }
+ private class HttpMethodAttribute : Attribute, IHttpMethodMetadata
+ {
+ public bool AcceptCorsPreflight => false;
- public HttpMethodAttribute(params string[] httpMethods)
- {
- HttpMethods = httpMethods;
- }
+ public IReadOnlyList<string> HttpMethods { get; }
+
+ public HttpMethodAttribute(params string[] httpMethods)
+ {
+ HttpMethods = httpMethods;
}
+ }
+
+ private class EmptyServiceProvider : IServiceScope, IServiceProvider, IServiceScopeFactory
+ {
+ public IServiceProvider ServiceProvider => this;
- private class EmptyServiceProvider : IServiceScope, IServiceProvider, IServiceScopeFactory
+ public RouteHandlerOptions RouteHandlerOptions { get; set; } = new RouteHandlerOptions();
+
+ public IServiceScope CreateScope()
{
- public IServiceProvider ServiceProvider => this;
+ return this;
+ }
- public RouteHandlerOptions RouteHandlerOptions { get; set; } = new RouteHandlerOptions();
+ public void Dispose()
+ {
+ }
- public IServiceScope CreateScope()
+ public object? GetService(Type serviceType)
+ {
+ if (serviceType == typeof(IServiceScopeFactory))
{
return this;
}
-
- public void Dispose()
+ else if (serviceType == typeof(IOptions<RouteHandlerOptions>))
{
+ return Options.Create(RouteHandlerOptions);
}
- public object? GetService(Type serviceType)
- {
- if (serviceType == typeof(IServiceScopeFactory))
- {
- return this;
- }
- else if (serviceType == typeof(IOptions<RouteHandlerOptions>))
- {
- return Options.Create(RouteHandlerOptions);
- }
-
- return null;
- }
+ return null;
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Builder/RoutingBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/RoutingBuilderExtensionsTest.cs
index 5d8299a442..e6f2b9673b 100644
--- a/src/Http/Routing/test/UnitTests/Builder/RoutingBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/RoutingBuilderExtensionsTest.cs
@@ -9,128 +9,127 @@ using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public class RoutingBuilderExtensionsTest
{
- public class RoutingBuilderExtensionsTest
+ [Fact]
+ public void UseRouter_ThrowsInvalidOperationException_IfRoutingMarkerServiceIsNotRegistered()
{
- [Fact]
- public void UseRouter_ThrowsInvalidOperationException_IfRoutingMarkerServiceIsNotRegistered()
- {
- // Arrange
- var applicationBuilderMock = new Mock<IApplicationBuilder>();
- applicationBuilderMock
- .Setup(s => s.ApplicationServices)
- .Returns(Mock.Of<IServiceProvider>());
-
- var router = Mock.Of<IRouter>();
-
- // Act & Assert
- var exception = Assert.Throws<InvalidOperationException>(
- () => applicationBuilderMock.Object.UseRouter(router));
-
- Assert.Equal(
- "Unable to find the required services. Please add all the required services by calling " +
- "'IServiceCollection.AddRouting' inside the call to 'ConfigureServices(...)'" +
- " in the application startup code.",
- exception.Message);
- }
-
- [Fact]
- public void UseRouter_IRouter_ThrowsWithoutCallingAddRouting()
- {
- // Arrange
- var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
-
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => app.UseRouter(Mock.Of<IRouter>()));
-
- // Assert
- Assert.Equal(
- "Unable to find the required services. " +
- "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
- "inside the call to 'ConfigureServices(...)' in the application startup code.",
- ex.Message);
- }
-
- [Fact]
- public void UseRouter_Action_ThrowsWithoutCallingAddRouting()
- {
- // Arrange
- var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
-
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => app.UseRouter(b => { }));
-
- // Assert
- Assert.Equal(
- "Unable to find the required services. " +
- "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
- "inside the call to 'ConfigureServices(...)' in the application startup code.",
- ex.Message);
- }
-
- [Fact]
- public async Task UseRouter_IRouter_CallsRoute()
- {
- // Arrange
- var services = CreateServices();
-
- var app = new ApplicationBuilder(services);
+ // Arrange
+ var applicationBuilderMock = new Mock<IApplicationBuilder>();
+ applicationBuilderMock
+ .Setup(s => s.ApplicationServices)
+ .Returns(Mock.Of<IServiceProvider>());
+
+ var router = Mock.Of<IRouter>();
+
+ // Act & Assert
+ var exception = Assert.Throws<InvalidOperationException>(
+ () => applicationBuilderMock.Object.UseRouter(router));
+
+ Assert.Equal(
+ "Unable to find the required services. Please add all the required services by calling " +
+ "'IServiceCollection.AddRouting' inside the call to 'ConfigureServices(...)'" +
+ " in the application startup code.",
+ exception.Message);
+ }
- var router = new Mock<IRouter>(MockBehavior.Strict);
- router
- .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
- .Returns(Task.CompletedTask)
- .Verifiable();
+ [Fact]
+ public void UseRouter_IRouter_ThrowsWithoutCallingAddRouting()
+ {
+ // Arrange
+ var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => app.UseRouter(Mock.Of<IRouter>()));
+
+ // Assert
+ Assert.Equal(
+ "Unable to find the required services. " +
+ "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
+ "inside the call to 'ConfigureServices(...)' in the application startup code.",
+ ex.Message);
+ }
- app.UseRouter(router.Object);
+ [Fact]
+ public void UseRouter_Action_ThrowsWithoutCallingAddRouting()
+ {
+ // Arrange
+ var app = new ApplicationBuilder(Mock.Of<IServiceProvider>());
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => app.UseRouter(b => { }));
+
+ // Assert
+ Assert.Equal(
+ "Unable to find the required services. " +
+ "Please add all the required services by calling 'IServiceCollection.AddRouting' " +
+ "inside the call to 'ConfigureServices(...)' in the application startup code.",
+ ex.Message);
+ }
- var appFunc = app.Build();
+ [Fact]
+ public async Task UseRouter_IRouter_CallsRoute()
+ {
+ // Arrange
+ var services = CreateServices();
- // Act
- await appFunc(new DefaultHttpContext());
+ var app = new ApplicationBuilder(services);
- // Assert
- router.Verify();
- }
+ var router = new Mock<IRouter>(MockBehavior.Strict);
+ router
+ .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
- [Fact]
- public async Task UseRouter_Action_CallsRoute()
- {
- // Arrange
- var services = CreateServices();
+ app.UseRouter(router.Object);
- var app = new ApplicationBuilder(services);
+ var appFunc = app.Build();
- var router = new Mock<IRouter>(MockBehavior.Strict);
- router
- .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
- .Returns(Task.CompletedTask)
- .Verifiable();
+ // Act
+ await appFunc(new DefaultHttpContext());
- app.UseRouter(b =>
- {
- b.Routes.Add(router.Object);
- });
+ // Assert
+ router.Verify();
+ }
- var appFunc = app.Build();
+ [Fact]
+ public async Task UseRouter_Action_CallsRoute()
+ {
+ // Arrange
+ var services = CreateServices();
- // Act
- await appFunc(new DefaultHttpContext());
+ var app = new ApplicationBuilder(services);
- // Assert
- router.Verify();
- }
+ var router = new Mock<IRouter>(MockBehavior.Strict);
+ router
+ .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
+ .Returns(Task.CompletedTask)
+ .Verifiable();
- private IServiceProvider CreateServices()
+ app.UseRouter(b =>
{
- var services = new ServiceCollection();
+ b.Routes.Add(router.Object);
+ });
+
+ var appFunc = app.Build();
+
+ // Act
+ await appFunc(new DefaultHttpContext());
+
+ // Assert
+ router.Verify();
+ }
+
+ private IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
- services.AddLogging();
- services.AddOptions();
- services.AddRouting();
+ services.AddLogging();
+ services.AddOptions();
+ services.AddRouting();
- return services.BuildServiceProvider();
- }
+ return services.BuildServiceProvider();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Builder/RoutingEndpointConventionBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/RoutingEndpointConventionBuilderExtensionsTest.cs
index 013e2a55ad..2082cc74ba 100644
--- a/src/Http/Routing/test/UnitTests/Builder/RoutingEndpointConventionBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/Builder/RoutingEndpointConventionBuilderExtensionsTest.cs
@@ -7,179 +7,178 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public class RoutingEndpointConventionBuilderExtensionsTest
{
- public class RoutingEndpointConventionBuilderExtensionsTest
+ [Fact]
+ public void RequireHost_AddsHostMetadata()
{
- [Fact]
- public void RequireHost_AddsHostMetadata()
- {
- // Arrange
- var builder = CreateBuilder();
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- builder.RequireHost("www.example.com", "example.com");
+ // Act
+ builder.RequireHost("www.example.com", "example.com");
- // Assert
- var endpoint = builder.Build();
+ // Assert
+ var endpoint = builder.Build();
- var metadata = endpoint.Metadata.GetMetadata<IHostMetadata>();
- Assert.NotNull(metadata);
- Assert.Equal(new[] { "www.example.com", "example.com" }, metadata.Hosts);
- }
+ var metadata = endpoint.Metadata.GetMetadata<IHostMetadata>();
+ Assert.NotNull(metadata);
+ Assert.Equal(new[] { "www.example.com", "example.com" }, metadata.Hosts);
+ }
- [Fact]
- public void RequireHost_ChainedCall_ReturnedBuilderIsDerivedType()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void RequireHost_ChainedCall_ReturnedBuilderIsDerivedType()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var chainedBuilder = builder.RequireHost("test");
+ // Act
+ var chainedBuilder = builder.RequireHost("test");
- // Assert
- Assert.True(chainedBuilder.TestProperty);
- }
+ // Assert
+ Assert.True(chainedBuilder.TestProperty);
+ }
- [Fact]
- public void WithDisplayName_String_SetsDisplayName()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void WithDisplayName_String_SetsDisplayName()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- builder.WithDisplayName("test");
+ // Act
+ builder.WithDisplayName("test");
- // Assert
- var endpoint = builder.Build();
- Assert.Equal("test", endpoint.DisplayName);
- }
+ // Assert
+ var endpoint = builder.Build();
+ Assert.Equal("test", endpoint.DisplayName);
+ }
- [Fact]
- public void WithDisplayName_ChainedCall_ReturnedBuilderIsDerivedType()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void WithDisplayName_ChainedCall_ReturnedBuilderIsDerivedType()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var chainedBuilder = builder.WithDisplayName("test");
+ // Act
+ var chainedBuilder = builder.WithDisplayName("test");
- // Assert
- Assert.True(chainedBuilder.TestProperty);
- }
+ // Assert
+ Assert.True(chainedBuilder.TestProperty);
+ }
- [Fact]
- public void WithDisplayName_Func_SetsDisplayName()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void WithDisplayName_Func_SetsDisplayName()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- builder.WithDisplayName(b => "test");
+ // Act
+ builder.WithDisplayName(b => "test");
- // Assert
- var endpoint = builder.Build();
- Assert.Equal("test", endpoint.DisplayName);
- }
+ // Assert
+ var endpoint = builder.Build();
+ Assert.Equal("test", endpoint.DisplayName);
+ }
- [Fact]
- public void WithMetadata_AddsMetadata()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void WithMetadata_AddsMetadata()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- builder.WithMetadata("test", new HostAttribute("www.example.com", "example.com"));
+ // Act
+ builder.WithMetadata("test", new HostAttribute("www.example.com", "example.com"));
- // Assert
- var endpoint = builder.Build();
+ // Assert
+ var endpoint = builder.Build();
- var hosts = endpoint.Metadata.GetMetadata<IHostMetadata>();
- Assert.NotNull(hosts);
- Assert.Equal(new[] { "www.example.com", "example.com" }, hosts.Hosts);
+ var hosts = endpoint.Metadata.GetMetadata<IHostMetadata>();
+ Assert.NotNull(hosts);
+ Assert.Equal(new[] { "www.example.com", "example.com" }, hosts.Hosts);
- var @string = endpoint.Metadata.GetMetadata<string>();
- Assert.Equal("test", @string);
- }
+ var @string = endpoint.Metadata.GetMetadata<string>();
+ Assert.Equal("test", @string);
+ }
- [Fact]
- public void WithMetadata_ChainedCall_ReturnedBuilderIsDerivedType()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void WithMetadata_ChainedCall_ReturnedBuilderIsDerivedType()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var chainedBuilder = builder.WithMetadata("test");
+ // Act
+ var chainedBuilder = builder.WithMetadata("test");
- // Assert
- Assert.True(chainedBuilder.TestProperty);
- }
+ // Assert
+ Assert.True(chainedBuilder.TestProperty);
+ }
- [Fact]
- public void WithName_SetsEndpointName()
- {
- // Arrange
- var name = "SomeEndpointName";
- var builder = CreateBuilder();
+ [Fact]
+ public void WithName_SetsEndpointName()
+ {
+ // Arrange
+ var name = "SomeEndpointName";
+ var builder = CreateBuilder();
- // Act
- builder.WithName(name);
+ // Act
+ builder.WithName(name);
- // Assert
- var endpoint = builder.Build();
+ // Assert
+ var endpoint = builder.Build();
- var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>();
- Assert.Equal(name, endpointName.EndpointName);
+ var endpointName = endpoint.Metadata.GetMetadata<IEndpointNameMetadata>();
+ Assert.Equal(name, endpointName.EndpointName);
- var routeName = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
- Assert.Equal(name, routeName.RouteName);
- }
+ var routeName = endpoint.Metadata.GetMetadata<IRouteNameMetadata>();
+ Assert.Equal(name, routeName.RouteName);
+ }
- [Fact]
- public void WithGroupName_SetsEndpointGroupName()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void WithGroupName_SetsEndpointGroupName()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- builder.WithGroupName("SomeEndpointGroupName");
+ // Act
+ builder.WithGroupName("SomeEndpointGroupName");
- // Assert
- var endpoint = builder.Build();
+ // Assert
+ var endpoint = builder.Build();
- var endpointGroupName = endpoint.Metadata.GetMetadata<IEndpointGroupNameMetadata>();
- Assert.Equal("SomeEndpointGroupName", endpointGroupName.EndpointGroupName);
- }
+ var endpointGroupName = endpoint.Metadata.GetMetadata<IEndpointGroupNameMetadata>();
+ Assert.Equal("SomeEndpointGroupName", endpointGroupName.EndpointGroupName);
+ }
- private TestEndpointConventionBuilder CreateBuilder()
+ private TestEndpointConventionBuilder CreateBuilder()
+ {
+ var conventionBuilder = new DefaultEndpointConventionBuilder(new RouteEndpointBuilder(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/test"),
+ order: 0));
+
+ return new TestEndpointConventionBuilder(conventionBuilder);
+ }
+
+ private class TestEndpointConventionBuilder : IEndpointConventionBuilder
+ {
+ private readonly DefaultEndpointConventionBuilder _endpointConventionBuilder;
+ public bool TestProperty { get; } = true;
+
+ public TestEndpointConventionBuilder(DefaultEndpointConventionBuilder endpointConventionBuilder)
{
- var conventionBuilder = new DefaultEndpointConventionBuilder(new RouteEndpointBuilder(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/test"),
- order: 0));
+ _endpointConventionBuilder = endpointConventionBuilder;
+ }
- return new TestEndpointConventionBuilder(conventionBuilder);
+ public void Add(Action<EndpointBuilder> convention)
+ {
+ _endpointConventionBuilder.Add(convention);
}
- private class TestEndpointConventionBuilder : IEndpointConventionBuilder
+ public Endpoint Build()
{
- private readonly DefaultEndpointConventionBuilder _endpointConventionBuilder;
- public bool TestProperty { get; } = true;
-
- public TestEndpointConventionBuilder(DefaultEndpointConventionBuilder endpointConventionBuilder)
- {
- _endpointConventionBuilder = endpointConventionBuilder;
- }
-
- public void Add(Action<EndpointBuilder> convention)
- {
- _endpointConventionBuilder.Add(convention);
- }
-
- public Endpoint Build()
- {
- return _endpointConventionBuilder.Build();
- }
+ return _endpointConventionBuilder.Build();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/CompositeEndpointDataSourceTest.cs b/src/Http/Routing/test/UnitTests/CompositeEndpointDataSourceTest.cs
index f8cf42bb37..de7ce6a4b9 100644
--- a/src/Http/Routing/test/UnitTests/CompositeEndpointDataSourceTest.cs
+++ b/src/Http/Routing/test/UnitTests/CompositeEndpointDataSourceTest.cs
@@ -12,168 +12,167 @@ using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class CompositeEndpointDataSourceTest
{
- public class CompositeEndpointDataSourceTest
+ [Fact]
+ public void CreatesShallowCopyOf_ListOfEndpoints()
{
- [Fact]
- public void CreatesShallowCopyOf_ListOfEndpoints()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/a");
- var endpoint2 = CreateEndpoint("/b");
- var dataSource = new DefaultEndpointDataSource(new Endpoint[] { endpoint1, endpoint2 });
- var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource });
-
- // Act
- var endpoints = compositeDataSource.Endpoints;
-
- // Assert
- Assert.NotSame(endpoints, dataSource.Endpoints);
- Assert.Equal(endpoints, dataSource.Endpoints);
- }
+ // Arrange
+ var endpoint1 = CreateEndpoint("/a");
+ var endpoint2 = CreateEndpoint("/b");
+ var dataSource = new DefaultEndpointDataSource(new Endpoint[] { endpoint1, endpoint2 });
+ var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource });
+
+ // Act
+ var endpoints = compositeDataSource.Endpoints;
+
+ // Assert
+ Assert.NotSame(endpoints, dataSource.Endpoints);
+ Assert.Equal(endpoints, dataSource.Endpoints);
+ }
- [Fact]
- public void Endpoints_ReturnsAllEndpoints_FromMultipleDataSources()
+ [Fact]
+ public void Endpoints_ReturnsAllEndpoints_FromMultipleDataSources()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/a");
+ var endpoint2 = CreateEndpoint("/b");
+ var endpoint3 = CreateEndpoint("/c");
+ var endpoint4 = CreateEndpoint("/d");
+ var endpoint5 = CreateEndpoint("/e");
+ var compositeDataSource = new CompositeEndpointDataSource(new[]
{
- // Arrange
- var endpoint1 = CreateEndpoint("/a");
- var endpoint2 = CreateEndpoint("/b");
- var endpoint3 = CreateEndpoint("/c");
- var endpoint4 = CreateEndpoint("/d");
- var endpoint5 = CreateEndpoint("/e");
- var compositeDataSource = new CompositeEndpointDataSource(new[]
- {
new DefaultEndpointDataSource(new Endpoint[] { endpoint1, endpoint2 }),
new DefaultEndpointDataSource(new Endpoint[] { endpoint3, endpoint4 }),
new DefaultEndpointDataSource(new Endpoint[] { endpoint5 }),
});
- // Act
- var endpoints = compositeDataSource.Endpoints;
-
- // Assert
- Assert.Collection(
- endpoints,
- (ep) => Assert.Same(endpoint1, ep),
- (ep) => Assert.Same(endpoint2, ep),
- (ep) => Assert.Same(endpoint3, ep),
- (ep) => Assert.Same(endpoint4, ep),
- (ep) => Assert.Same(endpoint5, ep));
- }
-
- [Fact]
- public void DataSourceChanges_AreReflected_InEndpoints()
- {
- // Arrange1
- var endpoint1 = CreateEndpoint("/a");
- var dataSource1 = new DynamicEndpointDataSource(endpoint1);
- var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource1 });
-
- // Act1
- var endpoints = compositeDataSource.Endpoints;
-
- // Assert1
- var endpoint = Assert.Single(endpoints);
- Assert.Same(endpoint1, endpoint);
-
- // Arrange2
- var endpoint2 = CreateEndpoint("/b");
-
- // Act2
- dataSource1.AddEndpoint(endpoint2);
-
- // Assert2
- Assert.Collection(
- compositeDataSource.Endpoints,
- (ep) => Assert.Same(endpoint1, ep),
- (ep) => Assert.Same(endpoint2, ep));
-
- // Arrange3
- var endpoint3 = CreateEndpoint("/c");
-
- // Act2
- dataSource1.AddEndpoint(endpoint3);
-
- // Assert2
- Assert.Collection(
- compositeDataSource.Endpoints,
- (ep) => Assert.Same(endpoint1, ep),
- (ep) => Assert.Same(endpoint2, ep),
- (ep) => Assert.Same(endpoint3, ep));
- }
+ // Act
+ var endpoints = compositeDataSource.Endpoints;
+
+ // Assert
+ Assert.Collection(
+ endpoints,
+ (ep) => Assert.Same(endpoint1, ep),
+ (ep) => Assert.Same(endpoint2, ep),
+ (ep) => Assert.Same(endpoint3, ep),
+ (ep) => Assert.Same(endpoint4, ep),
+ (ep) => Assert.Same(endpoint5, ep));
+ }
- [Fact]
- public void ConsumerChangeToken_IsRefreshed_WhenDataSourceCallbackFires()
- {
- // Arrange1
- var endpoint1 = CreateEndpoint("/a");
- var dataSource1 = new DynamicEndpointDataSource(endpoint1);
- var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource1 });
-
- // Act1
- var endpoints = compositeDataSource.Endpoints;
-
- // Assert1
- var changeToken1 = compositeDataSource.GetChangeToken();
- var token = Assert.IsType<CancellationChangeToken>(changeToken1);
- Assert.False(token.HasChanged); // initial state
-
- // Arrange2
- var endpoint2 = CreateEndpoint("/b");
-
- // Act2
- dataSource1.AddEndpoint(endpoint2);
-
- // Assert2
- Assert.True(changeToken1.HasChanged); // old token is expected to be changed
- var changeToken2 = compositeDataSource.GetChangeToken(); // new token is in a unchanged state
- Assert.NotSame(changeToken2, changeToken1);
- token = Assert.IsType<CancellationChangeToken>(changeToken2);
- Assert.False(token.HasChanged);
-
- // Arrange3
- var endpoint3 = CreateEndpoint("/c");
-
- // Act2
- dataSource1.AddEndpoint(endpoint3);
-
- // Assert2
- Assert.True(changeToken2.HasChanged); // old token is expected to be changed
- var changeToken3 = compositeDataSource.GetChangeToken(); // new token is in a unchanged state
- Assert.NotSame(changeToken3, changeToken2);
- Assert.NotSame(changeToken3, changeToken1);
- token = Assert.IsType<CancellationChangeToken>(changeToken3);
- Assert.False(token.HasChanged);
- }
+ [Fact]
+ public void DataSourceChanges_AreReflected_InEndpoints()
+ {
+ // Arrange1
+ var endpoint1 = CreateEndpoint("/a");
+ var dataSource1 = new DynamicEndpointDataSource(endpoint1);
+ var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource1 });
+
+ // Act1
+ var endpoints = compositeDataSource.Endpoints;
+
+ // Assert1
+ var endpoint = Assert.Single(endpoints);
+ Assert.Same(endpoint1, endpoint);
+
+ // Arrange2
+ var endpoint2 = CreateEndpoint("/b");
+
+ // Act2
+ dataSource1.AddEndpoint(endpoint2);
+
+ // Assert2
+ Assert.Collection(
+ compositeDataSource.Endpoints,
+ (ep) => Assert.Same(endpoint1, ep),
+ (ep) => Assert.Same(endpoint2, ep));
+
+ // Arrange3
+ var endpoint3 = CreateEndpoint("/c");
+
+ // Act2
+ dataSource1.AddEndpoint(endpoint3);
+
+ // Assert2
+ Assert.Collection(
+ compositeDataSource.Endpoints,
+ (ep) => Assert.Same(endpoint1, ep),
+ (ep) => Assert.Same(endpoint2, ep),
+ (ep) => Assert.Same(endpoint3, ep));
+ }
- private RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- int order = 0,
- string routeName = null)
- {
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
- order,
- EndpointMetadataCollection.Empty,
- null);
- }
+ [Fact]
+ public void ConsumerChangeToken_IsRefreshed_WhenDataSourceCallbackFires()
+ {
+ // Arrange1
+ var endpoint1 = CreateEndpoint("/a");
+ var dataSource1 = new DynamicEndpointDataSource(endpoint1);
+ var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource1 });
+
+ // Act1
+ var endpoints = compositeDataSource.Endpoints;
+
+ // Assert1
+ var changeToken1 = compositeDataSource.GetChangeToken();
+ var token = Assert.IsType<CancellationChangeToken>(changeToken1);
+ Assert.False(token.HasChanged); // initial state
+
+ // Arrange2
+ var endpoint2 = CreateEndpoint("/b");
+
+ // Act2
+ dataSource1.AddEndpoint(endpoint2);
+
+ // Assert2
+ Assert.True(changeToken1.HasChanged); // old token is expected to be changed
+ var changeToken2 = compositeDataSource.GetChangeToken(); // new token is in a unchanged state
+ Assert.NotSame(changeToken2, changeToken1);
+ token = Assert.IsType<CancellationChangeToken>(changeToken2);
+ Assert.False(token.HasChanged);
+
+ // Arrange3
+ var endpoint3 = CreateEndpoint("/c");
+
+ // Act2
+ dataSource1.AddEndpoint(endpoint3);
+
+ // Assert2
+ Assert.True(changeToken2.HasChanged); // old token is expected to be changed
+ var changeToken3 = compositeDataSource.GetChangeToken(); // new token is in a unchanged state
+ Assert.NotSame(changeToken3, changeToken2);
+ Assert.NotSame(changeToken3, changeToken1);
+ token = Assert.IsType<CancellationChangeToken>(changeToken3);
+ Assert.False(token.HasChanged);
+ }
- private class CustomEndpointDataSource : EndpointDataSource
- {
- private readonly CancellationTokenSource _cts;
- private readonly CancellationChangeToken _token;
+ private RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ int order = 0,
+ string routeName = null)
+ {
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template, defaults, parameterPolicies: null),
+ order,
+ EndpointMetadataCollection.Empty,
+ null);
+ }
- public CustomEndpointDataSource()
- {
- _cts = new CancellationTokenSource();
- _token = new CancellationChangeToken(_cts.Token);
- }
+ private class CustomEndpointDataSource : EndpointDataSource
+ {
+ private readonly CancellationTokenSource _cts;
+ private readonly CancellationChangeToken _token;
- public override IChangeToken GetChangeToken() => _token;
- public override IReadOnlyList<Endpoint> Endpoints => Array.Empty<Endpoint>();
+ public CustomEndpointDataSource()
+ {
+ _cts = new CancellationTokenSource();
+ _token = new CancellationChangeToken(_cts.Token);
}
+
+ public override IChangeToken GetChangeToken() => _token;
+ public override IReadOnlyList<Endpoint> Endpoints => Array.Empty<Endpoint>();
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/ConstraintMatcherTest.cs b/src/Http/Routing/test/UnitTests/ConstraintMatcherTest.cs
index 949b070350..186c1d60b8 100644
--- a/src/Http/Routing/test/UnitTests/ConstraintMatcherTest.cs
+++ b/src/Http/Routing/test/UnitTests/ConstraintMatcherTest.cs
@@ -8,245 +8,244 @@ using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class ConstraintMatcherTest
{
- public class ConstraintMatcherTest
- {
- private const string _name = "name";
+ private const string _name = "name";
- [Fact]
- public void MatchUrlGeneration_DoesNotLogData()
- {
- // Arrange
- var sink = new TestSink();
- var logger = new TestLogger(_name, sink, enabled: true);
+ [Fact]
+ public void MatchUrlGeneration_DoesNotLogData()
+ {
+ // Arrange
+ var sink = new TestSink();
+ var logger = new TestLogger(_name, sink, enabled: true);
- var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
- var constraints = new Dictionary<string, IRouteConstraint>
+ var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
- // Act
- RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.UrlGeneration,
- logger: logger);
-
- // Assert
- // There are no BeginScopes called.
- Assert.Empty(sink.Scopes);
-
- // There are no WriteCores called.
- Assert.Empty(sink.Writes);
- }
+ // Act
+ RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.UrlGeneration,
+ logger: logger);
+
+ // Assert
+ // There are no BeginScopes called.
+ Assert.Empty(sink.Scopes);
+
+ // There are no WriteCores called.
+ Assert.Empty(sink.Writes);
+ }
- [Fact]
- public void MatchFail_LogsCorrectData()
- {
- // Arrange & Act
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void MatchFail_LogsCorrectData()
+ {
+ // Arrange & Act
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
- var sink = SetUpMatch(constraints, loggerEnabled: true);
- var expectedMessage = "Route value 'value' with key 'b' did not match the constraint " +
- $"'{typeof(FailConstraint).FullName}'";
-
- // Assert
- Assert.Empty(sink.Scopes);
- var write = Assert.Single(sink.Writes);
- Assert.Equal(expectedMessage, write.State?.ToString());
- }
+ var sink = SetUpMatch(constraints, loggerEnabled: true);
+ var expectedMessage = "Route value 'value' with key 'b' did not match the constraint " +
+ $"'{typeof(FailConstraint).FullName}'";
+
+ // Assert
+ Assert.Empty(sink.Scopes);
+ var write = Assert.Single(sink.Writes);
+ Assert.Equal(expectedMessage, write.State?.ToString());
+ }
- [Fact]
- public void MatchSuccess_DoesNotLog()
- {
- // Arrange & Act
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void MatchSuccess_DoesNotLog()
+ {
+ // Arrange & Act
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new PassConstraint()}
};
- var sink = SetUpMatch(constraints, false);
+ var sink = SetUpMatch(constraints, false);
- // Assert
- Assert.Empty(sink.Scopes);
- Assert.Empty(sink.Writes);
- }
+ // Assert
+ Assert.Empty(sink.Scopes);
+ Assert.Empty(sink.Writes);
+ }
- [Fact]
- public void ReturnsTrueOnValidConstraints()
- {
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void ReturnsTrueOnValidConstraints()
+ {
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new PassConstraint()}
};
- var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
+ var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
- Assert.True(RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: NullLogger.Instance));
- }
+ Assert.True(RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: NullLogger.Instance));
+ }
- [Fact]
- public void ConstraintsGetTheRightKey()
- {
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void ConstraintsGetTheRightKey()
+ {
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint("a")},
{"b", new PassConstraint("b")}
};
- var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
+ var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
- Assert.True(RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: NullLogger.Instance));
- }
+ Assert.True(RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: NullLogger.Instance));
+ }
- [Fact]
- public void ReturnsFalseOnInvalidConstraintsThatDontMatch()
- {
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void ReturnsFalseOnInvalidConstraintsThatDontMatch()
+ {
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new FailConstraint()},
{"b", new FailConstraint()}
};
- var routeValueDictionary = new RouteValueDictionary(new { c = "value", d = "value" });
+ var routeValueDictionary = new RouteValueDictionary(new { c = "value", d = "value" });
- Assert.False(RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: NullLogger.Instance));
- }
+ Assert.False(RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: NullLogger.Instance));
+ }
- [Fact]
- public void ReturnsFalseOnInvalidConstraintsThatMatch()
- {
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void ReturnsFalseOnInvalidConstraintsThatMatch()
+ {
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new FailConstraint()},
{"b", new FailConstraint()}
};
- var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
+ var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
- Assert.False(RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: NullLogger.Instance));
- }
+ Assert.False(RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: NullLogger.Instance));
+ }
- [Fact]
- public void ReturnsFalseOnValidAndInvalidConstraintsMixThatMatch()
- {
- var constraints = new Dictionary<string, IRouteConstraint>
+ [Fact]
+ public void ReturnsFalseOnValidAndInvalidConstraintsMixThatMatch()
+ {
+ var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
- var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
+ var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
- Assert.False(RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: NullLogger.Instance));
- }
+ Assert.False(RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: NullLogger.Instance));
+ }
- [Fact]
- public void ReturnsTrueOnNullInput()
- {
- Assert.True(RouteConstraintMatcher.Match(
- constraints: null,
- routeValues: new RouteValueDictionary(),
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: NullLogger.Instance));
- }
+ [Fact]
+ public void ReturnsTrueOnNullInput()
+ {
+ Assert.True(RouteConstraintMatcher.Match(
+ constraints: null,
+ routeValues: new RouteValueDictionary(),
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: NullLogger.Instance));
+ }
- private TestSink SetUpMatch(Dictionary<string, IRouteConstraint> constraints, bool loggerEnabled)
+ private TestSink SetUpMatch(Dictionary<string, IRouteConstraint> constraints, bool loggerEnabled)
+ {
+ // Arrange
+ var sink = new TestSink();
+ var logger = new TestLogger(_name, sink, loggerEnabled);
+
+ var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
+
+ // Act
+ RouteConstraintMatcher.Match(
+ constraints: constraints,
+ routeValues: routeValueDictionary,
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeDirection: RouteDirection.IncomingRequest,
+ logger: logger);
+ return sink;
+ }
+
+ private class PassConstraint : IRouteConstraint
+ {
+ private readonly string _expectedKey;
+
+ public PassConstraint(string expectedKey = null)
{
- // Arrange
- var sink = new TestSink();
- var logger = new TestLogger(_name, sink, loggerEnabled);
-
- var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
-
- // Act
- RouteConstraintMatcher.Match(
- constraints: constraints,
- routeValues: routeValueDictionary,
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeDirection: RouteDirection.IncomingRequest,
- logger: logger);
- return sink;
+ _expectedKey = expectedKey;
}
- private class PassConstraint : IRouteConstraint
+ public bool Match(
+ HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- private readonly string _expectedKey;
-
- public PassConstraint(string expectedKey = null)
+ if (_expectedKey != null)
{
- _expectedKey = expectedKey;
+ Assert.Equal(_expectedKey, routeKey);
}
- public bool Match(
- HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- if (_expectedKey != null)
- {
- Assert.Equal(_expectedKey, routeKey);
- }
-
- return true;
- }
+ return true;
}
+ }
- private class FailConstraint : IRouteConstraint
+ private class FailConstraint : IRouteConstraint
+ {
+ public bool Match(
+ HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- public bool Match(
- HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- return false;
- }
+ return false;
}
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/AlphaRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/AlphaRouteConstraintTests.cs
index 2c3f4acfc3..1ef3882fa3 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/AlphaRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/AlphaRouteConstraintTests.cs
@@ -4,29 +4,28 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class AlphaRouteConstraintTests
{
- public class AlphaRouteConstraintTests
+ [Theory]
+ [InlineData("alpha", true)]
+ [InlineData("a1pha", false)]
+ [InlineData("ALPHA", true)]
+ [InlineData("A1PHA", false)]
+ [InlineData("alPHA", true)]
+ [InlineData("A1pHA", false)]
+ [InlineData("AlpHA╥", false)]
+ [InlineData("", true)]
+ public void AlphaRouteConstraintTest(string parameterValue, bool expected)
{
- [Theory]
- [InlineData("alpha", true)]
- [InlineData("a1pha", false)]
- [InlineData("ALPHA", true)]
- [InlineData("A1PHA", false)]
- [InlineData("alPHA", true)]
- [InlineData("A1pHA", false)]
- [InlineData("AlpHA╥", false)]
- [InlineData("", true)]
- public void AlphaRouteConstraintTest(string parameterValue, bool expected)
- {
- // Arrange
- var constraint = new AlphaRouteConstraint();
+ // Arrange
+ var constraint = new AlphaRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/BoolRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/BoolRouteConstraintTests.cs
index f95890fda8..337fca54a8 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/BoolRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/BoolRouteConstraintTests.cs
@@ -9,32 +9,31 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class BoolRouteConstraintTests
{
- public class BoolRouteConstraintTests
+ [Theory]
+ [InlineData("true", true)]
+ [InlineData("TruE", true)]
+ [InlineData("false", true)]
+ [InlineData("FalSe", true)]
+ [InlineData(" FalSe", true)]
+ [InlineData("True ", true)]
+ [InlineData(" False ", true)]
+ [InlineData(true, true)]
+ [InlineData(false, true)]
+ [InlineData(1, false)]
+ [InlineData("not-parseable-as-bool", false)]
+ public void BoolRouteConstraint(object parameterValue, bool expected)
{
- [Theory]
- [InlineData("true", true)]
- [InlineData("TruE", true)]
- [InlineData("false", true)]
- [InlineData("FalSe", true)]
- [InlineData(" FalSe", true)]
- [InlineData("True ", true)]
- [InlineData(" False ", true)]
- [InlineData(true, true)]
- [InlineData(false, true)]
- [InlineData(1, false)]
- [InlineData("not-parseable-as-bool", false)]
- public void BoolRouteConstraint(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new BoolRouteConstraint();
+ // Arrange
+ var constraint = new BoolRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/CompositeRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/CompositeRouteConstraintTests.cs
index 7a25b9b85e..bb33bcef0d 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/CompositeRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/CompositeRouteConstraintTests.cs
@@ -8,47 +8,46 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class CompositeRouteConstraintTests
{
- public class CompositeRouteConstraintTests
+ [Theory]
+ [InlineData(true, true, true)]
+ [InlineData(true, false, false)]
+ [InlineData(false, true, false)]
+ [InlineData(false, false, false)]
+ public void CompositeRouteConstraint_Match_CallsMatchOnInnerConstraints(
+ bool inner1Result,
+ bool inner2Result,
+ bool expected)
{
- [Theory]
- [InlineData(true, true, true)]
- [InlineData(true, false, false)]
- [InlineData(false, true, false)]
- [InlineData(false, false, false)]
- public void CompositeRouteConstraint_Match_CallsMatchOnInnerConstraints(
- bool inner1Result,
- bool inner2Result,
- bool expected)
- {
- // Arrange
- var inner1 = MockConstraintWithResult(inner1Result);
- var inner2 = MockConstraintWithResult(inner2Result);
+ // Arrange
+ var inner1 = MockConstraintWithResult(inner1Result);
+ var inner2 = MockConstraintWithResult(inner2Result);
- // Act
- var constraint = new CompositeRouteConstraint(new[] { inner1.Object, inner2.Object });
- var actual = ConstraintsTestHelper.TestConstraint(constraint, null);
+ // Act
+ var constraint = new CompositeRouteConstraint(new[] { inner1.Object, inner2.Object });
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, null);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- static readonly Expression<Func<IRouteConstraint, bool>> ConstraintMatchMethodExpression =
- c => c.Match(
- It.IsAny<HttpContext>(),
- It.IsAny<IRouter>(),
- It.IsAny<string>(),
- It.IsAny<RouteValueDictionary>(),
- It.IsAny<RouteDirection>());
+ static readonly Expression<Func<IRouteConstraint, bool>> ConstraintMatchMethodExpression =
+ c => c.Match(
+ It.IsAny<HttpContext>(),
+ It.IsAny<IRouter>(),
+ It.IsAny<string>(),
+ It.IsAny<RouteValueDictionary>(),
+ It.IsAny<RouteDirection>());
- private static Mock<IRouteConstraint> MockConstraintWithResult(bool result)
- {
- var mock = new Mock<IRouteConstraint>();
- mock.Setup(ConstraintMatchMethodExpression)
- .Returns(result)
- .Verifiable();
- return mock;
- }
+ private static Mock<IRouteConstraint> MockConstraintWithResult(bool result)
+ {
+ var mock = new Mock<IRouteConstraint>();
+ mock.Setup(ConstraintMatchMethodExpression)
+ .Returns(result)
+ .Verifiable();
+ return mock;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/ConstraintsTestHelper.cs b/src/Http/Routing/test/UnitTests/Constraints/ConstraintsTestHelper.cs
index 6a77eb095b..aa612f03b8 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/ConstraintsTestHelper.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/ConstraintsTestHelper.cs
@@ -6,16 +6,15 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Moq;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class ConstraintsTestHelper
{
- public class ConstraintsTestHelper
+ public static bool TestConstraint(IRouteConstraint constraint, object value)
{
- public static bool TestConstraint(IRouteConstraint constraint, object value)
- {
- var parameterName = "fake";
- var values = new RouteValueDictionary() { { parameterName, value } };
- var routeDirection = RouteDirection.IncomingRequest;
- return constraint.Match(httpContext: null, route: null, parameterName, values, routeDirection);
- }
+ var parameterName = "fake";
+ var values = new RouteValueDictionary() { { parameterName, value } };
+ var routeDirection = RouteDirection.IncomingRequest;
+ return constraint.Match(httpContext: null, route: null, parameterName, values, routeDirection);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/DateTimeRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/DateTimeRouteConstraintTests.cs
index 6622e04112..a0ed6690c5 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/DateTimeRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/DateTimeRouteConstraintTests.cs
@@ -6,48 +6,47 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class DateTimeRouteConstraintTests
{
- public class DateTimeRouteConstraintTests
+ public static IEnumerable<object[]> GetDateTimeObject
{
- public static IEnumerable<object[]> GetDateTimeObject
+ get
{
- get
+ yield return new object[]
{
- yield return new object[]
- {
DateTime.Now,
true
- };
- }
+ };
}
+ }
- [Theory]
- [InlineData("12/25/2009", true)]
- [InlineData("25/12/2009 11:45:00 PM", false)]
- [InlineData("25/12/2009", false)]
- [InlineData("11:45:00 PM", true)]
- [InlineData("11:45:00", true)]
- [InlineData("11:45", true)]
- [InlineData("11", false)]
- [InlineData("", false)]
- [InlineData("Apr 5 2009 11:45:00 PM", true)]
- [InlineData("April 5 2009 11:45:00 PM", true)]
- [InlineData("12/25/2009 11:45:00 PM", true)]
- [InlineData("2009-05-12T11:45:00Z", true)]
- [InlineData("not-parseable-as-date", false)]
- [InlineData(false, false)]
- [MemberData(nameof(GetDateTimeObject))]
- public void DateTimeRouteConstraint(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new DateTimeRouteConstraint();
+ [Theory]
+ [InlineData("12/25/2009", true)]
+ [InlineData("25/12/2009 11:45:00 PM", false)]
+ [InlineData("25/12/2009", false)]
+ [InlineData("11:45:00 PM", true)]
+ [InlineData("11:45:00", true)]
+ [InlineData("11:45", true)]
+ [InlineData("11", false)]
+ [InlineData("", false)]
+ [InlineData("Apr 5 2009 11:45:00 PM", true)]
+ [InlineData("April 5 2009 11:45:00 PM", true)]
+ [InlineData("12/25/2009 11:45:00 PM", true)]
+ [InlineData("2009-05-12T11:45:00Z", true)]
+ [InlineData("not-parseable-as-date", false)]
+ [InlineData(false, false)]
+ [MemberData(nameof(GetDateTimeObject))]
+ public void DateTimeRouteConstraint(object parameterValue, bool expected)
+ {
+ // Arrange
+ var constraint = new DateTimeRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/DecimalRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/DecimalRouteConstraintTests.cs
index 9acba3ff82..d0ed1738bb 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/DecimalRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/DecimalRouteConstraintTests.cs
@@ -5,39 +5,38 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class DecimalRouteConstraintTests
{
- public class DecimalRouteConstraintTests
+ public static IEnumerable<object[]> GetDecimalObject
{
- public static IEnumerable<object[]> GetDecimalObject
+ get
{
- get
+ yield return new object[]
{
- yield return new object[]
- {
2m,
true
- };
- }
+ };
}
+ }
- [Theory]
- [InlineData("3.14", true)]
- [InlineData("9223372036854775808.9223372036854775808", true)]
- [InlineData("1.79769313486232E+300", false)]
- [InlineData("not-parseable-as-decimal", false)]
- [InlineData(false, false)]
- [MemberData(nameof(GetDecimalObject))]
- public void DecimalRouteConstraint_ApplyConstraint(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new DecimalRouteConstraint();
+ [Theory]
+ [InlineData("3.14", true)]
+ [InlineData("9223372036854775808.9223372036854775808", true)]
+ [InlineData("1.79769313486232E+300", false)]
+ [InlineData("not-parseable-as-decimal", false)]
+ [InlineData(false, false)]
+ [MemberData(nameof(GetDecimalObject))]
+ public void DecimalRouteConstraint_ApplyConstraint(object parameterValue, bool expected)
+ {
+ // Arrange
+ var constraint = new DecimalRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/DoubleRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/DoubleRouteConstraintTests.cs
index fef54a2dfe..0881ca8cd2 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/DoubleRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/DoubleRouteConstraintTests.cs
@@ -4,27 +4,26 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class DoubleRouteConstraintTests
{
- public class DoubleRouteConstraintTests
+ [Theory]
+ [InlineData("3.14", true)]
+ [InlineData(3.14f, true)]
+ [InlineData(3.14d, true)]
+ [InlineData("1.79769313486232E+300", true)]
+ [InlineData("not-parseable-as-double", false)]
+ [InlineData(false, false)]
+ public void DoubleRouteConstraint_ApplyConstraint(object parameterValue, bool expected)
{
- [Theory]
- [InlineData("3.14", true)]
- [InlineData(3.14f, true)]
- [InlineData(3.14d, true)]
- [InlineData("1.79769313486232E+300", true)]
- [InlineData("not-parseable-as-double", false)]
- [InlineData(false, false)]
- public void DoubleRouteConstraint_ApplyConstraint(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new DoubleRouteConstraint();
+ // Arrange
+ var constraint = new DoubleRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/FIleNameRouteConstraintTest.cs b/src/Http/Routing/test/UnitTests/Constraints/FIleNameRouteConstraintTest.cs
index a76878a01f..d901a6a7f5 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/FIleNameRouteConstraintTest.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/FIleNameRouteConstraintTest.cs
@@ -3,15 +3,15 @@
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+public class FileNameRouteConstraintTest
{
- public class FileNameRouteConstraintTest
+ public static TheoryData<object> FileNameData
{
- public static TheoryData<object> FileNameData
+ get
{
- get
- {
- return new TheoryData<object>()
+ return new TheoryData<object>()
{
"hello.txt",
"hello.txt.jpg",
@@ -23,32 +23,32 @@ namespace Microsoft.AspNetCore.Routing.Constraints
".a",
"/.......a"
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(FileNameData))]
- public void Match_RouteValue_IsFileName(object value)
- {
- // Arrange
- var constraint = new FileNameRouteConstraint();
+ [Theory]
+ [MemberData(nameof(FileNameData))]
+ public void Match_RouteValue_IsFileName(object value)
+ {
+ // Arrange
+ var constraint = new FileNameRouteConstraint();
- var values = new RouteValueDictionary();
- values.Add("path", value);
+ var values = new RouteValueDictionary();
+ values.Add("path", value);
- // Act
- var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
+ // Act
+ var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- public static TheoryData<object> NonFileNameData
+ public static TheoryData<object> NonFileNameData
+ {
+ get
{
- get
- {
- return new TheoryData<object>()
+ return new TheoryData<object>()
{
null,
string.Empty,
@@ -62,39 +62,38 @@ namespace Microsoft.AspNetCore.Routing.Constraints
"/////hello.",
"a/b./.c/d.",
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(NonFileNameData))]
- public void Match_RouteValue_IsNotFileName(object value)
- {
- // Arrange
- var constraint = new FileNameRouteConstraint();
+ [Theory]
+ [MemberData(nameof(NonFileNameData))]
+ public void Match_RouteValue_IsNotFileName(object value)
+ {
+ // Arrange
+ var constraint = new FileNameRouteConstraint();
- var values = new RouteValueDictionary();
- values.Add("path", value);
+ var values = new RouteValueDictionary();
+ values.Add("path", value);
- // Act
- var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
+ // Act
+ var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void Match_MissingValue_IsNotFileName()
- {
- // Arrange
- var constraint = new FileNameRouteConstraint();
+ [Fact]
+ public void Match_MissingValue_IsNotFileName()
+ {
+ // Arrange
+ var constraint = new FileNameRouteConstraint();
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
+ // Act
+ var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/FloatRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/FloatRouteConstraintTests.cs
index 4ce6a72c19..6a51450a83 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/FloatRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/FloatRouteConstraintTests.cs
@@ -4,26 +4,25 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class FloatRouteConstraintTests
{
- public class FloatRouteConstraintTests
+ [Theory]
+ [InlineData("3.14", true)]
+ [InlineData(3.14, true)]
+ [InlineData("not-parseable-as-float", false)]
+ [InlineData(false, false)]
+ [InlineData("1.79769313486232E+300", true)] // Parses as infinity
+ public void FloatRouteConstraint_ApplyConstraint(object parameterValue, bool expected)
{
- [Theory]
- [InlineData("3.14", true)]
- [InlineData(3.14, true)]
- [InlineData("not-parseable-as-float", false)]
- [InlineData(false, false)]
- [InlineData("1.79769313486232E+300", true)] // Parses as infinity
- public void FloatRouteConstraint_ApplyConstraint(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new FloatRouteConstraint();
+ // Arrange
+ var constraint = new FloatRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/GuidRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/GuidRouteConstraintTests.cs
index 32f2c871e9..9954746ec8 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/GuidRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/GuidRouteConstraintTests.cs
@@ -6,32 +6,31 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class GuidRouteConstraintTests
{
- public class GuidRouteConstraintTests
- {
- [Theory]
- [InlineData("12345678-1234-1234-1234-123456789012", false, true)]
- [InlineData("12345678-1234-1234-1234-123456789012", true, true)]
- [InlineData("12345678901234567890123456789012", false, true)]
- [InlineData("not-parseable-as-guid", false, false)]
- [InlineData(12, false, false)]
+ [Theory]
+ [InlineData("12345678-1234-1234-1234-123456789012", false, true)]
+ [InlineData("12345678-1234-1234-1234-123456789012", true, true)]
+ [InlineData("12345678901234567890123456789012", false, true)]
+ [InlineData("not-parseable-as-guid", false, false)]
+ [InlineData(12, false, false)]
- public void GuidRouteConstraint_ApplyConstraint(object parameterValue, bool parseBeforeTest, bool expected)
+ public void GuidRouteConstraint_ApplyConstraint(object parameterValue, bool parseBeforeTest, bool expected)
+ {
+ // Arrange
+ if (parseBeforeTest)
{
- // Arrange
- if (parseBeforeTest)
- {
- parameterValue = Guid.Parse(parameterValue.ToString());
- }
+ parameterValue = Guid.Parse(parameterValue.ToString());
+ }
- var constraint = new GuidRouteConstraint();
+ var constraint = new GuidRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/HttpMethodRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/HttpMethodRouteConstraintTests.cs
index ba344419af..6ed74ea9f4 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/HttpMethodRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/HttpMethodRouteConstraintTests.cs
@@ -5,90 +5,89 @@ using Microsoft.AspNetCore.Http;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+public class HttpMethodRouteConstraintTests
{
- public class HttpMethodRouteConstraintTests
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("PosT")]
+ public void HttpMethodRouteConstraint_IncomingRequest_AcceptsAllowedMethods(string httpMethod)
+ {
+ // Arrange
+ var constraint = new HttpMethodRouteConstraint("GET", "post");
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Method = httpMethod;
+ var route = Mock.Of<IRouter>();
+
+ var values = new RouteValueDictionary(new { });
+
+ // Act
+ var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Theory]
+ [InlineData("OPTIONS")]
+ [InlineData("SomeRandomThing")]
+ public void HttpMethodRouteConstraint_IncomingRequest_RejectsOtherMethods(string httpMethod)
+ {
+ // Arrange
+ var constraint = new HttpMethodRouteConstraint("GET", "post");
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Method = httpMethod;
+ var route = Mock.Of<IRouter>();
+
+ var values = new RouteValueDictionary(new { });
+
+ // Act
+ var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Theory]
+ [InlineData("GET")]
+ [InlineData("PosT")]
+ public void HttpMethodRouteConstraint_UrlGeneration_AcceptsAllowedMethods(string httpMethod)
{
- [Theory]
- [InlineData("GET")]
- [InlineData("PosT")]
- public void HttpMethodRouteConstraint_IncomingRequest_AcceptsAllowedMethods(string httpMethod)
- {
- // Arrange
- var constraint = new HttpMethodRouteConstraint("GET", "post");
-
- var httpContext = new DefaultHttpContext();
- httpContext.Request.Method = httpMethod;
- var route = Mock.Of<IRouter>();
-
- var values = new RouteValueDictionary(new { });
-
- // Act
- var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.IncomingRequest);
-
- // Assert
- Assert.True(result);
- }
-
- [Theory]
- [InlineData("OPTIONS")]
- [InlineData("SomeRandomThing")]
- public void HttpMethodRouteConstraint_IncomingRequest_RejectsOtherMethods(string httpMethod)
- {
- // Arrange
- var constraint = new HttpMethodRouteConstraint("GET", "post");
-
- var httpContext = new DefaultHttpContext();
- httpContext.Request.Method = httpMethod;
- var route = Mock.Of<IRouter>();
-
- var values = new RouteValueDictionary(new { });
-
- // Act
- var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(result);
- }
-
- [Theory]
- [InlineData("GET")]
- [InlineData("PosT")]
- public void HttpMethodRouteConstraint_UrlGeneration_AcceptsAllowedMethods(string httpMethod)
- {
- // Arrange
- var constraint = new HttpMethodRouteConstraint("GET", "post");
-
- var httpContext = new DefaultHttpContext();
- var route = Mock.Of<IRouter>();
-
- var values = new RouteValueDictionary(new { httpMethod = httpMethod });
-
- // Act
- var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.UrlGeneration);
-
- // Assert
- Assert.True(result);
- }
-
- [Theory]
- [InlineData("OPTIONS")]
- [InlineData("SomeRandomThing")]
- public void HttpMethodRouteConstraint_UrlGeneration_RejectsOtherMethods(string httpMethod)
- {
- // Arrange
- var constraint = new HttpMethodRouteConstraint("GET", "post");
-
- var httpContext = new DefaultHttpContext();
- var route = Mock.Of<IRouter>();
-
- var values = new RouteValueDictionary(new { httpMethod = httpMethod });
-
- // Act
- var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.UrlGeneration);
-
- // Assert
- Assert.False(result);
- }
+ // Arrange
+ var constraint = new HttpMethodRouteConstraint("GET", "post");
+
+ var httpContext = new DefaultHttpContext();
+ var route = Mock.Of<IRouter>();
+
+ var values = new RouteValueDictionary(new { httpMethod = httpMethod });
+
+ // Act
+ var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.UrlGeneration);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Theory]
+ [InlineData("OPTIONS")]
+ [InlineData("SomeRandomThing")]
+ public void HttpMethodRouteConstraint_UrlGeneration_RejectsOtherMethods(string httpMethod)
+ {
+ // Arrange
+ var constraint = new HttpMethodRouteConstraint("GET", "post");
+
+ var httpContext = new DefaultHttpContext();
+ var route = Mock.Of<IRouter>();
+
+ var values = new RouteValueDictionary(new { httpMethod = httpMethod });
+
+ // Act
+ var result = constraint.Match(httpContext, route, "httpMethod", values, RouteDirection.UrlGeneration);
+
+ // Assert
+ Assert.False(result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/IntRouteConstraintsTests.cs b/src/Http/Routing/test/UnitTests/Constraints/IntRouteConstraintsTests.cs
index 26e196c736..d69bc1d793 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/IntRouteConstraintsTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/IntRouteConstraintsTests.cs
@@ -4,26 +4,25 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class IntRouteConstraintsTests
{
- public class IntRouteConstraintsTests
+ [Theory]
+ [InlineData(42, true)]
+ [InlineData("42", true)]
+ [InlineData(3.14, false)]
+ [InlineData("43.567", false)]
+ [InlineData("42a", false)]
+ public void IntRouteConstraint_Match_AppliesConstraint(object parameterValue, bool expected)
{
- [Theory]
- [InlineData(42, true)]
- [InlineData("42", true)]
- [InlineData(3.14, false)]
- [InlineData("43.567", false)]
- [InlineData("42a", false)]
- public void IntRouteConstraint_Match_AppliesConstraint(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new IntRouteConstraint();
+ // Arrange
+ var constraint = new IntRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/LengthRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/LengthRouteConstraintTests.cs
index c5311a21fe..df8eb6d2dc 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/LengthRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/LengthRouteConstraintTests.cs
@@ -5,99 +5,98 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class LengthRouteConstraintTests
{
- public class LengthRouteConstraintTests
+ [Theory]
+ [InlineData(3, "123", true)]
+ [InlineData(3, "1234", false)]
+ [InlineData(0, "", true)]
+ public void LengthRouteConstraint_ExactLength_Tests(int length, string parameterValue, bool expected)
+ {
+ // Arrange
+ var constraint = new LengthRouteConstraint(length);
+
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Theory]
+ [InlineData(3, 5, "12", false)]
+ [InlineData(3, 5, "123", true)]
+ [InlineData(3, 5, "1234", true)]
+ [InlineData(3, 5, "12345", true)]
+ [InlineData(3, 5, "123456", false)]
+ public void LengthRouteConstraint_Range_Tests(int min, int max, string parameterValue, bool expected)
+ {
+ // Arrange
+ var constraint = new LengthRouteConstraint(min, max);
+
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void LengthRouteConstraint_SettingLengthLessThanZero_Throws()
+ {
+ // Arrange
+ var expectedMessage = "Value must be greater than or equal to 0.";
+
+ // Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new LengthRouteConstraint(-1),
+ "length",
+ expectedMessage,
+ -1);
+ }
+
+ [Fact]
+ public void LengthRouteConstraint_SettingMinLengthLessThanZero_Throws()
+ {
+ // Arrange
+ var expectedMessage = "Value must be greater than or equal to 0.";
+
+ // Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new LengthRouteConstraint(-1, 3),
+ "minLength",
+ expectedMessage,
+ -1);
+ }
+
+ [Fact]
+ public void LengthRouteConstraint_SettingMaxLengthLessThanZero_Throws()
+ {
+ // Arrange
+ var expectedMessage = "Value must be greater than or equal to 0.";
+
+ // Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new LengthRouteConstraint(0, -1),
+ "maxLength",
+ expectedMessage,
+ -1);
+ }
+
+ [Fact]
+ public void LengthRouteConstraint_MinGreaterThanMax_Throws()
{
- [Theory]
- [InlineData(3, "123", true)]
- [InlineData(3, "1234", false)]
- [InlineData(0, "", true)]
- public void LengthRouteConstraint_ExactLength_Tests(int length, string parameterValue, bool expected)
- {
- // Arrange
- var constraint = new LengthRouteConstraint(length);
-
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Theory]
- [InlineData(3, 5, "12", false)]
- [InlineData(3, 5, "123", true)]
- [InlineData(3, 5, "1234", true)]
- [InlineData(3, 5, "12345", true)]
- [InlineData(3, 5, "123456", false)]
- public void LengthRouteConstraint_Range_Tests(int min, int max, string parameterValue, bool expected)
- {
- // Arrange
- var constraint = new LengthRouteConstraint(min, max);
-
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void LengthRouteConstraint_SettingLengthLessThanZero_Throws()
- {
- // Arrange
- var expectedMessage = "Value must be greater than or equal to 0.";
-
- // Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new LengthRouteConstraint(-1),
- "length",
- expectedMessage,
- -1);
- }
-
- [Fact]
- public void LengthRouteConstraint_SettingMinLengthLessThanZero_Throws()
- {
- // Arrange
- var expectedMessage = "Value must be greater than or equal to 0.";
-
- // Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new LengthRouteConstraint(-1, 3),
- "minLength",
- expectedMessage,
- -1);
- }
-
- [Fact]
- public void LengthRouteConstraint_SettingMaxLengthLessThanZero_Throws()
- {
- // Arrange
- var expectedMessage = "Value must be greater than or equal to 0.";
-
- // Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new LengthRouteConstraint(0, -1),
- "maxLength",
- expectedMessage,
- -1);
- }
-
- [Fact]
- public void LengthRouteConstraint_MinGreaterThanMax_Throws()
- {
- // Arrange
- var expectedMessage = "The value for argument 'minLength' should be less than or equal to the " +
- "value for the argument 'maxLength'.";
-
- // Arrange Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new LengthRouteConstraint(3, 2),
- "minLength",
- expectedMessage,
- 3);
- }
+ // Arrange
+ var expectedMessage = "The value for argument 'minLength' should be less than or equal to the " +
+ "value for the argument 'maxLength'.";
+
+ // Arrange Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new LengthRouteConstraint(3, 2),
+ "minLength",
+ expectedMessage,
+ 3);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/LongRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/LongRouteConstraintTests.cs
index ecb2a661cd..ba2c29130c 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/LongRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/LongRouteConstraintTests.cs
@@ -4,28 +4,27 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class LongRouteConstraintTests
{
- public class LongRouteConstraintTests
+ [Theory]
+ [InlineData(42, true)]
+ [InlineData(42L, true)]
+ [InlineData("42", true)]
+ [InlineData("9223372036854775807", true)]
+ [InlineData(3.14, false)]
+ [InlineData("43.567", false)]
+ [InlineData("42a", false)]
+ public void LongRouteConstraintTest(object parameterValue, bool expected)
{
- [Theory]
- [InlineData(42, true)]
- [InlineData(42L, true)]
- [InlineData("42", true)]
- [InlineData("9223372036854775807", true)]
- [InlineData(3.14, false)]
- [InlineData("43.567", false)]
- [InlineData("42a", false)]
- public void LongRouteConstraintTest(object parameterValue, bool expected)
- {
- // Arrange
- var constraint = new LongRouteConstraint();
+ // Arrange
+ var constraint = new LongRouteConstraint();
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/MaxLengthRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/MaxLengthRouteConstraintTests.cs
index c6deec8d75..684c353915 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/MaxLengthRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/MaxLengthRouteConstraintTests.cs
@@ -5,39 +5,38 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class MaxLengthRouteConstraintTests
{
- public class MaxLengthRouteConstraintTests
+ [Theory]
+ [InlineData(3, "", true)]
+ [InlineData(3, "12", true)]
+ [InlineData(3, "123", true)]
+ [InlineData(3, "1234", false)]
+ public void MaxLengthRouteConstraint_ApplyConstraint(int min, string parameterValue, bool expected)
{
- [Theory]
- [InlineData(3, "", true)]
- [InlineData(3, "12", true)]
- [InlineData(3, "123", true)]
- [InlineData(3, "1234", false)]
- public void MaxLengthRouteConstraint_ApplyConstraint(int min, string parameterValue, bool expected)
- {
- // Arrange
- var constraint = new MaxLengthRouteConstraint(min);
+ // Arrange
+ var constraint = new MaxLengthRouteConstraint(min);
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public void MaxLengthRouteConstraint_SettingMaxLengthLessThanZero_Throws()
- {
- // Arrange
- var expectedMessage = "Value must be greater than or equal to 0.";
+ [Fact]
+ public void MaxLengthRouteConstraint_SettingMaxLengthLessThanZero_Throws()
+ {
+ // Arrange
+ var expectedMessage = "Value must be greater than or equal to 0.";
- // Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new MaxLengthRouteConstraint(-1),
- "maxLength",
- expectedMessage,
- -1);
- }
+ // Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new MaxLengthRouteConstraint(-1),
+ "maxLength",
+ expectedMessage,
+ -1);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/MaxRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/MaxRouteConstraintTests.cs
index 475eaa94e0..a9424ebdd7 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/MaxRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/MaxRouteConstraintTests.cs
@@ -4,24 +4,23 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class MaxRouteConstraintTests
{
- public class MaxRouteConstraintTests
+ [Theory]
+ [InlineData(3, 2, true)]
+ [InlineData(3, 3, true)]
+ [InlineData(3, 4, false)]
+ public void MaxRouteConstraint_ApplyConstraint(long max, int parameterValue, bool expected)
{
- [Theory]
- [InlineData(3, 2, true)]
- [InlineData(3, 3, true)]
- [InlineData(3, 4, false)]
- public void MaxRouteConstraint_ApplyConstraint(long max, int parameterValue, bool expected)
- {
- // Arrange
- var constraint = new MaxRouteConstraint(max);
+ // Arrange
+ var constraint = new MaxRouteConstraint(max);
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/MinLengthRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/MinLengthRouteConstraintTests.cs
index 92a411d5e3..181d8a7eae 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/MinLengthRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/MinLengthRouteConstraintTests.cs
@@ -5,39 +5,38 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class MinLengthRouteConstraintTests
{
- public class MinLengthRouteConstraintTests
+ [Theory]
+ [InlineData(3, "1234", true)]
+ [InlineData(3, "123", true)]
+ [InlineData(3, "12", false)]
+ [InlineData(3, "", false)]
+ public void MinLengthRouteConstraint_ApplyConstraint(int min, string parameterValue, bool expected)
{
- [Theory]
- [InlineData(3, "1234", true)]
- [InlineData(3, "123", true)]
- [InlineData(3, "12", false)]
- [InlineData(3, "", false)]
- public void MinLengthRouteConstraint_ApplyConstraint(int min, string parameterValue, bool expected)
- {
- // Arrange
- var constraint = new MinLengthRouteConstraint(min);
+ // Arrange
+ var constraint = new MinLengthRouteConstraint(min);
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public void MinLengthRouteConstraint_SettingMinLengthLessThanZero_Throws()
- {
- // Arrange
- var expectedMessage = "Value must be greater than or equal to 0.";
+ [Fact]
+ public void MinLengthRouteConstraint_SettingMinLengthLessThanZero_Throws()
+ {
+ // Arrange
+ var expectedMessage = "Value must be greater than or equal to 0.";
- // Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new MinLengthRouteConstraint(-1),
- "minLength",
- expectedMessage,
- -1);
- }
+ // Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new MinLengthRouteConstraint(-1),
+ "minLength",
+ expectedMessage,
+ -1);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/MinRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/MinRouteConstraintTests.cs
index 772ddc3669..b008ef8814 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/MinRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/MinRouteConstraintTests.cs
@@ -4,24 +4,23 @@
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class MinRouteConstraintTests
{
- public class MinRouteConstraintTests
+ [Theory]
+ [InlineData(3, 4, true)]
+ [InlineData(3, 3, true)]
+ [InlineData(3, 2, false)]
+ public void MinRouteConstraint_ApplyConstraint(long min, int parameterValue, bool expected)
{
- [Theory]
- [InlineData(3, 4, true)]
- [InlineData(3, 3, true)]
- [InlineData(3, 2, false)]
- public void MinRouteConstraint_ApplyConstraint(long min, int parameterValue, bool expected)
- {
- // Arrange
- var constraint = new MinRouteConstraint(min);
+ // Arrange
+ var constraint = new MinRouteConstraint(min);
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/NonFIleNameRouteConstraintTest.cs b/src/Http/Routing/test/UnitTests/Constraints/NonFIleNameRouteConstraintTest.cs
index cf461fb700..f5bbb5a29b 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/NonFIleNameRouteConstraintTest.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/NonFIleNameRouteConstraintTest.cs
@@ -3,57 +3,56 @@
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+public class NonFileNameRouteConstraintTest
{
- public class NonFileNameRouteConstraintTest
+ [Theory]
+ [MemberData(nameof(FileNameRouteConstraintTest.FileNameData), MemberType = typeof(FileNameRouteConstraintTest))]
+ public void Match_RouteValue_IsNotNonFileName(object value)
+ {
+ // Arrange
+ var constraint = new NonFileNameRouteConstraint();
+
+ var values = new RouteValueDictionary();
+ values.Add("path", value);
+
+ // Act
+ var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Theory]
+ [MemberData(nameof(FileNameRouteConstraintTest.NonFileNameData), MemberType = typeof(FileNameRouteConstraintTest))]
+ public void Match_RouteValue_IsNonFileName(object value)
+ {
+ // Arrange
+ var constraint = new NonFileNameRouteConstraint();
+
+ var values = new RouteValueDictionary();
+ values.Add("path", value);
+
+ // Act
+ var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void Match_MissingValue_IsNotFileName()
{
- [Theory]
- [MemberData(nameof(FileNameRouteConstraintTest.FileNameData), MemberType = typeof(FileNameRouteConstraintTest))]
- public void Match_RouteValue_IsNotNonFileName(object value)
- {
- // Arrange
- var constraint = new NonFileNameRouteConstraint();
-
- var values = new RouteValueDictionary();
- values.Add("path", value);
-
- // Act
- var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(result);
- }
-
- [Theory]
- [MemberData(nameof(FileNameRouteConstraintTest.NonFileNameData), MemberType = typeof(FileNameRouteConstraintTest))]
- public void Match_RouteValue_IsNonFileName(object value)
- {
- // Arrange
- var constraint = new NonFileNameRouteConstraint();
-
- var values = new RouteValueDictionary();
- values.Add("path", value);
-
- // Act
- var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
-
- // Assert
- Assert.True(result);
- }
-
- [Fact]
- public void Match_MissingValue_IsNotFileName()
- {
- // Arrange
- var constraint = new NonFileNameRouteConstraint();
-
- var values = new RouteValueDictionary();
-
- // Act
- var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
-
- // Assert
- Assert.True(result);
- }
+ // Arrange
+ var constraint = new NonFileNameRouteConstraint();
+
+ var values = new RouteValueDictionary();
+
+ // Act
+ var result = constraint.Match(httpContext: null, route: null, "path", values, RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.True(result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/RangeRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/RangeRouteConstraintTests.cs
index 137b70324a..7d0f13f07d 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/RangeRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/RangeRouteConstraintTests.cs
@@ -5,44 +5,43 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class RangeRouteConstraintTests
{
- public class RangeRouteConstraintTests
+ [Theory]
+ [InlineData(long.MinValue, long.MaxValue, 2, true)]
+ [InlineData(3, 5, 3, true)]
+ [InlineData(3, 5, 4, true)]
+ [InlineData(3, 5, 5, true)]
+ [InlineData(3, 5, 6, false)]
+ [InlineData(3, 5, 2, false)]
+ [InlineData(3, 3, 2, false)]
+ [InlineData(3, 3, 3, true)]
+ public void RangeRouteConstraintTest_ValidValue_ApplyConstraint(long min, long max, int parameterValue, bool expected)
{
- [Theory]
- [InlineData(long.MinValue, long.MaxValue, 2, true)]
- [InlineData(3, 5, 3, true)]
- [InlineData(3, 5, 4, true)]
- [InlineData(3, 5, 5, true)]
- [InlineData(3, 5, 6, false)]
- [InlineData(3, 5, 2, false)]
- [InlineData(3, 3, 2, false)]
- [InlineData(3, 3, 3, true)]
- public void RangeRouteConstraintTest_ValidValue_ApplyConstraint(long min, long max, int parameterValue, bool expected)
- {
- // Arrange
- var constraint = new RangeRouteConstraint(min, max);
+ // Arrange
+ var constraint = new RangeRouteConstraint(min, max);
- // Act
- var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
+ // Act
+ var actual = ConstraintsTestHelper.TestConstraint(constraint, parameterValue);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public void RangeRouteConstraint_MinGreaterThanMax_Throws()
- {
- // Arrange
- var expectedMessage = "The value for argument 'min' should be less than or equal to the value for the " +
- "argument 'max'.";
+ [Fact]
+ public void RangeRouteConstraint_MinGreaterThanMax_Throws()
+ {
+ // Arrange
+ var expectedMessage = "The value for argument 'min' should be less than or equal to the value for the " +
+ "argument 'max'.";
- // Act & Assert
- ExceptionAssert.ThrowsArgumentOutOfRange(
- () => new RangeRouteConstraint(3, 2),
- "min",
- expectedMessage,
- 3L);
- }
+ // Act & Assert
+ ExceptionAssert.ThrowsArgumentOutOfRange(
+ () => new RangeRouteConstraint(3, 2),
+ "min",
+ expectedMessage,
+ 3L);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/RegexInlineRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/RegexInlineRouteConstraintTests.cs
index 9eca3f91c4..c5c50f55eb 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/RegexInlineRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/RegexInlineRouteConstraintTests.cs
@@ -7,45 +7,75 @@ using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class RegexInlineRouteConstraintTests
{
- public class RegexInlineRouteConstraintTests
+ [Theory]
+ [InlineData("abc", "abc", true)] // simple match
+ [InlineData("Abc", "abc", true)] // case insensitive match
+ [InlineData("Abc ", "abc", true)] // Extra space on input match (because we don't add ^({0})$
+ [InlineData("Abcd", "abc", true)] // Extra char
+ [InlineData("^Abcd", "abc", true)] // Extra special char
+ [InlineData("Abc", " abc", false)] // Missing char
+ public void RegexInlineConstraintBuildRegexVerbatimFromInput(
+ string routeValue,
+ string constraintValue,
+ bool shouldMatch)
{
- [Theory]
- [InlineData("abc", "abc", true)] // simple match
- [InlineData("Abc", "abc", true)] // case insensitive match
- [InlineData("Abc ", "abc", true)] // Extra space on input match (because we don't add ^({0})$
- [InlineData("Abcd", "abc", true)] // Extra char
- [InlineData("^Abcd", "abc", true)] // Extra special char
- [InlineData("Abc", " abc", false)] // Missing char
- public void RegexInlineConstraintBuildRegexVerbatimFromInput(
- string routeValue,
- string constraintValue,
- bool shouldMatch)
- {
- // Arrange
- var constraint = new RegexInlineRouteConstraint(constraintValue);
- var values = new RouteValueDictionary(new { controller = routeValue });
+ // Arrange
+ var constraint = new RegexInlineRouteConstraint(constraintValue);
+ var values = new RouteValueDictionary(new { controller = routeValue });
- // Act
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
+ // Act
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
- // Assert
- Assert.Equal(shouldMatch, match);
- }
+ // Assert
+ Assert.Equal(shouldMatch, match);
+ }
+
+ [Fact]
+ public void RegexInlineConstraint_FailsIfKeyIsNotFoundInRouteValues()
+ {
+ // Arrange
+ var constraint = new RegexInlineRouteConstraint("^abc$");
+ var values = new RouteValueDictionary(new { action = "abc" });
- [Fact]
- public void RegexInlineConstraint_FailsIfKeyIsNotFoundInRouteValues()
+ // Act
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(match);
+ }
+
+ [Theory]
+ [InlineData("tr-TR")]
+ [InlineData("en-US")]
+ public void RegexInlineConstraint_IsCultureInsensitive(string culture)
+ {
+ if (TestPlatformHelper.IsMono)
{
- // Arrange
- var constraint = new RegexInlineRouteConstraint("^abc$");
- var values = new RouteValueDictionary(new { action = "abc" });
+ // The Regex in Mono returns true when matching the Turkish I for the a-z range which causes the test
+ // to fail. Tracked via #100.
+ return;
+ }
+
+ // Arrange
+ var constraint = new RegexInlineRouteConstraint("^([a-z]+)$");
+ var values = new RouteValueDictionary(new { controller = "\u0130" }); // Turkish upper-case dotted I
+ using (new CultureReplacer(culture))
+ {
// Act
var match = constraint.Match(
new DefaultHttpContext(),
@@ -57,36 +87,5 @@ namespace Microsoft.AspNetCore.Routing.Tests
// Assert
Assert.False(match);
}
-
- [Theory]
- [InlineData("tr-TR")]
- [InlineData("en-US")]
- public void RegexInlineConstraint_IsCultureInsensitive(string culture)
- {
- if (TestPlatformHelper.IsMono)
- {
- // The Regex in Mono returns true when matching the Turkish I for the a-z range which causes the test
- // to fail. Tracked via #100.
- return;
- }
-
- // Arrange
- var constraint = new RegexInlineRouteConstraint("^([a-z]+)$");
- var values = new RouteValueDictionary(new { controller = "\u0130" }); // Turkish upper-case dotted I
-
- using (new CultureReplacer(culture))
- {
- // Act
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(match);
- }
- }
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/RegexRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/RegexRouteConstraintTests.cs
index 3010ce7bdb..5de70c745a 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/RegexRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/RegexRouteConstraintTests.cs
@@ -8,89 +8,119 @@ using Microsoft.AspNetCore.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class RegexRouteConstraintTests
{
- public class RegexRouteConstraintTests
+ [Theory]
+ [InlineData("abc", "abc", true)] // simple match
+ [InlineData("Abc", "abc", true)] // case insensitive match
+ [InlineData("Abc ", "abc", true)] // Extra space on input match (because we don't add ^({0})$
+ [InlineData("Abcd", "abc", true)] // Extra char
+ [InlineData("^Abcd", "abc", true)] // Extra special char
+ [InlineData("Abc", " abc", false)] // Missing char
+ [InlineData("123-456-2334", @"^\d{3}-\d{3}-\d{4}$", true)] // ssn
+ [InlineData(@"12/4/2013", @"^\d{1,2}\/\d{1,2}\/\d{4}$", true)] // date
+ [InlineData(@"abc@def.com", @"^\w+[\w\.]*\@\w+((-\w+)|(\w*))\.[a-z]{2,3}$", true)] // email
+ public void RegexConstraintBuildRegexVerbatimFromInput(
+ string routeValue,
+ string constraintValue,
+ bool shouldMatch)
{
- [Theory]
- [InlineData("abc", "abc", true)] // simple match
- [InlineData("Abc", "abc", true)] // case insensitive match
- [InlineData("Abc ", "abc", true)] // Extra space on input match (because we don't add ^({0})$
- [InlineData("Abcd", "abc", true)] // Extra char
- [InlineData("^Abcd", "abc", true)] // Extra special char
- [InlineData("Abc", " abc", false)] // Missing char
- [InlineData("123-456-2334", @"^\d{3}-\d{3}-\d{4}$", true)] // ssn
- [InlineData(@"12/4/2013", @"^\d{1,2}\/\d{1,2}\/\d{4}$", true)] // date
- [InlineData(@"abc@def.com", @"^\w+[\w\.]*\@\w+((-\w+)|(\w*))\.[a-z]{2,3}$", true)] // email
- public void RegexConstraintBuildRegexVerbatimFromInput(
- string routeValue,
- string constraintValue,
- bool shouldMatch)
- {
- // Arrange
- var constraint = new RegexRouteConstraint(constraintValue);
- var values = new RouteValueDictionary(new { controller = routeValue });
-
- // Act
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.Equal(shouldMatch, match);
- }
+ // Arrange
+ var constraint = new RegexRouteConstraint(constraintValue);
+ var values = new RouteValueDictionary(new { controller = routeValue });
+
+ // Act
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.Equal(shouldMatch, match);
+ }
- [Fact]
- public void RegexConstraint_TakesRegexAsInput_SimpleMatch()
- {
- // Arrange
- var constraint = new RegexRouteConstraint(new Regex("^abc$"));
- var values = new RouteValueDictionary(new { controller = "abc" });
+ [Fact]
+ public void RegexConstraint_TakesRegexAsInput_SimpleMatch()
+ {
+ // Arrange
+ var constraint = new RegexRouteConstraint(new Regex("^abc$"));
+ var values = new RouteValueDictionary(new { controller = "abc" });
+
+ // Act
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.True(match);
+ }
- // Act
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
+ [Fact]
+ public void RegexConstraintConstructedWithRegex_SimpleFailedMatch()
+ {
+ // Arrange
+ var constraint = new RegexRouteConstraint(new Regex("^abc$"));
+ var values = new RouteValueDictionary(new { controller = "Abc" });
+
+ // Act
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(match);
+ }
- // Assert
- Assert.True(match);
- }
+ [Fact]
+ public void RegexConstraintFailsIfKeyIsNotFoundInRouteValues()
+ {
+ // Arrange
+ var constraint = new RegexRouteConstraint(new Regex("^abc$"));
+ var values = new RouteValueDictionary(new { action = "abc" });
+
+ // Act
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(match);
+ }
- [Fact]
- public void RegexConstraintConstructedWithRegex_SimpleFailedMatch()
+ [Theory]
+ [InlineData("tr-TR")]
+ [InlineData("en-US")]
+ public void RegexConstraintIsCultureInsensitiveWhenConstructedWithString(string culture)
+ {
+ if (TestPlatformHelper.IsMono)
{
- // Arrange
- var constraint = new RegexRouteConstraint(new Regex("^abc$"));
- var values = new RouteValueDictionary(new { controller = "Abc" });
-
- // Act
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(match);
+ // The Regex in Mono returns true when matching the Turkish I for the a-z range which causes the test
+ // to fail. Tracked via #100.
+ return;
}
- [Fact]
- public void RegexConstraintFailsIfKeyIsNotFoundInRouteValues()
- {
- // Arrange
- var constraint = new RegexRouteConstraint(new Regex("^abc$"));
- var values = new RouteValueDictionary(new { action = "abc" });
+ // Arrange
+ var constraint = new RegexRouteConstraint("^([a-z]+)$");
+ var values = new RouteValueDictionary(new { controller = "\u0130" }); // Turkish upper-case dotted I
+ using (new CultureReplacer(culture))
+ {
// Act
var match = constraint.Match(
- new DefaultHttpContext(),
+ httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeKey: "controller",
values: values,
@@ -99,36 +129,5 @@ namespace Microsoft.AspNetCore.Routing.Tests
// Assert
Assert.False(match);
}
-
- [Theory]
- [InlineData("tr-TR")]
- [InlineData("en-US")]
- public void RegexConstraintIsCultureInsensitiveWhenConstructedWithString(string culture)
- {
- if (TestPlatformHelper.IsMono)
- {
- // The Regex in Mono returns true when matching the Turkish I for the a-z range which causes the test
- // to fail. Tracked via #100.
- return;
- }
-
- // Arrange
- var constraint = new RegexRouteConstraint("^([a-z]+)$");
- var values = new RouteValueDictionary(new { controller = "\u0130" }); // Turkish upper-case dotted I
-
- using (new CultureReplacer(culture))
- {
- // Act
- var match = constraint.Match(
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(match);
- }
- }
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/RequiredRouteConstraintTests.cs b/src/Http/Routing/test/UnitTests/Constraints/RequiredRouteConstraintTests.cs
index 5d01d51592..d54649b7d0 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/RequiredRouteConstraintTests.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/RequiredRouteConstraintTests.cs
@@ -6,88 +6,87 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class RequiredRouteConstraintTests
{
- public class RequiredRouteConstraintTests
+ [Theory]
+ [InlineData(RouteDirection.IncomingRequest)]
+ [InlineData(RouteDirection.UrlGeneration)]
+ public void RequiredRouteConstraint_NoValue(RouteDirection direction)
{
- [Theory]
- [InlineData(RouteDirection.IncomingRequest)]
- [InlineData(RouteDirection.UrlGeneration)]
- public void RequiredRouteConstraint_NoValue(RouteDirection direction)
- {
- // Arrange
- var constraint = new RequiredRouteConstraint();
+ // Arrange
+ var constraint = new RequiredRouteConstraint();
- // Act
- var result = constraint.Match(
- new DefaultHttpContext(),
- Mock.Of<IRouter>(),
- "area",
- new RouteValueDictionary(new { controller = "Home", action = "Index" }),
- direction);
+ // Act
+ var result = constraint.Match(
+ new DefaultHttpContext(),
+ Mock.Of<IRouter>(),
+ "area",
+ new RouteValueDictionary(new { controller = "Home", action = "Index" }),
+ direction);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Theory]
- [InlineData(RouteDirection.IncomingRequest)]
- [InlineData(RouteDirection.UrlGeneration)]
- public void RequiredRouteConstraint_Null(RouteDirection direction)
- {
- // Arrange
- var constraint = new RequiredRouteConstraint();
+ [Theory]
+ [InlineData(RouteDirection.IncomingRequest)]
+ [InlineData(RouteDirection.UrlGeneration)]
+ public void RequiredRouteConstraint_Null(RouteDirection direction)
+ {
+ // Arrange
+ var constraint = new RequiredRouteConstraint();
- // Act
- var result = constraint.Match(
- new DefaultHttpContext(),
- Mock.Of<IRouter>(),
- "area",
- new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null }),
- direction);
+ // Act
+ var result = constraint.Match(
+ new DefaultHttpContext(),
+ Mock.Of<IRouter>(),
+ "area",
+ new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null }),
+ direction);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Theory]
- [InlineData(RouteDirection.IncomingRequest)]
- [InlineData(RouteDirection.UrlGeneration)]
- public void RequiredRouteConstraint_EmptyString(RouteDirection direction)
- {
- // Arrange
- var constraint = new RequiredRouteConstraint();
+ [Theory]
+ [InlineData(RouteDirection.IncomingRequest)]
+ [InlineData(RouteDirection.UrlGeneration)]
+ public void RequiredRouteConstraint_EmptyString(RouteDirection direction)
+ {
+ // Arrange
+ var constraint = new RequiredRouteConstraint();
- // Act
- var result = constraint.Match(
- new DefaultHttpContext(),
- Mock.Of<IRouter>(),
- "area",
- new RouteValueDictionary(new { controller = "Home", action = "Index", area = string.Empty}),
- direction);
+ // Act
+ var result = constraint.Match(
+ new DefaultHttpContext(),
+ Mock.Of<IRouter>(),
+ "area",
+ new RouteValueDictionary(new { controller = "Home", action = "Index", area = string.Empty }),
+ direction);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Theory]
- [InlineData(RouteDirection.IncomingRequest)]
- [InlineData(RouteDirection.UrlGeneration)]
- public void RequiredRouteConstraint_WithValue(RouteDirection direction)
- {
- // Arrange
- var constraint = new RequiredRouteConstraint();
+ [Theory]
+ [InlineData(RouteDirection.IncomingRequest)]
+ [InlineData(RouteDirection.UrlGeneration)]
+ public void RequiredRouteConstraint_WithValue(RouteDirection direction)
+ {
+ // Arrange
+ var constraint = new RequiredRouteConstraint();
- // Act
- var result = constraint.Match(
- new DefaultHttpContext(),
- Mock.Of<IRouter>(),
- "area",
- new RouteValueDictionary(new { controller = "Home", action = "Index", area = "Store" }),
- direction);
+ // Act
+ var result = constraint.Match(
+ new DefaultHttpContext(),
+ Mock.Of<IRouter>(),
+ "area",
+ new RouteValueDictionary(new { controller = "Home", action = "Index", area = "Store" }),
+ direction);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Constraints/StringRouteConstraintTest.cs b/src/Http/Routing/test/UnitTests/Constraints/StringRouteConstraintTest.cs
index 7ec1294219..560264e4bc 100644
--- a/src/Http/Routing/test/UnitTests/Constraints/StringRouteConstraintTest.cs
+++ b/src/Http/Routing/test/UnitTests/Constraints/StringRouteConstraintTest.cs
@@ -6,152 +6,151 @@ using Microsoft.AspNetCore.Routing.Constraints;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Constraints
+namespace Microsoft.AspNetCore.Routing.Constraints;
+
+public class StringRouteConstraintTest
{
- public class StringRouteConstraintTest
+ [Fact]
+ public void StringRouteConstraintSimpleTrueWithRouteDirectionIncomingRequestTest()
+ {
+ // Arrange
+ var constraint = new StringRouteConstraint("home");
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = "home" });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.True(match);
+ }
+
+ [Fact]
+ public void StringRouteConstraintSimpleTrueWithRouteDirectionUrlGenerationTest()
+ {
+ // Arrange
+ var constraint = new StringRouteConstraint("home");
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = "home" });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.UrlGeneration);
+
+ // Assert
+ Assert.True(match);
+ }
+
+ [Fact]
+ public void StringRouteConstraintSimpleFalseWithRouteDirectionIncomingRequestTest()
+ {
+ // Arrange
+ var constraint = new StringRouteConstraint("admin");
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = "home" });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(match);
+ }
+
+ [Fact]
+ public void StringRouteConstraintSimpleFalseWithRouteDirectionUrlGenerationTest()
+ {
+ // Arrange
+ var constraint = new StringRouteConstraint("admin");
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = "home" });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.UrlGeneration);
+
+ // Assert
+ Assert.False(match);
+ }
+
+ [Fact]
+ public void StringRouteConstraintKeyNotFoundWithRouteDirectionIncomingRequestTest()
+ {
+ // Arrange
+ var constraint = new StringRouteConstraint("admin");
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = "admin" });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "action",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.False(match);
+ }
+
+ [Fact]
+ public void StringRouteConstraintKeyNotFoundWithRouteDirectionUrlGenerationTest()
+ {
+ // Arrange
+ var constraint = new StringRouteConstraint("admin");
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = "admin" });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "action",
+ values: values,
+ routeDirection: RouteDirection.UrlGeneration);
+
+ // Assert
+ Assert.False(match);
+ }
+
+ [Theory]
+ [InlineData("User", "uSer", true)]
+ [InlineData("User.Admin", "User.Admin", true)]
+ [InlineData(@"User\Admin", "User\\Admin", true)]
+ [InlineData(null, "user", false)]
+ public void StringRouteConstraintEscapingCaseSensitiveAndRouteNullTest(string routeValue, string constraintValue, bool expected)
{
- [Fact]
- public void StringRouteConstraintSimpleTrueWithRouteDirectionIncomingRequestTest()
- {
- // Arrange
- var constraint = new StringRouteConstraint("home");
-
- // Act
- var values = new RouteValueDictionary(new { controller = "home" });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.True(match);
- }
-
- [Fact]
- public void StringRouteConstraintSimpleTrueWithRouteDirectionUrlGenerationTest()
- {
- // Arrange
- var constraint = new StringRouteConstraint("home");
-
- // Act
- var values = new RouteValueDictionary(new { controller = "home" });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.UrlGeneration);
-
- // Assert
- Assert.True(match);
- }
-
- [Fact]
- public void StringRouteConstraintSimpleFalseWithRouteDirectionIncomingRequestTest()
- {
- // Arrange
- var constraint = new StringRouteConstraint("admin");
-
- // Act
- var values = new RouteValueDictionary(new { controller = "home" });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(match);
- }
-
- [Fact]
- public void StringRouteConstraintSimpleFalseWithRouteDirectionUrlGenerationTest()
- {
- // Arrange
- var constraint = new StringRouteConstraint("admin");
-
- // Act
- var values = new RouteValueDictionary(new { controller = "home" });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.UrlGeneration);
-
- // Assert
- Assert.False(match);
- }
-
- [Fact]
- public void StringRouteConstraintKeyNotFoundWithRouteDirectionIncomingRequestTest()
- {
- // Arrange
- var constraint = new StringRouteConstraint("admin");
-
- // Act
- var values = new RouteValueDictionary(new { controller = "admin" });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "action",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.False(match);
- }
-
- [Fact]
- public void StringRouteConstraintKeyNotFoundWithRouteDirectionUrlGenerationTest()
- {
- // Arrange
- var constraint = new StringRouteConstraint("admin");
-
- // Act
- var values = new RouteValueDictionary(new { controller = "admin" });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "action",
- values: values,
- routeDirection: RouteDirection.UrlGeneration);
-
- // Assert
- Assert.False(match);
- }
-
- [Theory]
- [InlineData("User", "uSer", true)]
- [InlineData("User.Admin", "User.Admin", true)]
- [InlineData(@"User\Admin", "User\\Admin", true)]
- [InlineData(null, "user", false)]
- public void StringRouteConstraintEscapingCaseSensitiveAndRouteNullTest(string routeValue, string constraintValue, bool expected)
- {
- // Arrange
- var constraint = new StringRouteConstraint(constraintValue);
-
- // Act
- var values = new RouteValueDictionary(new { controller = routeValue });
-
- var match = constraint.Match(
- new DefaultHttpContext(),
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: values,
- routeDirection: RouteDirection.IncomingRequest);
-
- // Assert
- Assert.Equal(expected, match);
- }
+ // Arrange
+ var constraint = new StringRouteConstraint(constraintValue);
+
+ // Act
+ var values = new RouteValueDictionary(new { controller = routeValue });
+
+ var match = constraint.Match(
+ new DefaultHttpContext(),
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: values,
+ routeDirection: RouteDirection.IncomingRequest);
+
+ // Assert
+ Assert.Equal(expected, match);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/DataSourceDependentCacheTest.cs b/src/Http/Routing/test/UnitTests/DataSourceDependentCacheTest.cs
index 4e74f9ec06..c58178552d 100644
--- a/src/Http/Routing/test/UnitTests/DataSourceDependentCacheTest.cs
+++ b/src/Http/Routing/test/UnitTests/DataSourceDependentCacheTest.cs
@@ -7,119 +7,118 @@ using System.Text;
using Microsoft.AspNetCore.Routing.TestObjects;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class DataSourceDependentCacheTest
{
- public class DataSourceDependentCacheTest
+ [Fact]
+ public void Cache_Initializes_WhenEnsureInitializedCalled()
{
- [Fact]
- public void Cache_Initializes_WhenEnsureInitializedCalled()
+ // Arrange
+ var called = false;
+
+ var dataSource = new DynamicEndpointDataSource();
+ var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
{
- // Arrange
- var called = false;
-
- var dataSource = new DynamicEndpointDataSource();
- var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
- {
- called = true;
- return "hello, world!";
- });
-
- // Act
- cache.EnsureInitialized();
-
- // Assert
- Assert.True(called);
- Assert.Equal("hello, world!", cache.Value);
- }
-
- [Fact]
- public void Cache_DoesNotInitialize_WhenValueCalled()
+ called = true;
+ return "hello, world!";
+ });
+
+ // Act
+ cache.EnsureInitialized();
+
+ // Assert
+ Assert.True(called);
+ Assert.Equal("hello, world!", cache.Value);
+ }
+
+ [Fact]
+ public void Cache_DoesNotInitialize_WhenValueCalled()
+ {
+ // Arrange
+ var called = false;
+
+ var dataSource = new DynamicEndpointDataSource();
+ var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
{
- // Arrange
- var called = false;
-
- var dataSource = new DynamicEndpointDataSource();
- var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
- {
- called = true;
- return "hello, world!";
- });
-
- // Act
- GC.KeepAlive(cache.Value);
-
- // Assert
- Assert.False(called);
- Assert.Null(cache.Value);
- }
-
- [Fact]
- public void Cache_Reinitializes_WhenDataSourceChanges()
+ called = true;
+ return "hello, world!";
+ });
+
+ // Act
+ GC.KeepAlive(cache.Value);
+
+ // Assert
+ Assert.False(called);
+ Assert.Null(cache.Value);
+ }
+
+ [Fact]
+ public void Cache_Reinitializes_WhenDataSourceChanges()
+ {
+ // Arrange
+ var count = 0;
+
+ var dataSource = new DynamicEndpointDataSource();
+ var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
{
- // Arrange
- var count = 0;
+ count++;
+ return $"hello, {count}!";
+ });
- var dataSource = new DynamicEndpointDataSource();
- var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
- {
- count++;
- return $"hello, {count}!";
- });
+ cache.EnsureInitialized();
+ Assert.Equal("hello, 1!", cache.Value);
- cache.EnsureInitialized();
- Assert.Equal("hello, 1!", cache.Value);
+ // Act
+ dataSource.AddEndpoint(null);
- // Act
- dataSource.AddEndpoint(null);
+ // Assert
+ Assert.Equal(2, count);
+ Assert.Equal("hello, 2!", cache.Value);
+ }
- // Assert
- Assert.Equal(2, count);
- Assert.Equal("hello, 2!", cache.Value);
- }
+ [Fact]
+ public void Cache_CanDispose_WhenUninitialized()
+ {
+ // Arrange
+ var count = 0;
- [Fact]
- public void Cache_CanDispose_WhenUninitialized()
+ var dataSource = new DynamicEndpointDataSource();
+ var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
{
- // Arrange
- var count = 0;
-
- var dataSource = new DynamicEndpointDataSource();
- var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
- {
- count++;
- return $"hello, {count}!";
- });
-
- // Act
- cache.Dispose();
-
- // Assert
- dataSource.AddEndpoint(null);
- Assert.Null(cache.Value);
- }
-
- [Fact]
- public void Cache_CanDispose_WhenInitialized()
+ count++;
+ return $"hello, {count}!";
+ });
+
+ // Act
+ cache.Dispose();
+
+ // Assert
+ dataSource.AddEndpoint(null);
+ Assert.Null(cache.Value);
+ }
+
+ [Fact]
+ public void Cache_CanDispose_WhenInitialized()
+ {
+ // Arrange
+ var count = 0;
+
+ var dataSource = new DynamicEndpointDataSource();
+ var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
{
- // Arrange
- var count = 0;
-
- var dataSource = new DynamicEndpointDataSource();
- var cache = new DataSourceDependentCache<string>(dataSource, (endpoints) =>
- {
- count++;
- return $"hello, {count}!";
- });
-
- cache.EnsureInitialized();
- Assert.Equal("hello, 1!", cache.Value);
-
- // Act
- cache.Dispose();
-
- // Assert
- dataSource.AddEndpoint(null);
- Assert.Equal("hello, 1!", cache.Value); // Ignores update
- }
+ count++;
+ return $"hello, {count}!";
+ });
+
+ cache.EnsureInitialized();
+ Assert.Equal("hello, 1!", cache.Value);
+
+ // Act
+ cache.Dispose();
+
+ // Assert
+ dataSource.AddEndpoint(null);
+ Assert.Equal("hello, 1!", cache.Value); // Ignores update
}
}
diff --git a/src/Http/Routing/test/UnitTests/DecisionTreeBuilderTest.cs b/src/Http/Routing/test/UnitTests/DecisionTreeBuilderTest.cs
index 025601f83a..4615ba5f02 100644
--- a/src/Http/Routing/test/UnitTests/DecisionTreeBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/DecisionTreeBuilderTest.cs
@@ -5,214 +5,213 @@ using System;
using System.Collections.Generic;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.DecisionTree
+namespace Microsoft.AspNetCore.Routing.DecisionTree;
+
+public class DecisionTreeBuilderTest
{
- public class DecisionTreeBuilderTest
+ [Fact]
+ public void BuildTree_Empty()
{
- [Fact]
- public void BuildTree_Empty()
- {
- // Arrange
- var items = new List<Item>();
+ // Arrange
+ var items = new List<Item>();
- // Act
- var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
+ // Act
+ var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
- // Assert
- Assert.Empty(tree.Criteria);
- Assert.Empty(tree.Matches);
- }
+ // Assert
+ Assert.Empty(tree.Criteria);
+ Assert.Empty(tree.Matches);
+ }
- [Fact]
- public void BuildTree_TrivialMatch()
- {
- // Arrange
- var items = new List<Item>();
+ [Fact]
+ public void BuildTree_TrivialMatch()
+ {
+ // Arrange
+ var items = new List<Item>();
- var item = new Item();
- items.Add(item);
+ var item = new Item();
+ items.Add(item);
- // Act
- var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
+ // Act
+ var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
- // Assert
- Assert.Empty(tree.Criteria);
- Assert.Same(item, Assert.Single(tree.Matches));
- }
+ // Assert
+ Assert.Empty(tree.Criteria);
+ Assert.Same(item, Assert.Single(tree.Matches));
+ }
- [Fact]
- public void BuildTree_WithMultipleCriteria()
- {
- // Arrange
- var items = new List<Item>();
+ [Fact]
+ public void BuildTree_WithMultipleCriteria()
+ {
+ // Arrange
+ var items = new List<Item>();
- var item = new Item();
- item.Criteria.Add("area", new DecisionCriterionValue(value: "Admin"));
- item.Criteria.Add("controller", new DecisionCriterionValue(value: "Users"));
- item.Criteria.Add("action", new DecisionCriterionValue(value: "AddUser"));
- items.Add(item);
+ var item = new Item();
+ item.Criteria.Add("area", new DecisionCriterionValue(value: "Admin"));
+ item.Criteria.Add("controller", new DecisionCriterionValue(value: "Users"));
+ item.Criteria.Add("action", new DecisionCriterionValue(value: "AddUser"));
+ items.Add(item);
- // Act
- var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
+ // Act
+ var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
- // Assert
- Assert.Empty(tree.Matches);
+ // Assert
+ Assert.Empty(tree.Matches);
- var area = Assert.Single(tree.Criteria);
- Assert.Equal("area", area.Key);
+ var area = Assert.Single(tree.Criteria);
+ Assert.Equal("area", area.Key);
- var admin = Assert.Single(area.Branches);
- Assert.Equal("Admin", admin.Key);
- Assert.Empty(admin.Value.Matches);
+ var admin = Assert.Single(area.Branches);
+ Assert.Equal("Admin", admin.Key);
+ Assert.Empty(admin.Value.Matches);
- var controller = Assert.Single(admin.Value.Criteria);
- Assert.Equal("controller", controller.Key);
+ var controller = Assert.Single(admin.Value.Criteria);
+ Assert.Equal("controller", controller.Key);
- var users = Assert.Single(controller.Branches);
- Assert.Equal("Users", users.Key);
- Assert.Empty(users.Value.Matches);
+ var users = Assert.Single(controller.Branches);
+ Assert.Equal("Users", users.Key);
+ Assert.Empty(users.Value.Matches);
- var action = Assert.Single(users.Value.Criteria);
- Assert.Equal("action", action.Key);
+ var action = Assert.Single(users.Value.Criteria);
+ Assert.Equal("action", action.Key);
- var addUser = Assert.Single(action.Branches);
- Assert.Equal("AddUser", addUser.Key);
- Assert.Empty(addUser.Value.Criteria);
- Assert.Same(item, Assert.Single(addUser.Value.Matches));
- }
+ var addUser = Assert.Single(action.Branches);
+ Assert.Equal("AddUser", addUser.Key);
+ Assert.Empty(addUser.Value.Criteria);
+ Assert.Same(item, Assert.Single(addUser.Value.Matches));
+ }
- [Fact]
- public void BuildTree_WithMultipleItems()
- {
- // Arrange
- var items = new List<Item>();
+ [Fact]
+ public void BuildTree_WithMultipleItems()
+ {
+ // Arrange
+ var items = new List<Item>();
- var item1 = new Item();
- item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
- item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
- items.Add(item1);
+ var item1 = new Item();
+ item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
+ item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
+ items.Add(item1);
- var item2 = new Item();
- item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
- item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
- items.Add(item2);
+ var item2 = new Item();
+ item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
+ item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
+ items.Add(item2);
- // Act
- var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
+ // Act
+ var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
- // Assert
- Assert.Empty(tree.Matches);
+ // Assert
+ Assert.Empty(tree.Matches);
- var action = Assert.Single(tree.Criteria);
- Assert.Equal("action", action.Key);
+ var action = Assert.Single(tree.Criteria);
+ Assert.Equal("action", action.Key);
- var buy = action.Branches["Buy"];
- Assert.Empty(buy.Matches);
+ var buy = action.Branches["Buy"];
+ Assert.Empty(buy.Matches);
- var controller = Assert.Single(buy.Criteria);
- Assert.Equal("controller", controller.Key);
+ var controller = Assert.Single(buy.Criteria);
+ Assert.Equal("controller", controller.Key);
- var store = Assert.Single(controller.Branches);
- Assert.Equal("Store", store.Key);
- Assert.Empty(store.Value.Criteria);
- Assert.Same(item1, Assert.Single(store.Value.Matches));
+ var store = Assert.Single(controller.Branches);
+ Assert.Equal("Store", store.Key);
+ Assert.Empty(store.Value.Criteria);
+ Assert.Same(item1, Assert.Single(store.Value.Matches));
- var checkout = action.Branches["Checkout"];
- Assert.Empty(checkout.Matches);
+ var checkout = action.Branches["Checkout"];
+ Assert.Empty(checkout.Matches);
- controller = Assert.Single(checkout.Criteria);
- Assert.Equal("controller", controller.Key);
+ controller = Assert.Single(checkout.Criteria);
+ Assert.Equal("controller", controller.Key);
- store = Assert.Single(controller.Branches);
- Assert.Equal("Store", store.Key);
- Assert.Empty(store.Value.Criteria);
- Assert.Same(item2, Assert.Single(store.Value.Matches));
- }
+ store = Assert.Single(controller.Branches);
+ Assert.Equal("Store", store.Key);
+ Assert.Empty(store.Value.Criteria);
+ Assert.Same(item2, Assert.Single(store.Value.Matches));
+ }
- [Fact]
- public void BuildTree_WithInteriorMatch()
- {
- // Arrange
- var items = new List<Item>();
+ [Fact]
+ public void BuildTree_WithInteriorMatch()
+ {
+ // Arrange
+ var items = new List<Item>();
- var item1 = new Item();
- item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
- item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
- items.Add(item1);
+ var item1 = new Item();
+ item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
+ item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
+ items.Add(item1);
- var item2 = new Item();
- item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
- item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
- items.Add(item2);
+ var item2 = new Item();
+ item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
+ item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
+ items.Add(item2);
- var item3 = new Item();
- item3.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
- items.Add(item3);
+ var item3 = new Item();
+ item3.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
+ items.Add(item3);
- // Act
- var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
+ // Act
+ var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
- // Assert
- Assert.Empty(tree.Matches);
+ // Assert
+ Assert.Empty(tree.Matches);
- var action = Assert.Single(tree.Criteria);
- Assert.Equal("action", action.Key);
+ var action = Assert.Single(tree.Criteria);
+ Assert.Equal("action", action.Key);
- var buy = action.Branches["Buy"];
- Assert.Same(item3, Assert.Single(buy.Matches));
- }
+ var buy = action.Branches["Buy"];
+ Assert.Same(item3, Assert.Single(buy.Matches));
+ }
- [Fact]
- public void BuildTree_WithDivergentCriteria()
- {
- // Arrange
- var items = new List<Item>();
+ [Fact]
+ public void BuildTree_WithDivergentCriteria()
+ {
+ // Arrange
+ var items = new List<Item>();
- var item1 = new Item();
- item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
- item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
- items.Add(item1);
+ var item1 = new Item();
+ item1.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
+ item1.Criteria.Add("action", new DecisionCriterionValue(value: "Buy"));
+ items.Add(item1);
- var item2 = new Item();
- item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
- item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
- items.Add(item2);
+ var item2 = new Item();
+ item2.Criteria.Add("controller", new DecisionCriterionValue(value: "Store"));
+ item2.Criteria.Add("action", new DecisionCriterionValue(value: "Checkout"));
+ items.Add(item2);
- var item3 = new Item();
- item3.Criteria.Add("stub", new DecisionCriterionValue(value: "Bleh"));
- items.Add(item3);
+ var item3 = new Item();
+ item3.Criteria.Add("stub", new DecisionCriterionValue(value: "Bleh"));
+ items.Add(item3);
- // Act
- var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
+ // Act
+ var tree = DecisionTreeBuilder<Item>.GenerateTree(items, new ItemClassifier());
- // Assert
- Assert.Empty(tree.Matches);
+ // Assert
+ Assert.Empty(tree.Matches);
- var action = tree.Criteria[0];
- Assert.Equal("action", action.Key);
+ var action = tree.Criteria[0];
+ Assert.Equal("action", action.Key);
- var stub = tree.Criteria[1];
- Assert.Equal("stub", stub.Key);
- }
+ var stub = tree.Criteria[1];
+ Assert.Equal("stub", stub.Key);
+ }
- private class Item
+ private class Item
+ {
+ public Item()
{
- public Item()
- {
- Criteria = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
- }
-
- public Dictionary<string, DecisionCriterionValue> Criteria { get; private set; }
+ Criteria = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
}
- private class ItemClassifier : IClassifier<Item>
- {
- public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;
+ public Dictionary<string, DecisionCriterionValue> Criteria { get; private set; }
+ }
- public IDictionary<string, DecisionCriterionValue> GetCriteria(Item item)
- {
- return item.Criteria;
- }
+ private class ItemClassifier : IClassifier<Item>
+ {
+ public IEqualityComparer<object> ValueComparer => RouteValueEqualityComparer.Default;
+
+ public IDictionary<string, DecisionCriterionValue> GetCriteria(Item item)
+ {
+ return item.Criteria;
}
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/DefaultEndpointDataSourceTests.cs b/src/Http/Routing/test/UnitTests/DefaultEndpointDataSourceTests.cs
index 82495c7daf..9156bb4229 100644
--- a/src/Http/Routing/test/UnitTests/DefaultEndpointDataSourceTests.cs
+++ b/src/Http/Routing/test/UnitTests/DefaultEndpointDataSourceTests.cs
@@ -6,95 +6,94 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class DefaultEndpointDataSourceTests
{
- public class DefaultEndpointDataSourceTests
+ [Fact]
+ public void Constructor_Params_EndpointsInitialized()
{
- [Fact]
- public void Constructor_Params_EndpointsInitialized()
- {
- // Arrange & Act
- var dataSource = new DefaultEndpointDataSource(
- new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1"),
- new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2")
- );
-
- // Assert
- Assert.Collection(dataSource.Endpoints,
- endpoint => Assert.Equal("1", endpoint.DisplayName),
- endpoint => Assert.Equal("2", endpoint.DisplayName));
- }
-
- [Fact]
- public void Constructor_Params_ShouldMakeCopyOfEndpoints()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2");
- var endpoints = new[] { endpoint1, endpoint2 };
-
- // Act
- var dataSource = new DefaultEndpointDataSource(endpoints);
- Array.Resize(ref endpoints, 1);
- endpoints[0] = null;
-
- // Assert
- Assert.Equal(2, dataSource.Endpoints.Count);
- Assert.Contains(endpoint1, dataSource.Endpoints);
- Assert.Contains(endpoint2, dataSource.Endpoints);
- }
-
- [Fact]
- public void Constructor_Params_ShouldThrowArgumentNullExceptionWhenEndpointsIsNull()
- {
- Endpoint[] endpoints = null;
-
- var actual = Assert.Throws<ArgumentNullException>(() => new DefaultEndpointDataSource(endpoints));
- Assert.Equal("endpoints", actual.ParamName);
- }
-
- [Fact]
- public void Constructor_Enumerable_EndpointsInitialized()
- {
- // Arrange & Act
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ // Arrange & Act
+ var dataSource = new DefaultEndpointDataSource(
+ new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1"),
+ new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2")
+ );
+
+ // Assert
+ Assert.Collection(dataSource.Endpoints,
+ endpoint => Assert.Equal("1", endpoint.DisplayName),
+ endpoint => Assert.Equal("2", endpoint.DisplayName));
+ }
+
+ [Fact]
+ public void Constructor_Params_ShouldMakeCopyOfEndpoints()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2");
+ var endpoints = new[] { endpoint1, endpoint2 };
+
+ // Act
+ var dataSource = new DefaultEndpointDataSource(endpoints);
+ Array.Resize(ref endpoints, 1);
+ endpoints[0] = null;
+
+ // Assert
+ Assert.Equal(2, dataSource.Endpoints.Count);
+ Assert.Contains(endpoint1, dataSource.Endpoints);
+ Assert.Contains(endpoint2, dataSource.Endpoints);
+ }
+
+ [Fact]
+ public void Constructor_Params_ShouldThrowArgumentNullExceptionWhenEndpointsIsNull()
+ {
+ Endpoint[] endpoints = null;
+
+ var actual = Assert.Throws<ArgumentNullException>(() => new DefaultEndpointDataSource(endpoints));
+ Assert.Equal("endpoints", actual.ParamName);
+ }
+
+ [Fact]
+ public void Constructor_Enumerable_EndpointsInitialized()
+ {
+ // Arrange & Act
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1"),
new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2")
});
- // Assert
- Assert.Collection(dataSource.Endpoints,
- endpoint => Assert.Equal("1", endpoint.DisplayName),
- endpoint => Assert.Equal("2", endpoint.DisplayName));
- }
-
- [Fact]
- public void Constructor_Enumerable_ShouldMakeCopyOfEndpoints()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2");
- var endpoints = new List<Endpoint> { endpoint1, endpoint2 };
-
- // Act
- var dataSource = new DefaultEndpointDataSource((IEnumerable<Endpoint>)endpoints);
- endpoints.RemoveAt(0);
- endpoints[0] = null;
-
- // Assert
- Assert.Equal(2, dataSource.Endpoints.Count);
- Assert.Contains(endpoint1, dataSource.Endpoints);
- Assert.Contains(endpoint2, dataSource.Endpoints);
- }
-
- [Fact]
- public void Constructor_Enumerable_ShouldThrowArgumentNullExceptionWhenEndpointsIsNull()
- {
- IEnumerable<Endpoint> endpoints = null;
-
- var actual = Assert.Throws<ArgumentNullException>(() => new DefaultEndpointDataSource(endpoints));
- Assert.Equal("endpoints", actual.ParamName);
- }
+ // Assert
+ Assert.Collection(dataSource.Endpoints,
+ endpoint => Assert.Equal("1", endpoint.DisplayName),
+ endpoint => Assert.Equal("2", endpoint.DisplayName));
+ }
+
+ [Fact]
+ public void Constructor_Enumerable_ShouldMakeCopyOfEndpoints()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "2");
+ var endpoints = new List<Endpoint> { endpoint1, endpoint2 };
+
+ // Act
+ var dataSource = new DefaultEndpointDataSource((IEnumerable<Endpoint>)endpoints);
+ endpoints.RemoveAt(0);
+ endpoints[0] = null;
+
+ // Assert
+ Assert.Equal(2, dataSource.Endpoints.Count);
+ Assert.Contains(endpoint1, dataSource.Endpoints);
+ Assert.Contains(endpoint2, dataSource.Endpoints);
+ }
+
+ [Fact]
+ public void Constructor_Enumerable_ShouldThrowArgumentNullExceptionWhenEndpointsIsNull()
+ {
+ IEnumerable<Endpoint> endpoints = null;
+
+ var actual = Assert.Throws<ArgumentNullException>(() => new DefaultEndpointDataSource(endpoints));
+ Assert.Equal("endpoints", actual.ParamName);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs b/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs
index 5d4731aad0..b8daa9a40e 100644
--- a/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs
+++ b/src/Http/Routing/test/UnitTests/DefaultInlineConstraintResolverTest.cs
@@ -11,404 +11,403 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class DefaultInlineConstraintResolverTest
{
- public class DefaultInlineConstraintResolverTest
+ private readonly IInlineConstraintResolver _constraintResolver;
+
+ public DefaultInlineConstraintResolverTest()
{
- private readonly IInlineConstraintResolver _constraintResolver;
+ var routeOptions = new RouteOptions();
+ _constraintResolver = GetInlineConstraintResolver(routeOptions);
+ }
- public DefaultInlineConstraintResolverTest()
- {
- var routeOptions = new RouteOptions();
- _constraintResolver = GetInlineConstraintResolver(routeOptions);
- }
+ [Fact]
+ public void ResolveConstraint_RequiredConstraint_ResolvesCorrectly()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("required");
- [Fact]
- public void ResolveConstraint_RequiredConstraint_ResolvesCorrectly()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("required");
+ // Assert
+ Assert.IsType<RequiredRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<RequiredRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_IntConstraint_ResolvesCorrectly()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("int");
- [Fact]
- public void ResolveConstraint_IntConstraint_ResolvesCorrectly()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("int");
+ // Assert
+ Assert.IsType<IntRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<IntRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_IntConstraintWithArgument_Throws()
+ {
+ // Arrange, Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(
+ () => _constraintResolver.ResolveConstraint("int(5)"));
- [Fact]
- public void ResolveConstraint_IntConstraintWithArgument_Throws()
- {
- // Arrange, Act & Assert
- var ex = Assert.Throws<RouteCreationException>(
- () => _constraintResolver.ResolveConstraint("int(5)"));
+ Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
+ " with the following number of parameters: 1.",
+ ex.Message);
+ }
- Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
- " with the following number of parameters: 1.",
- ex.Message);
- }
+ [Fact]
+ public void ResolveConstraint_AlphaConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("alpha");
- [Fact]
- public void ResolveConstraint_AlphaConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("alpha");
+ // Assert
+ Assert.IsType<AlphaRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<AlphaRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_RegexInlineConstraint_WithAComma_PassesAsASingleArgument()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("regex(ab,1)");
- [Fact]
- public void ResolveConstraint_RegexInlineConstraint_WithAComma_PassesAsASingleArgument()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("regex(ab,1)");
+ // Assert
+ Assert.IsType<RegexInlineRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<RegexInlineRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_RegexInlineConstraint_WithCurlyBraces_Balanced()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint(
+ @"regex(\\b(?<month>\\d{1,2})/(?<day>\\d{1,2})/(?<year>\\d{2,4})\\b)");
- [Fact]
- public void ResolveConstraint_RegexInlineConstraint_WithCurlyBraces_Balanced()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint(
- @"regex(\\b(?<month>\\d{1,2})/(?<day>\\d{1,2})/(?<year>\\d{2,4})\\b)");
+ // Assert
+ Assert.IsType<RegexInlineRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<RegexInlineRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_BoolConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("bool");
- [Fact]
- public void ResolveConstraint_BoolConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("bool");
+ // Assert
+ Assert.IsType<BoolRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<BoolRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_CompositeConstraintIsNotRegistered()
+ {
+ // Arrange, Act & Assert
+ Assert.Null(_constraintResolver.ResolveConstraint("composite"));
+ }
- [Fact]
- public void ResolveConstraint_CompositeConstraintIsNotRegistered()
- {
- // Arrange, Act & Assert
- Assert.Null(_constraintResolver.ResolveConstraint("composite"));
- }
+ [Fact]
+ public void ResolveConstraint_DateTimeConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("datetime");
- [Fact]
- public void ResolveConstraint_DateTimeConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("datetime");
+ // Assert
+ Assert.IsType<DateTimeRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<DateTimeRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_DecimalConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("decimal");
- [Fact]
- public void ResolveConstraint_DecimalConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("decimal");
+ // Assert
+ Assert.IsType<DecimalRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<DecimalRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_DoubleConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("double");
- [Fact]
- public void ResolveConstraint_DoubleConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("double");
+ // Assert
+ Assert.IsType<DoubleRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<DoubleRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_FloatConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("float");
- [Fact]
- public void ResolveConstraint_FloatConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("float");
+ // Assert
+ Assert.IsType<FloatRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<FloatRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_GuidConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("guid");
- [Fact]
- public void ResolveConstraint_GuidConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("guid");
+ // Assert
+ Assert.IsType<GuidRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<GuidRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_IntConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("int");
- [Fact]
- public void ResolveConstraint_IntConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("int");
+ // Assert
+ Assert.IsType<IntRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<IntRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_LengthConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("length(5)");
- [Fact]
- public void ResolveConstraint_LengthConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("length(5)");
+ // Assert
+ Assert.IsType<LengthRouteConstraint>(constraint);
+ Assert.Equal(5, ((LengthRouteConstraint)constraint).MinLength);
+ Assert.Equal(5, ((LengthRouteConstraint)constraint).MaxLength);
+ }
- // Assert
- Assert.IsType<LengthRouteConstraint>(constraint);
- Assert.Equal(5, ((LengthRouteConstraint)constraint).MinLength);
- Assert.Equal(5, ((LengthRouteConstraint)constraint).MaxLength);
- }
+ [Fact]
+ public void ResolveConstraint_LengthRangeConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("length(5, 10)");
- [Fact]
- public void ResolveConstraint_LengthRangeConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("length(5, 10)");
+ // Assert
+ var lengthConstraint = Assert.IsType<LengthRouteConstraint>(constraint);
+ Assert.Equal(5, lengthConstraint.MinLength);
+ Assert.Equal(10, lengthConstraint.MaxLength);
+ }
- // Assert
- var lengthConstraint = Assert.IsType<LengthRouteConstraint>(constraint);
- Assert.Equal(5, lengthConstraint.MinLength);
- Assert.Equal(10, lengthConstraint.MaxLength);
- }
+ [Fact]
+ public void ResolveConstraint_LongRangeConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("long");
- [Fact]
- public void ResolveConstraint_LongRangeConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("long");
+ // Assert
+ Assert.IsType<LongRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<LongRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_MaxConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("max(10)");
- [Fact]
- public void ResolveConstraint_MaxConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("max(10)");
+ // Assert
+ Assert.IsType<MaxRouteConstraint>(constraint);
+ Assert.Equal(10, ((MaxRouteConstraint)constraint).Max);
+ }
- // Assert
- Assert.IsType<MaxRouteConstraint>(constraint);
- Assert.Equal(10, ((MaxRouteConstraint)constraint).Max);
- }
+ [Fact]
+ public void ResolveConstraint_MaxLengthConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("maxlength(10)");
- [Fact]
- public void ResolveConstraint_MaxLengthConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("maxlength(10)");
+ // Assert
+ Assert.IsType<MaxLengthRouteConstraint>(constraint);
+ Assert.Equal(10, ((MaxLengthRouteConstraint)constraint).MaxLength);
+ }
- // Assert
- Assert.IsType<MaxLengthRouteConstraint>(constraint);
- Assert.Equal(10, ((MaxLengthRouteConstraint)constraint).MaxLength);
- }
+ [Fact]
+ public void ResolveConstraint_MinConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("min(3)");
- [Fact]
- public void ResolveConstraint_MinConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("min(3)");
+ // Assert
+ Assert.IsType<MinRouteConstraint>(constraint);
+ Assert.Equal(3, ((MinRouteConstraint)constraint).Min);
+ }
- // Assert
- Assert.IsType<MinRouteConstraint>(constraint);
- Assert.Equal(3, ((MinRouteConstraint)constraint).Min);
- }
+ [Fact]
+ public void ResolveConstraint_MinLengthConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("minlength(3)");
- [Fact]
- public void ResolveConstraint_MinLengthConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("minlength(3)");
+ // Assert
+ Assert.IsType<MinLengthRouteConstraint>(constraint);
+ Assert.Equal(3, ((MinLengthRouteConstraint)constraint).MinLength);
+ }
- // Assert
- Assert.IsType<MinLengthRouteConstraint>(constraint);
- Assert.Equal(3, ((MinLengthRouteConstraint)constraint).MinLength);
- }
+ [Fact]
+ public void ResolveConstraint_RangeConstraint()
+ {
+ // Arrange & Act
+ var constraint = _constraintResolver.ResolveConstraint("range(5, 10)");
+
+ // Assert
+ Assert.IsType<RangeRouteConstraint>(constraint);
+ var rangeConstraint = (RangeRouteConstraint)constraint;
+ Assert.Equal(5, rangeConstraint.Min);
+ Assert.Equal(10, rangeConstraint.Max);
+ }
- [Fact]
- public void ResolveConstraint_RangeConstraint()
- {
- // Arrange & Act
- var constraint = _constraintResolver.ResolveConstraint("range(5, 10)");
-
- // Assert
- Assert.IsType<RangeRouteConstraint>(constraint);
- var rangeConstraint = (RangeRouteConstraint)constraint;
- Assert.Equal(5, rangeConstraint.Min);
- Assert.Equal(10, rangeConstraint.Max);
- }
+ [Fact]
+ public void ResolveConstraint_SupportsCustomConstraints()
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ routeOptions.ConstraintMap.Add("custom", typeof(CustomRouteConstraint));
+ var resolver = GetInlineConstraintResolver(routeOptions);
- [Fact]
- public void ResolveConstraint_SupportsCustomConstraints()
- {
- // Arrange
- var routeOptions = new RouteOptions();
- routeOptions.ConstraintMap.Add("custom", typeof(CustomRouteConstraint));
- var resolver = GetInlineConstraintResolver(routeOptions);
+ // Act
+ var constraint = resolver.ResolveConstraint("custom(argument)");
- // Act
- var constraint = resolver.ResolveConstraint("custom(argument)");
+ // Assert
+ Assert.IsType<CustomRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<CustomRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_SupportsCustomConstraintsUsingNonGenericOverload()
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ routeOptions.SetParameterPolicy("custom", typeof(CustomRouteConstraint));
+ var resolver = GetInlineConstraintResolver(routeOptions);
- [Fact]
- public void ResolveConstraint_SupportsCustomConstraintsUsingNonGenericOverload()
- {
- // Arrange
- var routeOptions = new RouteOptions();
- routeOptions.SetParameterPolicy("custom", typeof(CustomRouteConstraint));
- var resolver = GetInlineConstraintResolver(routeOptions);
+ // Act
+ var constraint = resolver.ResolveConstraint("custom(argument)");
- // Act
- var constraint = resolver.ResolveConstraint("custom(argument)");
+ // Assert
+ Assert.IsType<CustomRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<CustomRouteConstraint>(constraint);
- }
+ [Fact]
+ public void SetParameterPolicyThrowsIfTypeIsNotIParameterPolicy()
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ var ex = Assert.Throws<InvalidOperationException>(() => routeOptions.SetParameterPolicy("custom", typeof(string)));
- [Fact]
- public void SetParameterPolicyThrowsIfTypeIsNotIParameterPolicy()
- {
- // Arrange
- var routeOptions = new RouteOptions();
- var ex = Assert.Throws<InvalidOperationException>(() => routeOptions.SetParameterPolicy("custom", typeof(string)));
+ Assert.Equal("System.String must implement Microsoft.AspNetCore.Routing.IParameterPolicy", ex.Message);
+ }
- Assert.Equal("System.String must implement Microsoft.AspNetCore.Routing.IParameterPolicy", ex.Message);
- }
+ [Fact]
+ public void ResolveConstraint_SupportsCustomConstraintsUsingGenericOverloads()
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ routeOptions.SetParameterPolicy<CustomRouteConstraint>("custom");
+ var resolver = GetInlineConstraintResolver(routeOptions);
- [Fact]
- public void ResolveConstraint_SupportsCustomConstraintsUsingGenericOverloads()
- {
- // Arrange
- var routeOptions = new RouteOptions();
- routeOptions.SetParameterPolicy<CustomRouteConstraint>("custom");
- var resolver = GetInlineConstraintResolver(routeOptions);
+ // Act
+ var constraint = resolver.ResolveConstraint("custom(argument)");
- // Act
- var constraint = resolver.ResolveConstraint("custom(argument)");
+ // Assert
+ Assert.IsType<CustomRouteConstraint>(constraint);
+ }
- // Assert
- Assert.IsType<CustomRouteConstraint>(constraint);
- }
+ [Fact]
+ public void ResolveConstraint_CustomConstraintThatDoesNotImplementIRouteConstraint_Throws()
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ routeOptions.ConstraintMap.Add("custom", typeof(string));
+ var resolver = GetInlineConstraintResolver(routeOptions);
+
+ // Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(() => resolver.ResolveConstraint("custom"));
+ Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'" +
+ " must implement the 'IRouteConstraint' interface.",
+ ex.Message);
+ }
- [Fact]
- public void ResolveConstraint_CustomConstraintThatDoesNotImplementIRouteConstraint_Throws()
- {
- // Arrange
- var routeOptions = new RouteOptions();
- routeOptions.ConstraintMap.Add("custom", typeof(string));
- var resolver = GetInlineConstraintResolver(routeOptions);
-
- // Act & Assert
- var ex = Assert.Throws<RouteCreationException>(() => resolver.ResolveConstraint("custom"));
- Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'" +
- " must implement the 'IRouteConstraint' interface.",
- ex.Message);
- }
+ [Fact]
+ public void ResolveConstraint_AmbiguousConstructors_Throws()
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ routeOptions.ConstraintMap.Add("custom", typeof(MultiConstructorRouteConstraint));
+ var resolver = GetInlineConstraintResolver(routeOptions);
+
+ // Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(() => resolver.ResolveConstraint("custom(5,6)"));
+ Assert.Equal("The constructor to use for activating the constraint type 'MultiConstructorRouteConstraint' is ambiguous." +
+ " Multiple constructors were found with the following number of parameters: 2.",
+ ex.Message);
+ }
- [Fact]
- public void ResolveConstraint_AmbiguousConstructors_Throws()
- {
- // Arrange
- var routeOptions = new RouteOptions();
- routeOptions.ConstraintMap.Add("custom", typeof(MultiConstructorRouteConstraint));
- var resolver = GetInlineConstraintResolver(routeOptions);
-
- // Act & Assert
- var ex = Assert.Throws<RouteCreationException>(() => resolver.ResolveConstraint("custom(5,6)"));
- Assert.Equal("The constructor to use for activating the constraint type 'MultiConstructorRouteConstraint' is ambiguous." +
- " Multiple constructors were found with the following number of parameters: 2.",
- ex.Message);
- }
+ // These are cases which parsing does not catch and we'll end up here
+ [Theory]
+ [InlineData("regex(abc")]
+ [InlineData("int/")]
+ [InlineData("in{t")]
+ public void ResolveConstraint_Invalid_Throws(string constraint)
+ {
+ // Arrange
+ var routeOptions = new RouteOptions();
+ var resolver = GetInlineConstraintResolver(routeOptions);
- // These are cases which parsing does not catch and we'll end up here
- [Theory]
- [InlineData("regex(abc")]
- [InlineData("int/")]
- [InlineData("in{t")]
- public void ResolveConstraint_Invalid_Throws(string constraint)
- {
- // Arrange
- var routeOptions = new RouteOptions();
- var resolver = GetInlineConstraintResolver(routeOptions);
+ // Act & Assert
+ Assert.Null(resolver.ResolveConstraint(constraint));
+ }
- // Act & Assert
- Assert.Null(resolver.ResolveConstraint(constraint));
- }
+ [Fact]
+ public void ResolveConstraint_NoMatchingConstructor_Throws()
+ {
+ // Arrange
+ // Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(() => _constraintResolver.ResolveConstraint("int(5,6)"));
+ Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
+ " with the following number of parameters: 2.",
+ ex.Message);
+ }
+
+ private IInlineConstraintResolver GetInlineConstraintResolver(RouteOptions routeOptions)
+ {
+ var optionsAccessor = new Mock<IOptions<RouteOptions>>();
+ optionsAccessor.SetupGet(o => o.Value).Returns(routeOptions);
+
+ return new DefaultInlineConstraintResolver(optionsAccessor.Object, new TestServiceProvider());
+ }
- [Fact]
- public void ResolveConstraint_NoMatchingConstructor_Throws()
+ private class MultiConstructorRouteConstraint : IRouteConstraint
+ {
+ public MultiConstructorRouteConstraint(string pattern, int intArg)
{
- // Arrange
- // Act & Assert
- var ex = Assert.Throws<RouteCreationException>(() => _constraintResolver.ResolveConstraint("int(5,6)"));
- Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" +
- " with the following number of parameters: 2.",
- ex.Message);
}
- private IInlineConstraintResolver GetInlineConstraintResolver(RouteOptions routeOptions)
+ public MultiConstructorRouteConstraint(int intArg, string pattern)
{
- var optionsAccessor = new Mock<IOptions<RouteOptions>>();
- optionsAccessor.SetupGet(o => o.Value).Returns(routeOptions);
+ }
- return new DefaultInlineConstraintResolver(optionsAccessor.Object, new TestServiceProvider());
+ public bool Match(HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ return true;
}
+ }
- private class MultiConstructorRouteConstraint : IRouteConstraint
+ private class CustomRouteConstraint : IRouteConstraint
+ {
+ public CustomRouteConstraint(string pattern)
{
- public MultiConstructorRouteConstraint(string pattern, int intArg)
- {
- }
-
- public MultiConstructorRouteConstraint(int intArg, string pattern)
- {
- }
-
- public bool Match(HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- return true;
- }
+ Pattern = pattern;
}
- private class CustomRouteConstraint : IRouteConstraint
+ public string Pattern { get; private set; }
+ public bool Match(HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- public CustomRouteConstraint(string pattern)
- {
- Pattern = pattern;
- }
-
- public string Pattern { get; private set; }
- public bool Match(HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- return true;
- }
+ return true;
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorProcessTemplateTest.cs b/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorProcessTemplateTest.cs
index 61b47dc9c8..ab2dc4fe44 100644
--- a/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorProcessTemplateTest.cs
+++ b/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorProcessTemplateTest.cs
@@ -12,1392 +12,1392 @@ using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// Detailed coverage for how DefaultLinkGenerator processes templates
+public class DefaultLinkGeneratorProcessTemplateTest : LinkGeneratorTestBase
{
- // Detailed coverage for how DefaultLinkGenerator processes templates
- public class DefaultLinkGeneratorProcessTemplateTest : LinkGeneratorTestBase
+ [Fact]
+ public void TryProcessTemplate_EncodesIntermediate_DefaultValues()
{
- [Fact]
- public void TryProcessTemplate_EncodesIntermediate_DefaultValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{p1}/{p2=a b}/{p3=foo}");
- var linkGenerator = CreateLinkGenerator(endpoint);
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: null,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { p1 = "Home", p3 = "bar", }),
- ambientValues: null,
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/a%20b/bar", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{p1}/{p2=a b}/{p3=foo}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: null,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { p1 = "Home", p3 = "bar", }),
+ ambientValues: null,
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/a%20b/bar", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Theory]
- [InlineData("a/b/c", "/Home/Index/a%2Fb%2Fc")]
- [InlineData("a/b b1/c c1", "/Home/Index/a%2Fb%20b1%2Fc%20c1")]
- public void TryProcessTemplate_EncodesValue_OfSingleAsteriskCatchAllParameter(string routeValue, string expected)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{*path}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { path = routeValue, }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal(expected, result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Theory]
+ [InlineData("a/b/c", "/Home/Index/a%2Fb%2Fc")]
+ [InlineData("a/b b1/c c1", "/Home/Index/a%2Fb%20b1%2Fc%20c1")]
+ public void TryProcessTemplate_EncodesValue_OfSingleAsteriskCatchAllParameter(string routeValue, string expected)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{*path}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { path = routeValue, }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal(expected, result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Theory]
- [InlineData("/", "/Home/Index//")]
- [InlineData("a", "/Home/Index/a")]
- [InlineData("a/", "/Home/Index/a/")]
- [InlineData("a/b", "/Home/Index/a/b")]
- [InlineData("a/b/c", "/Home/Index/a/b/c")]
- [InlineData("a/b/cc", "/Home/Index/a/b/cc")]
- [InlineData("a/b/c/", "/Home/Index/a/b/c/")]
- [InlineData("a/b/c//", "/Home/Index/a/b/c//")]
- [InlineData("a//b//c", "/Home/Index/a//b//c")]
- public void TryProcessTemplate_DoesNotEncodeSlashes_OfDoubleAsteriskCatchAllParameter(string routeValue, string expected)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{**path}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { path = routeValue, }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal(expected, result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Theory]
+ [InlineData("/", "/Home/Index//")]
+ [InlineData("a", "/Home/Index/a")]
+ [InlineData("a/", "/Home/Index/a/")]
+ [InlineData("a/b", "/Home/Index/a/b")]
+ [InlineData("a/b/c", "/Home/Index/a/b/c")]
+ [InlineData("a/b/cc", "/Home/Index/a/b/cc")]
+ [InlineData("a/b/c/", "/Home/Index/a/b/c/")]
+ [InlineData("a/b/c//", "/Home/Index/a/b/c//")]
+ [InlineData("a//b//c", "/Home/Index/a//b//c")]
+ public void TryProcessTemplate_DoesNotEncodeSlashes_OfDoubleAsteriskCatchAllParameter(string routeValue, string expected)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{**path}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { path = routeValue, }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal(expected, result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_EncodesContentOtherThanSlashes_OfDoubleAsteriskCatchAllParameter()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{**path}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { path = "a/b b1/c c1" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/a/b%20b1/c%20c1", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_EncodesContentOtherThanSlashes_OfDoubleAsteriskCatchAllParameter()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{**path}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { path = "a/b b1/c c1" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/a/b%20b1/c%20c1", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_EncodesValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { name = "name with %special #characters" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal("?name=name%20with%20%25special%20%23characters", result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_EncodesValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { name = "name with %special #characters" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal("?name=name%20with%20%25special%20%23characters", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_ForListOfStrings()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { color = new List<string> { "red", "green", "blue" } }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal("?color=red&color=green&color=blue", result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_ForListOfStrings()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { color = new List<string> { "red", "green", "blue" } }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal("?color=red&color=green&color=blue", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_ForListOfInts()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { items = new List<int> { 10, 20, 30 } }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal("?items=10&items=20&items=30", result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_ForListOfInts()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { items = new List<int> { 10, 20, 30 } }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal("?items=10&items=20&items=30", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_ForList_Empty()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { color = new List<string> { } }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_ForList_Empty()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { color = new List<string> { } }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_ForList_StringWorkaround()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal("?page=1&color=red&color=green&color=blue&message=textfortest", result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_ForList_StringWorkaround()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal("?page=1&color=red&color=green&color=blue&message=textfortest", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_Success_AmbientValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_Success_AmbientValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_GeneratesLowercaseUrl_SetOnRouteOptions()
+ [Fact]
+ public void TryProcessTemplate_GeneratesLowercaseUrl_SetOnRouteOptions()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
- {
- s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
- };
-
- var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/home/index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/home/index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- // Regression test for https://github.com/aspnet/Routing/issues/802
- [Fact]
- public void TryProcessTemplate_GeneratesLowercaseUrl_Includes_BufferedValues_SetOnRouteOptions()
+ // Regression test for https://github.com/aspnet/Routing/issues/802
+ [Fact]
+ public void TryProcessTemplate_GeneratesLowercaseUrl_Includes_BufferedValues_SetOnRouteOptions()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=BAR}/{id?}");
- Action<IServiceCollection> configure = (s) =>
- {
- s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
- };
-
- var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
- var httpContext = CreateHttpContext();
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { id = "18" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/foo/bar/18", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
+ var httpContext = CreateHttpContext();
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { id = "18" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/foo/bar/18", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- // Regression test for https://github.com/aspnet/Routing/issues/802
- [Fact]
- public void TryProcessTemplate_ParameterPolicy_Includes_BufferedValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=MyBar}/{id?}", policies: new { bar = new SlugifyParameterTransformer(), });
- var linkGenerator = CreateLinkGenerator(endpoints: new[] { endpoint, });
- var httpContext = CreateHttpContext();
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { id = "18" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Foo/my-bar/18", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ // Regression test for https://github.com/aspnet/Routing/issues/802
+ [Fact]
+ public void TryProcessTemplate_ParameterPolicy_Includes_BufferedValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("Foo/{bar=MyBar}/{id?}", policies: new { bar = new SlugifyParameterTransformer(), });
+ var linkGenerator = CreateLinkGenerator(endpoints: new[] { endpoint, });
+ var httpContext = CreateHttpContext();
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { id = "18" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Foo/my-bar/18", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- // Regression test for aspnet/Routing#435
- //
- // In this issue we used to lowercase URLs after parameters were encoded, meaning that if a character needed
- // encoding (such as a cyrillic character, it would not be encoded).
- [Fact]
- public void TryProcessTemplate_GeneratesLowercaseUrl_SetOnRouteOptions_CanLowercaseCharactersThatNeedEncoding()
+ // Regression test for aspnet/Routing#435
+ //
+ // In this issue we used to lowercase URLs after parameters were encoded, meaning that if a character needed
+ // encoding (such as a cyrillic character, it would not be encoded).
+ [Fact]
+ public void TryProcessTemplate_GeneratesLowercaseUrl_SetOnRouteOptions_CanLowercaseCharactersThatNeedEncoding()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
- {
- s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
- };
-
- var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "П" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), // Cryillic uppercase Pe
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/home/%D0%BF", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
-
- // Convert back to decoded.
- //
- // This is Cyrillic lowercase Pe (not an n).
- Assert.Equal("/home/п", PathString.FromUriComponent(result.path.ToUriComponent()).Value);
- }
+ s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(configure, endpoints: new[] { endpoint, });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "П" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext), // Cryillic uppercase Pe
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/home/%D0%BF", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+
+ // Convert back to decoded.
+ //
+ // This is Cyrillic lowercase Pe (not an n).
+ Assert.Equal("/home/п", PathString.FromUriComponent(result.path.ToUriComponent()).Value);
+ }
- [Fact]
- public void TryProcessTemplate_GeneratesLowercaseQueryString_SetOnRouteOptions()
+ [Fact]
+ public void TryProcessTemplate_GeneratesLowercaseQueryString_SetOnRouteOptions()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.LowercaseUrls = true;
- o.LowercaseQueryStrings = true;
- });
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint, });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/home/index", result.path.ToUriComponent());
- Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent());
- }
+ o.LowercaseUrls = true;
+ o.LowercaseQueryStrings = true;
+ });
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint, });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/home/index", result.path.ToUriComponent());
+ Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_AppendsTrailingSlash_SetOnRouteOptions()
+ [Fact]
+ public void TryProcessTemplate_AppendsTrailingSlash_SetOnRouteOptions()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
- {
- s.Configure<RouteOptions>(o => o.AppendTrailingSlash = true);
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ s.Configure<RouteOptions>(o => o.AppendTrailingSlash = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_GeneratesLowercaseQueryStringAndTrailingSlash_SetOnRouteOptions()
+ [Fact]
+ public void TryProcessTemplate_GeneratesLowercaseQueryStringAndTrailingSlash_SetOnRouteOptions()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.LowercaseUrls = true;
- o.LowercaseQueryStrings = true;
- o.AppendTrailingSlash = true;
- });
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/home/index/", result.path.ToUriComponent());
- Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent());
- }
+ o.LowercaseUrls = true;
+ o.LowercaseQueryStrings = true;
+ o.AppendTrailingSlash = true;
+ });
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/home/index/", result.path.ToUriComponent());
+ Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_LowercaseUrlSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
+ [Fact]
+ public void TryProcessTemplate_LowercaseUrlSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "InDex" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: new LinkOptions
{
- s.Configure<RouteOptions>(o => o.LowercaseUrls = true);
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "InDex" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: new LinkOptions
- {
- LowercaseUrls = false
- },
- result: out var result);
+ LowercaseUrls = false
+ },
+ result: out var result);
- // Assert
- Assert.True(success);
- Assert.Equal("/HoMe/InDex", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/HoMe/InDex", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_LowercaseUrlSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
+ [Fact]
+ public void TryProcessTemplate_LowercaseUrlSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ s.Configure<RouteOptions>(o => o.LowercaseUrls = false);
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "InDex" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: new LinkOptions()
{
- s.Configure<RouteOptions>(o => o.LowercaseUrls = false);
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "HoMe" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "InDex" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: new LinkOptions()
- {
- LowercaseUrls = true
- },
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/home/index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ LowercaseUrls = true
+ },
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/home/index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_LowercaseUrlQueryStringsSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
+ [Fact]
+ public void TryProcessTemplate_LowercaseUrlQueryStringsSetToTrue_OnRouteOptions_OverridenByCallsiteValue()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.LowercaseUrls = true;
- o.LowercaseQueryStrings = true;
- });
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: new LinkOptions
- {
- LowercaseUrls = false,
- LowercaseQueryStrings = false
- },
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal("?ShowStatus=True&INFO=DETAILED", result.query.ToUriComponent());
- }
-
- [Fact]
- public void TryProcessTemplate_LowercaseUrlQueryStringsSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ o.LowercaseUrls = true;
+ o.LowercaseQueryStrings = true;
+ });
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: new LinkOptions
{
- s.Configure<RouteOptions>(o =>
- {
- o.LowercaseUrls = false;
- o.LowercaseQueryStrings = false;
- });
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: new LinkOptions()
- {
- LowercaseUrls = true,
- LowercaseQueryStrings = true,
- },
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/home/index", result.path.ToUriComponent());
- Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent());
- }
+ LowercaseUrls = false,
+ LowercaseQueryStrings = false
+ },
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal("?ShowStatus=True&INFO=DETAILED", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_AppendTrailingSlashSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
+ [Fact]
+ public void TryProcessTemplate_LowercaseUrlQueryStringsSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
- Action<IServiceCollection> configure = (s) =>
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o => o.AppendTrailingSlash = false);
- };
-
- var linkGenerator = CreateLinkGenerator(
- configure,
- endpoints: new[] { endpoint });
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: new LinkOptions() { AppendTrailingSlash = true, },
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ o.LowercaseUrls = false;
+ o.LowercaseQueryStrings = false;
+ });
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", ShowStatus = "True", INFO = "DETAILED" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: new LinkOptions()
+ {
+ LowercaseUrls = true,
+ LowercaseQueryStrings = true,
+ },
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/home/index", result.path.ToUriComponent());
+ Assert.Equal("?showstatus=true&info=detailed", result.query.ToUriComponent());
+ }
- [Fact]
- public void RouteGenerationRejectsConstraints()
+ [Fact]
+ public void TryProcessTemplate_AppendTrailingSlashSetToFalse_OnRouteOptions_OverridenByCallsiteValue()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}");
+ Action<IServiceCollection> configure = (s) =>
{
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "{p1}/{p2}",
- defaults: new { p2 = "catchall" },
- policies: new { p2 = "\\d{4}" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { p1 = "abcd" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ s.Configure<RouteOptions>(o => o.AppendTrailingSlash = false);
+ };
+
+ var linkGenerator = CreateLinkGenerator(
+ configure,
+ endpoints: new[] { endpoint });
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: new LinkOptions() { AppendTrailingSlash = true, },
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void RouteGenerationAcceptsConstraints()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "{p1}/{p2}",
- defaults: new { p2 = "catchall" },
- policies: new { p2 = new RegexRouteConstraint("\\d{4}"), });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/hello/1234", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void RouteGenerationRejectsConstraints()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "{p1}/{p2}",
+ defaults: new { p2 = "catchall" },
+ policies: new { p2 = "\\d{4}" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { p1 = "abcd" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
+ }
- [Fact]
- public void RouteWithCatchAllRejectsConstraints()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "{p1}/{*p2}",
- defaults: new { p2 = "catchall" },
- policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { p1 = "abcd" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ [Fact]
+ public void RouteGenerationAcceptsConstraints()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "{p1}/{p2}",
+ defaults: new { p2 = "catchall" },
+ policies: new { p2 = new RegexRouteConstraint("\\d{4}"), });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/hello/1234", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void RouteWithCatchAllAcceptsConstraints()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "{p1}/{*p2}",
- defaults: new { p2 = "catchall" },
- policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/hello/1234", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void RouteWithCatchAllRejectsConstraints()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "{p1}/{*p2}",
+ defaults: new { p2 = "catchall" },
+ policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { p1 = "abcd" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
+ }
- [Fact]
- public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString()
- {
- // Arrange
- var target = new Mock<IRouteConstraint>();
- target
- .Setup(
- e => e.Match(
- It.IsAny<HttpContext>(),
- It.IsAny<IRouter>(),
- It.IsAny<string>(),
- It.IsAny<RouteValueDictionary>(),
- It.IsAny<RouteDirection>()))
- .Returns(true)
- .Verifiable();
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "{p1}/{p2}",
- defaults: new { p2 = "catchall" },
- policies: new { p2 = target.Object });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/hello/1234", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
-
- target.VerifyAll();
- }
+ [Fact]
+ public void RouteWithCatchAllAcceptsConstraints()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "{p1}/{*p2}",
+ defaults: new { p2 = "catchall" },
+ policies: new { p2 = new RegexRouteConstraint("\\d{4}") });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/hello/1234", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
+ [Fact]
+ public void GetLinkWithNonParameterConstraintReturnsUrlWithoutQueryString()
+ {
+ // Arrange
+ var target = new Mock<IRouteConstraint>();
+ target
+ .Setup(
+ e => e.Match(
+ It.IsAny<HttpContext>(),
+ It.IsAny<IRouter>(),
+ It.IsAny<string>(),
+ It.IsAny<RouteValueDictionary>(),
+ It.IsAny<RouteDirection>()))
+ .Returns(true)
+ .Verifiable();
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "{p1}/{p2}",
+ defaults: new { p2 = "catchall" },
+ policies: new { p2 = target.Object });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { p1 = "hello", p2 = "1234" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/hello/1234", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+
+ target.VerifyAll();
+ }
- // Any ambient values from the current request should be visible to constraint, even
- // if they have nothing to do with the route generating a link
- [Fact]
- public void TryProcessTemplate_ConstraintsSeeAmbientValues()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "slug/Home/Store",
- defaults: new { controller = "Home", action = "Store" },
- policies: new { c = constraint });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(
- ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
- var expectedValues = new RouteValueDictionary(
- new { controller = "Home", action = "Store", extra = "42" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Store" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/slug/Home/Store", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
-
- Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
- }
- // Non-parameter default values from the routing generating a link are not in the 'values'
- // collection when constraints are processed.
- [Fact]
- public void TryProcessTemplate_ConstraintsDontSeeDefaults_WhenTheyArentParameters()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "slug/Home/Store",
- defaults: new { controller = "Home", action = "Store", otherthing = "17" },
- policies: new { c = constraint });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
- var expectedValues = new RouteValueDictionary(
- new { controller = "Home", action = "Store" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Store" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/slug/Home/Store", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
-
- Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
- }
+ // Any ambient values from the current request should be visible to constraint, even
+ // if they have nothing to do with the route generating a link
+ [Fact]
+ public void TryProcessTemplate_ConstraintsSeeAmbientValues()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "slug/Home/Store",
+ defaults: new { controller = "Home", action = "Store" },
+ policies: new { c = constraint });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(
+ ambientValues: new { controller = "Home", action = "Blog", extra = "42" });
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Home", action = "Store", extra = "42" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Store" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/slug/Home/Store", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+
+ Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
+ }
- // Default values are visible to the constraint when they are used to fill a parameter.
- [Fact]
- public void TryProcessTemplate_ConstraintsSeesDefault_WhenThereItsAParameter()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "slug/{controller}/{action}",
- defaults: new { action = "Index" },
- policies: new { c = constraint, });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
- var expectedValues = new RouteValueDictionary(
- new { controller = "Shopping", action = "Index" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { controller = "Shopping" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/slug/Shopping", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- Assert.Equal(expectedValues, constraint.Values);
- }
+ // Non-parameter default values from the routing generating a link are not in the 'values'
+ // collection when constraints are processed.
+ [Fact]
+ public void TryProcessTemplate_ConstraintsDontSeeDefaults_WhenTheyArentParameters()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "slug/Home/Store",
+ defaults: new { controller = "Home", action = "Store", otherthing = "17" },
+ policies: new { c = constraint });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Home", action = "Store" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Store" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/slug/Home/Store", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+
+ Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
+ }
- // Default values from the routing generating a link are in the 'values' collection when
- // constraints are processed - IFF they are specified as values or ambient values.
- [Fact]
- public void TryProcessTemplate_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "slug/Home/Store",
- defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
- policies: new { c = constraint, });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(
- ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
-
- var expectedValues = new RouteValueDictionary(
- new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Store", thirdthing = "13" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/slug/Home/Store", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
-
- Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
- }
+ // Default values are visible to the constraint when they are used to fill a parameter.
+ [Fact]
+ public void TryProcessTemplate_ConstraintsSeesDefault_WhenThereItsAParameter()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "slug/{controller}/{action}",
+ defaults: new { action = "Index" },
+ policies: new { c = constraint, });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { controller = "Home", action = "Blog" });
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Shopping", action = "Index" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { controller = "Shopping" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/slug/Shopping", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ Assert.Equal(expectedValues, constraint.Values);
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void TryProcessTemplate_InlineConstraints_Success(bool hasHttpContext)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id:int}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 4 }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/4", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ // Default values from the routing generating a link are in the 'values' collection when
+ // constraints are processed - IFF they are specified as values or ambient values.
+ [Fact]
+ public void TryProcessTemplate_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "slug/Home/Store",
+ defaults: new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" },
+ policies: new { c = constraint, });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(
+ ambientValues: new { controller = "Home", action = "Blog", otherthing = "17" });
+
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Store", thirdthing = "13" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/slug/Home/Store", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+
+ Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
+ }
- [Fact]
- public void TryProcessTemplate_InlineConstraints_NonMatchingvalue()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { id = "int" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void TryProcessTemplate_InlineConstraints_Success(bool hasHttpContext)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id:int}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 4 }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/4", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValuePresent(bool hasHttpContext)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id:int?}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 98 }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/98", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_InlineConstraints_NonMatchingvalue()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { id = "int" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
+ }
- [Fact]
- public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValueNotPresent()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { id = "int" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValuePresent(bool hasHttpContext)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id:int?}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 98 }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/98", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { id = "int" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ [Fact]
+ public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValueNotPresent()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { id = "int" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void TryProcessTemplate_InlineConstraints_MultipleInlineConstraints(bool hasHttpContext)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id:int:range(1,20)}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 14 }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/14", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { id = "int" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = "not-an-integer" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void TryProcessTemplate_InlineConstraints_CompositeInlineConstraint_Fails(bool hasHttpContext)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{id:int:range(1,20)}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 50 }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void TryProcessTemplate_InlineConstraints_MultipleInlineConstraints(bool hasHttpContext)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id:int:range(1,20)}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 14 }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/14", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_InlineConstraints_CompositeConstraint_FromConstructor()
- {
- // Arrange
- var constraint = new MaxLengthRouteConstraint(20);
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "Home/Index/{name}",
- defaults: new { controller = "Home", action = "Index" },
- policies: new { name = constraint });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void TryProcessTemplate_InlineConstraints_CompositeInlineConstraint_Fails(bool hasHttpContext)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{id:int:range(1,20)}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = hasHttpContext ? CreateHttpContext(new { }) : null;
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", id = 50 }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_ParameterPresentInValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_InlineConstraints_CompositeConstraint_FromConstructor()
+ {
+ // Arrange
+ var constraint = new MaxLengthRouteConstraint(20);
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "Home/Index/{name}",
+ defaults: new { controller = "Home", action = "Index" },
+ policies: new { name = constraint });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_ParameterNotPresentInValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_ParameterPresentInValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_ParameterPresentInValuesAndDefaults()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "{controller}/{action}/{name}",
- defaults: new { name = "default-products" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_ParameterNotPresentInValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "{controller}/{action}/{name}",
- defaults: new { name = "products" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_ParameterPresentInValuesAndDefaults()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "{controller}/{action}/{name}",
+ defaults: new { name = "default-products" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_ParameterNotPresentInTemplate_PresentInValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products", format = "json" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
- Assert.Equal("?format=json", result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "{controller}/{action}/{name}",
+ defaults: new { name = "products" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- template: "{controller}/{action}/.{name?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index/.products", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_ParameterNotPresentInTemplate_PresentInValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products", format = "json" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/products", result.path.ToUriComponent());
+ Assert.Equal("?format=json", result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/.{name?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
-
- Assert.True(success);
- Assert.Equal("/Home/Index/", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ template: "{controller}/{action}/.{name?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", name = "products" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index/.products", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameter_InSimpleSegment()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Home/Index", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/.{name?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+
+ Assert.True(success);
+ Assert.Equal("/Home/Index/", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_TwoOptionalParameters_OneValueFromAmbientValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}/{d?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { c = "17" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/a/15/17", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameter_InSimpleSegment()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{name?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Home/Index", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_OptionalParameterAfterDefault_OneValueFromAmbientValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { c = "17" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/a/15/17", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_TwoOptionalParameters_OneValueFromAmbientValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}/{d?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { c = "17" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/a/15/17", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}/{d?}");
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { d = "17" });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/a", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_OptionalParameterAfterDefault_OneValueFromAmbientValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { c = "17" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/a/15/17", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
+
+ [Fact]
+ public void TryProcessTemplate_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("a/{b=15}/{c?}/{d?}");
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { d = "17" });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/a", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- public static TheoryData<object, object, object, object> DoesNotDiscardAmbientValuesData
+ public static TheoryData<object, object, object, object> DoesNotDiscardAmbientValuesData
+ {
+ get
{
- get
- {
- // - ambient values
- // - explicit values
- // - required values
- // - defaults
- return new TheoryData<object, object, object, object>
+ // - ambient values
+ // - explicit values
+ // - required values
+ // - defaults
+ return new TheoryData<object, object, object, object>
{
// link to same action on same controller
{
@@ -1459,103 +1459,103 @@ namespace Microsoft.AspNetCore.Routing
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" }
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DoesNotDiscardAmbientValuesData))]
- public void TryProcessTemplate_DoesNotDiscardAmbientValues_IfAllRequiredKeysMatch(
- object ambientValues,
- object explicitValues,
- object requiredValues,
- object defaults)
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "Products/Edit/{id}",
- requiredValues: requiredValues,
- defaults: defaults);
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues);
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(explicitValues),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Products/Edit/10", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Theory]
+ [MemberData(nameof(DoesNotDiscardAmbientValuesData))]
+ public void TryProcessTemplate_DoesNotDiscardAmbientValues_IfAllRequiredKeysMatch(
+ object ambientValues,
+ object explicitValues,
+ object requiredValues,
+ object defaults)
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "Products/Edit/{id}",
+ requiredValues: requiredValues,
+ defaults: defaults);
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues);
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(explicitValues),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Products/Edit/10", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_DoesNotDiscardAmbientValues_IfAllRequiredValuesMatch_ForGenericKeys()
- {
- // Verifying that discarding works in general usage case i.e when keys are not like controller, action etc.
-
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "Products/Edit/{id}",
- requiredValues: new { c = "Products", a = "Edit" },
- defaults: new { c = "Products", a = "Edit" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { c = "Products", a = "Edit" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.True(success);
- Assert.Equal("/Products/Edit/10", result.path.ToUriComponent());
- Assert.Equal(string.Empty, result.query.ToUriComponent());
- }
+ [Fact]
+ public void TryProcessTemplate_DoesNotDiscardAmbientValues_IfAllRequiredValuesMatch_ForGenericKeys()
+ {
+ // Verifying that discarding works in general usage case i.e when keys are not like controller, action etc.
+
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "Products/Edit/{id}",
+ requiredValues: new { c = "Products", a = "Edit" },
+ defaults: new { c = "Products", a = "Edit" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { c = "Products", a = "Edit" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.True(success);
+ Assert.Equal("/Products/Edit/10", result.path.ToUriComponent());
+ Assert.Equal(string.Empty, result.query.ToUriComponent());
+ }
- [Fact]
- public void TryProcessTemplate_DiscardsAmbientValues_ForGenericKeys()
- {
- // Verifying that discarding works in general usage case i.e when keys are not like controller, action etc.
-
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "Products/Edit/{id}",
- requiredValues: new { c = "Products", a = "Edit" },
- defaults: new { c = "Products", a = "Edit" });
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 });
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(new { c = "Products", a = "List" }),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ [Fact]
+ public void TryProcessTemplate_DiscardsAmbientValues_ForGenericKeys()
+ {
+ // Verifying that discarding works in general usage case i.e when keys are not like controller, action etc.
+
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "Products/Edit/{id}",
+ requiredValues: new { c = "Products", a = "Edit" },
+ defaults: new { c = "Products", a = "Edit" });
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues: new { c = "Products", a = "Edit", id = 10 });
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(new { c = "Products", a = "List" }),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
+ }
- public static TheoryData<object, object, object, object> DiscardAmbientValuesData
+ public static TheoryData<object, object, object, object> DiscardAmbientValuesData
+ {
+ get
{
- get
- {
- // - ambient values
- // - explicit values
- // - required values
- // - defaults
- return new TheoryData<object, object, object, object>
+ // - ambient values
+ // - explicit values
+ // - required values
+ // - defaults
+ return new TheoryData<object, object, object, object>
{
// link to different action on same controller
{
@@ -1621,38 +1621,37 @@ namespace Microsoft.AspNetCore.Routing
new { area = (string)null, controller = (string)null, action = (string)null, page = "Products/Edit" }
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DiscardAmbientValuesData))]
- public void TryProcessTemplate_DiscardsAmbientValues_IfAnyAmbientValue_IsDifferentThan_EndpointRequiredValues(
- object ambientValues,
- object explicitValues,
- object requiredValues,
- object defaults)
- {
- // Linking to a different action on the same controller
-
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "Products/Edit/{id}",
- requiredValues: requiredValues,
- defaults: defaults);
- var linkGenerator = CreateLinkGenerator(endpoint);
- var httpContext = CreateHttpContext(ambientValues);
-
- // Act
- var success = linkGenerator.TryProcessTemplate(
- httpContext: httpContext,
- endpoint: endpoint,
- values: new RouteValueDictionary(explicitValues),
- ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
- options: null,
- result: out var result);
-
- // Assert
- Assert.False(success);
- }
+ [Theory]
+ [MemberData(nameof(DiscardAmbientValuesData))]
+ public void TryProcessTemplate_DiscardsAmbientValues_IfAnyAmbientValue_IsDifferentThan_EndpointRequiredValues(
+ object ambientValues,
+ object explicitValues,
+ object requiredValues,
+ object defaults)
+ {
+ // Linking to a different action on the same controller
+
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "Products/Edit/{id}",
+ requiredValues: requiredValues,
+ defaults: defaults);
+ var linkGenerator = CreateLinkGenerator(endpoint);
+ var httpContext = CreateHttpContext(ambientValues);
+
+ // Act
+ var success = linkGenerator.TryProcessTemplate(
+ httpContext: httpContext,
+ endpoint: endpoint,
+ values: new RouteValueDictionary(explicitValues),
+ ambientValues: DefaultLinkGenerator.GetAmbientValues(httpContext),
+ options: null,
+ result: out var result);
+
+ // Assert
+ Assert.False(success);
}
}
diff --git a/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs b/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs
index 69cc3a1db6..1be83277ba 100644
--- a/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs
+++ b/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs
@@ -10,754 +10,753 @@ using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// Tests LinkGenerator functionality using GetXyzByAddress - see tests for the extension
+// methods for more E2E tests.
+//
+// Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
+// and DefaultLinkGeneratorProcessTemplateTest
+public class DefaultLinkGeneratorTest : LinkGeneratorTestBase
{
- // Tests LinkGenerator functionality using GetXyzByAddress - see tests for the extension
- // methods for more E2E tests.
- //
- // Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
- // and DefaultLinkGeneratorProcessTemplateTest
- public class DefaultLinkGeneratorTest : LinkGeneratorTestBase
- {
- [Fact]
- public void GetPathByAddress_WithoutHttpContext_NoMatches_ReturnsNull()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithoutHttpContext_NoMatches_ReturnsNull()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint);
+ var linkGenerator = CreateLinkGenerator(endpoint);
- // Act
- var path = linkGenerator.GetPathByAddress(0, values: null);
+ // Act
+ var path = linkGenerator.GetPathByAddress(0, values: null);
- // Assert
- Assert.Null(path);
- }
+ // Assert
+ Assert.Null(path);
+ }
- [Fact]
- public void GetPathByAddress_WithHttpContext_NoMatches_ReturnsNull()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_NoMatches_ReturnsNull()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint);
+ var linkGenerator = CreateLinkGenerator(endpoint);
- // Act
- var path = linkGenerator.GetPathByAddress(CreateHttpContext(), 0, values: null);
+ // Act
+ var path = linkGenerator.GetPathByAddress(CreateHttpContext(), 0, values: null);
- // Assert
- Assert.Null(path);
- }
+ // Assert
+ Assert.Null(path);
+ }
- [Fact]
- public void GetUriByAddress_WithoutHttpContext_NoMatches_ReturnsNull()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetUriByAddress_WithoutHttpContext_NoMatches_ReturnsNull()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint);
+ var linkGenerator = CreateLinkGenerator(endpoint);
- // Act
- var uri = linkGenerator.GetUriByAddress(0, values: null, "http", new HostString("example.com"));
+ // Act
+ var uri = linkGenerator.GetUriByAddress(0, values: null, "http", new HostString("example.com"));
- // Assert
- Assert.Null(uri);
- }
+ // Assert
+ Assert.Null(uri);
+ }
- [Fact]
- public void GetUriByAddress_WithHttpContext_NoMatches_ReturnsNull()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetUriByAddress_WithHttpContext_NoMatches_ReturnsNull()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint);
+ var linkGenerator = CreateLinkGenerator(endpoint);
- // Act
- var uri = linkGenerator.GetUriByAddress(CreateHttpContext(), 0, values: null);
+ // Act
+ var uri = linkGenerator.GetUriByAddress(CreateHttpContext(), 0, values: null);
- // Assert
- Assert.Null(uri);
- }
+ // Assert
+ Assert.Null(uri);
+ }
- [Fact]
- public void GetPathByAddress_WithoutHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithoutHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- // Act
- var path = linkGenerator.GetPathByAddress(1, values: new RouteValueDictionary(new { controller = "Home", action = "Index", }));
+ // Act
+ var path = linkGenerator.GetPathByAddress(1, values: new RouteValueDictionary(new { controller = "Home", action = "Index", }));
- // Assert
- Assert.Equal("/Home/Index", path);
- }
+ // Assert
+ Assert.Equal("/Home/Index", path);
+ }
- [Fact]
- public void GetPathByAddress_WithHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- // Act
- var path = linkGenerator.GetPathByAddress(CreateHttpContext(), 1, values: new RouteValueDictionary(new { controller = "Home", action = "Index", }));
+ // Act
+ var path = linkGenerator.GetPathByAddress(CreateHttpContext(), 1, values: new RouteValueDictionary(new { controller = "Home", action = "Index", }));
- // Assert
- Assert.Equal("/Home/Index", path);
- }
+ // Assert
+ Assert.Equal("/Home/Index", path);
+ }
- [Fact]
- public void GetUriByAddress_WithoutHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetUriByAddress_WithoutHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- // Act
- var path = linkGenerator.GetUriByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
- "http",
- new HostString("example.com"));
+ // Act
+ var path = linkGenerator.GetUriByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
+ "http",
+ new HostString("example.com"));
- // Assert
- Assert.Equal("http://example.com/Home/Index", path);
- }
+ // Assert
+ Assert.Equal("http://example.com/Home/Index", path);
+ }
- [Fact]
- public void GetUriByAddress_WithHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetUriByAddress_WithHttpContext_HasMatches_ReturnsFirstSuccessfulTemplateResult()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
- // Act
- var uri = linkGenerator.GetUriByAddress(httpContext, 1, values: new RouteValueDictionary(new { controller = "Home", action = "Index", }));
+ // Act
+ var uri = linkGenerator.GetUriByAddress(httpContext, 1, values: new RouteValueDictionary(new { controller = "Home", action = "Index", }));
- // Assert
- Assert.Equal("http://example.com/Home/Index", uri);
- }
+ // Assert
+ Assert.Equal("http://example.com/Home/Index", uri);
+ }
- [Fact]
- public void GetPathByAddress_WithoutHttpContext_WithLinkOptions()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithoutHttpContext_WithLinkOptions()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- // Act
- var path = linkGenerator.GetPathByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
- options: new LinkOptions() { AppendTrailingSlash = true, });
+ // Act
+ var path = linkGenerator.GetPathByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
- // Assert
- Assert.Equal("/Home/Index/", path);
- }
+ // Assert
+ Assert.Equal("/Home/Index/", path);
+ }
- [Fact]
- public void GetPathByAddress_WithParameterTransformer()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithParameterTransformer()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- Action<IServiceCollection> configureServices = s =>
+ Action<IServiceCollection> configureServices = s =>
+ {
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
- });
- };
+ o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
+ };
- var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
- // Act
- var path = linkGenerator.GetPathByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "TestController", action = "Index", }));
+ // Act
+ var path = linkGenerator.GetPathByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "TestController", action = "Index", }));
- // Assert
- Assert.Equal("/test-controller/Index", path);
- }
+ // Assert
+ Assert.Equal("/test-controller/Index", path);
+ }
- [Fact]
- public void GetPathByAddress_WithParameterTransformer_WithLowercaseUrl()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithParameterTransformer_WithLowercaseUrl()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller:slugify}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- Action<IServiceCollection> configureServices = s =>
+ Action<IServiceCollection> configureServices = s =>
+ {
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
- });
- };
-
- var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
-
- // Act
- var path = linkGenerator.GetPathByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "TestController", action = "Index", }),
- options: new LinkOptions() { LowercaseUrls = true, });
-
- // Assert
- Assert.Equal("/test-controller/index", path);
- }
+ o.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
+ };
- [Fact]
- public void GetPathByAddress_WithHttpContext_WithLinkOptions()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ var linkGenerator = CreateLinkGenerator(configureServices, endpoint1, endpoint2);
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ // Act
+ var path = linkGenerator.GetPathByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "TestController", action = "Index", }),
+ options: new LinkOptions() { LowercaseUrls = true, });
- // Act
- var path = linkGenerator.GetPathByAddress(
- CreateHttpContext(),
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
- options: new LinkOptions() { AppendTrailingSlash = true, });
+ // Assert
+ Assert.Equal("/test-controller/index", path);
+ }
- // Assert
- Assert.Equal("/Home/Index/", path);
- }
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_WithLinkOptions()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- [Fact]
- public void GetUriByAddress_WithoutHttpContext_WithLinkOptions()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- // Act
- var path = linkGenerator.GetUriByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
- "http",
- new HostString("example.com"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("http://example.com/Home/Index/", path);
- }
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- [Fact]
- public void GetUriByAddress_WithHttpContext_WithLinkOptions()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ // Act
+ var path = linkGenerator.GetPathByAddress(
+ CreateHttpContext(),
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ // Assert
+ Assert.Equal("/Home/Index/", path);
+ }
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
+ [Fact]
+ public void GetUriByAddress_WithoutHttpContext_WithLinkOptions()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ // Act
+ var path = linkGenerator.GetUriByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
+ "http",
+ new HostString("example.com"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("http://example.com/Home/Index/", path);
+ }
- // Act
- var uri = linkGenerator.GetUriByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
- options: new LinkOptions() { AppendTrailingSlash = true, });
+ [Fact]
+ public void GetUriByAddress_WithHttpContext_WithLinkOptions()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- // Assert
- Assert.Equal("http://example.com/Home/Index/", uri);
- }
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- // Includes characters that need to be encoded
- [Fact]
- public void GetPathByAddress_WithoutHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ // Act
+ var uri = linkGenerator.GetUriByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", }),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
- // Act
- var path = linkGenerator.GetPathByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
- new PathString("/Foo/Bar?encodeme?"),
- new FragmentString("#Fragment?"));
+ // Assert
+ Assert.Equal("http://example.com/Home/Index/", uri);
+ }
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
- }
+ // Includes characters that need to be encoded
+ [Fact]
+ public void GetPathByAddress_WithoutHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- [Fact]
- public void GetLink_ParameterTransformer()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", requiredValues: new { controller = "Home", name = "Test" });
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ // Act
+ var path = linkGenerator.GetPathByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
+ new PathString("/Foo/Bar?encodeme?"),
+ new FragmentString("#Fragment?"));
+
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetLink_ParameterTransformer()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller:upper-case}/{name}", requiredValues: new { controller = "Home", name = "Test" });
- Action<IServiceCollection> configure = (s) =>
+ Action<IServiceCollection> configure = (s) =>
+ {
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
- });
- };
+ o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
+ });
+ };
- var linkGenerator = CreateLinkGenerator(configure, endpoint);
+ var linkGenerator = CreateLinkGenerator(configure, endpoint);
- // Act
- var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" });
+ // Act
+ var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test" });
- // Assert
- Assert.Equal("/HOME/Test", link);
- }
+ // Assert
+ Assert.Equal("/HOME/Test", link);
+ }
- [Fact]
- public void GetLink_ParameterTransformer_ForQueryString()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "{controller:upper-case}/{name}",
- requiredValues: new { controller = "Home", name = "Test", },
- policies: new { c = new UpperCaseParameterTransform(), });
+ [Fact]
+ public void GetLink_ParameterTransformer_ForQueryString()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "{controller:upper-case}/{name}",
+ requiredValues: new { controller = "Home", name = "Test", },
+ policies: new { c = new UpperCaseParameterTransform(), });
- Action<IServiceCollection> configure = (s) =>
+ Action<IServiceCollection> configure = (s) =>
+ {
+ s.Configure<RouteOptions>(o =>
{
- s.Configure<RouteOptions>(o =>
- {
- o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
- });
- };
+ o.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform);
+ });
+ };
- var linkGenerator = CreateLinkGenerator(configure, endpoint);
+ var linkGenerator = CreateLinkGenerator(configure, endpoint);
- // Act
- var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", });
+ // Act
+ var link = linkGenerator.GetPathByRouteValues(routeName: null, new { controller = "Home", name = "Test", c = "hithere", });
- // Assert
- Assert.Equal("/HOME/Test?c=HITHERE", link);
- }
+ // Assert
+ Assert.Equal("/HOME/Test?c=HITHERE", link);
+ }
- // Includes characters that need to be encoded
- [Fact]
- public void GetPathByAddress_WithHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ // Includes characters that need to be encoded
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- var httpContext = CreateHttpContext();
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+ var httpContext = CreateHttpContext();
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
- // Act
- var path = linkGenerator.GetPathByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
- fragment: new FragmentString("#Fragment?"));
+ // Act
+ var path = linkGenerator.GetPathByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
+ fragment: new FragmentString("#Fragment?"));
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
- }
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
+ }
- // Includes characters that need to be encoded
- [Fact]
- public void GetUriByAddress_WithoutHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- // Act
- var path = linkGenerator.GetUriByAddress(
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
- "http",
- new HostString("example.com"),
- new PathString("/Foo/Bar?encodeme?"),
- new FragmentString("#Fragment?"));
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
- }
+ // Includes characters that need to be encoded
+ [Fact]
+ public void GetUriByAddress_WithoutHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ // Act
+ var path = linkGenerator.GetUriByAddress(
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
+ "http",
+ new HostString("example.com"),
+ new PathString("/Foo/Bar?encodeme?"),
+ new FragmentString("#Fragment?"));
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", path);
+ }
- // Includes characters that need to be encoded
- [Fact]
- public void GetUriByAddress_WithHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- // Act
- var uri = linkGenerator.GetUriByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
- fragment: new FragmentString("#Fragment?"));
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", uri);
- }
+ // Includes characters that need to be encoded
+ [Fact]
+ public void GetUriByAddress_WithHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ // Act
+ var uri = linkGenerator.GetUriByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { controller = "Home", action = "In?dex", query = "some?query" }),
+ fragment: new FragmentString("#Fragment?"));
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/In%3Fdex?query=some%3Fquery#Fragment?", uri);
+ }
- [Fact]
- public void GetPathByAddress_WithHttpContext_IncludesAmbientValues()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_IncludesAmbientValues()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
- // Act
- var uri = linkGenerator.GetPathByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { action = "Index", }),
- ambientValues: new RouteValueDictionary(new { controller = "Home", }));
+ // Act
+ var uri = linkGenerator.GetPathByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { action = "Index", }),
+ ambientValues: new RouteValueDictionary(new { controller = "Home", }));
- // Assert
- Assert.Equal("/Home/Index", uri);
- }
+ // Assert
+ Assert.Equal("/Home/Index", uri);
+ }
- [Fact]
- public void GetUriByAddress_WithHttpContext_IncludesAmbientValues()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetUriByAddress_WithHttpContext_IncludesAmbientValues()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
- // Act
- var uri = linkGenerator.GetUriByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { action = "Index", }),
- ambientValues: new RouteValueDictionary(new { controller = "Home", }));
+ // Act
+ var uri = linkGenerator.GetUriByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { action = "Index", }),
+ ambientValues: new RouteValueDictionary(new { controller = "Home", }));
- // Assert
- Assert.Equal("http://example.com/Home/Index", uri);
- }
+ // Assert
+ Assert.Equal("http://example.com/Home/Index", uri);
+ }
- [Fact]
- public void GetPathByAddress_WithHttpContext_CanOverrideUriParts()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_CanOverrideUriParts()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
- var httpContext = CreateHttpContext();
- httpContext.Request.PathBase = "/Foo";
+ var httpContext = CreateHttpContext();
+ httpContext.Request.PathBase = "/Foo";
- // Act
- var uri = linkGenerator.GetPathByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", }),
- pathBase: "/");
+ // Act
+ var uri = linkGenerator.GetPathByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", }),
+ pathBase: "/");
- // Assert
- Assert.Equal("/Home/Index", uri);
- }
+ // Assert
+ Assert.Equal("/Home/Index", uri);
+ }
- [Fact]
- public void GetUriByAddress_WithHttpContext_CanOverrideUriParts()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
- httpContext.Request.PathBase = "/Foo";
-
- // Act
- var uri = linkGenerator.GetUriByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", }),
- scheme: "ftp",
- host: new HostString("example.com:5000"),
- pathBase: "/");
-
- // Assert
- Assert.Equal("ftp://example.com:5000/Home/Index", uri);
- }
+ [Fact]
+ public void GetUriByAddress_WithHttpContext_CanOverrideUriParts()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new IntMetadata(1), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
+ httpContext.Request.PathBase = "/Foo";
+
+ // Act
+ var uri = linkGenerator.GetUriByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", }),
+ scheme: "ftp",
+ host: new HostString("example.com:5000"),
+ pathBase: "/");
+
+ // Assert
+ Assert.Equal("ftp://example.com:5000/Home/Index", uri);
+ }
- [Fact]
- public void GetPathByAddress_WithHttpContext_ContextPassedToConstraint()
- {
- // Arrange
- var constraint = new TestRouteConstraint();
+ [Fact]
+ public void GetPathByAddress_WithHttpContext_ContextPassedToConstraint()
+ {
+ // Arrange
+ var constraint = new TestRouteConstraint();
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", policies: new { controller = constraint }, metadata: new object[] { new IntMetadata(1), });
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", policies: new { controller = constraint }, metadata: new object[] { new IntMetadata(1), });
- var linkGenerator = CreateLinkGenerator(endpoint1);
+ var linkGenerator = CreateLinkGenerator(endpoint1);
- var httpContext = CreateHttpContext();
- httpContext.Request.PathBase = "/Foo";
+ var httpContext = CreateHttpContext();
+ httpContext.Request.PathBase = "/Foo";
- // Act
- var uri = linkGenerator.GetPathByAddress(
- httpContext,
- 1,
- values: new RouteValueDictionary(new { action = "Index", controller = "Home", }),
- pathBase: "/");
+ // Act
+ var uri = linkGenerator.GetPathByAddress(
+ httpContext,
+ 1,
+ values: new RouteValueDictionary(new { action = "Index", controller = "Home", }),
+ pathBase: "/");
- // Assert
- Assert.Equal("/Home/Index", uri);
- Assert.True(constraint.HasHttpContext);
- }
+ // Assert
+ Assert.Equal("/Home/Index", uri);
+ Assert.True(constraint.HasHttpContext);
+ }
- private class TestRouteConstraint : IRouteConstraint
- {
- public bool HasHttpContext { get; set; }
+ private class TestRouteConstraint : IRouteConstraint
+ {
+ public bool HasHttpContext { get; set; }
- public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
- {
- HasHttpContext = (httpContext != null);
- return true;
- }
+ public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
+ {
+ HasHttpContext = (httpContext != null);
+ return true;
}
+ }
- [Fact]
- public void GetTemplateBinder_CanCache()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var dataSource = new DynamicEndpointDataSource(endpoint1);
+ [Fact]
+ public void GetTemplateBinder_CanCache()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var dataSource = new DynamicEndpointDataSource(endpoint1);
- var linkGenerator = CreateLinkGenerator(dataSources: new[] { dataSource });
+ var linkGenerator = CreateLinkGenerator(dataSources: new[] { dataSource });
- var expected = linkGenerator.GetTemplateBinder(endpoint1);
+ var expected = linkGenerator.GetTemplateBinder(endpoint1);
- // Act
- var actual = linkGenerator.GetTemplateBinder(endpoint1);
+ // Act
+ var actual = linkGenerator.GetTemplateBinder(endpoint1);
- // Assert
- Assert.Same(expected, actual);
- }
+ // Assert
+ Assert.Same(expected, actual);
+ }
- [Fact]
- public void GetTemplateBinder_CanClearCache()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var dataSource = new DynamicEndpointDataSource(endpoint1);
+ [Fact]
+ public void GetTemplateBinder_CanClearCache()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var dataSource = new DynamicEndpointDataSource(endpoint1);
- var linkGenerator = CreateLinkGenerator(dataSources: new[] { dataSource });
- var original = linkGenerator.GetTemplateBinder(endpoint1);
+ var linkGenerator = CreateLinkGenerator(dataSources: new[] { dataSource });
+ var original = linkGenerator.GetTemplateBinder(endpoint1);
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- dataSource.AddEndpoint(endpoint2);
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ dataSource.AddEndpoint(endpoint2);
- // Act
- var actual = linkGenerator.GetTemplateBinder(endpoint1);
+ // Act
+ var actual = linkGenerator.GetTemplateBinder(endpoint1);
- // Assert
- Assert.NotSame(original, actual);
- }
+ // Assert
+ Assert.NotSame(original, actual);
+ }
- [Theory]
- [InlineData(new string[] { }, new string[] { }, "/")]
- [InlineData(new string[] { "id" }, new string[] { "3" }, "/Home/Index/3")]
- [InlineData(new string[] { "custom" }, new string[] { "Custom" }, "/?custom=Custom")]
- public void GetPathByRouteValues_UsesFirstTemplateThatSucceeds(string[] routeNames, string[] routeValues, string expectedPath)
- {
- // Arrange
- var endpointControllerAction = EndpointFactory.CreateRouteEndpoint(
- "Home/Index",
- order: 3,
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpointController = EndpointFactory.CreateRouteEndpoint(
- "Home",
- order: 2,
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpointEmpty = EndpointFactory.CreateRouteEndpoint(
- "",
- order: 1,
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- // This endpoint should be used to generate the link when an id is present
- var endpointControllerActionParameter = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- order: 0,
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpointControllerAction, endpointController, endpointEmpty, endpointControllerActionParameter);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index" });
-
- var values = new RouteValueDictionary();
- for (int i = 0; i < routeNames.Length; i++)
- {
- values[routeNames[i]] = routeValues[i];
- }
+ [Theory]
+ [InlineData(new string[] { }, new string[] { }, "/")]
+ [InlineData(new string[] { "id" }, new string[] { "3" }, "/Home/Index/3")]
+ [InlineData(new string[] { "custom" }, new string[] { "Custom" }, "/?custom=Custom")]
+ public void GetPathByRouteValues_UsesFirstTemplateThatSucceeds(string[] routeNames, string[] routeValues, string expectedPath)
+ {
+ // Arrange
+ var endpointControllerAction = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index",
+ order: 3,
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpointController = EndpointFactory.CreateRouteEndpoint(
+ "Home",
+ order: 2,
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpointEmpty = EndpointFactory.CreateRouteEndpoint(
+ "",
+ order: 1,
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ // This endpoint should be used to generate the link when an id is present
+ var endpointControllerActionParameter = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ order: 0,
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpointControllerAction, endpointController, endpointEmpty, endpointControllerActionParameter);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index" });
+
+ var values = new RouteValueDictionary();
+ for (int i = 0; i < routeNames.Length; i++)
+ {
+ values[routeNames[i]] = routeValues[i];
+ }
+
+ // Act
+ var generatedPath = linkGenerator.GetPathByRouteValues(
+ httpContext,
+ routeName: null,
+ values: values);
+
+ // Assert
+ Assert.Equal(expectedPath, generatedPath);
+ }
- // Act
- var generatedPath = linkGenerator.GetPathByRouteValues(
- httpContext,
- routeName: null,
- values: values);
+ [Theory]
+ [InlineData(new string[] { }, new string[] { }, "/")]
+ [InlineData(new string[] { "id" }, new string[] { "3" }, "/Home/Index/3")]
+ [InlineData(new string[] { "custom" }, new string[] { "Custom" }, "/?custom=Custom")]
+ [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Login", "3" }, "/Home/Login/3")]
+ [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Fake", "3" }, null)]
+ public void GetPathByRouteValues_ParameterMatchesRequireValues_HasAmbientValues(string[] routeNames, string[] routeValues, string expectedPath)
+ {
+ // Arrange
+ var homeIndex = EndpointFactory.CreateRouteEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var homeLogin = EndpointFactory.CreateRouteEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Login", });
- // Assert
- Assert.Equal(expectedPath, generatedPath);
- }
+ var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", });
- [Theory]
- [InlineData(new string[] { }, new string[] { }, "/")]
- [InlineData(new string[] { "id" }, new string[] { "3" }, "/Home/Index/3")]
- [InlineData(new string[] { "custom" }, new string[] { "Custom" }, "/?custom=Custom")]
- [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Login", "3" }, "/Home/Login/3")]
- [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Fake", "3" }, null)]
- public void GetPathByRouteValues_ParameterMatchesRequireValues_HasAmbientValues(string[] routeNames, string[] routeValues, string expectedPath)
+ var values = new RouteValueDictionary();
+ for (int i = 0; i < routeNames.Length; i++)
{
- // Arrange
- var homeIndex = EndpointFactory.CreateRouteEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var homeLogin = EndpointFactory.CreateRouteEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Login", });
-
- var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", });
-
- var values = new RouteValueDictionary();
- for (int i = 0; i < routeNames.Length; i++)
- {
- values[routeNames[i]] = routeValues[i];
- }
+ values[routeNames[i]] = routeValues[i];
+ }
- // Act
- var generatedPath = linkGenerator.GetPathByRouteValues(
- httpContext,
- routeName: null,
- values: values);
+ // Act
+ var generatedPath = linkGenerator.GetPathByRouteValues(
+ httpContext,
+ routeName: null,
+ values: values);
- // Assert
- Assert.Equal(expectedPath, generatedPath);
- }
+ // Assert
+ Assert.Equal(expectedPath, generatedPath);
+ }
- [Theory]
- [InlineData(new string[] { }, new string[] { }, null)]
- [InlineData(new string[] { "id" }, new string[] { "3" }, null)]
- [InlineData(new string[] { "custom" }, new string[] { "Custom" }, null)]
- [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Login", "3" }, "/Home/Login/3")]
- [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Fake", "3" }, null)]
- public void GetPathByRouteValues_ParameterMatchesRequireValues_NoAmbientValues(string[] routeNames, string[] routeValues, string expectedPath)
- {
- // Arrange
- var homeIndex = EndpointFactory.CreateRouteEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var homeLogin = EndpointFactory.CreateRouteEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Login", });
-
- var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin);
-
- var httpContext = CreateHttpContext();
-
- var values = new RouteValueDictionary();
- for (int i = 0; i < routeNames.Length; i++)
- {
- values[routeNames[i]] = routeValues[i];
- }
+ [Theory]
+ [InlineData(new string[] { }, new string[] { }, null)]
+ [InlineData(new string[] { "id" }, new string[] { "3" }, null)]
+ [InlineData(new string[] { "custom" }, new string[] { "Custom" }, null)]
+ [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Login", "3" }, "/Home/Login/3")]
+ [InlineData(new string[] { "controller", "action", "id" }, new string[] { "Home", "Fake", "3" }, null)]
+ public void GetPathByRouteValues_ParameterMatchesRequireValues_NoAmbientValues(string[] routeNames, string[] routeValues, string expectedPath)
+ {
+ // Arrange
+ var homeIndex = EndpointFactory.CreateRouteEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var homeLogin = EndpointFactory.CreateRouteEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Login", });
- // Act
- var generatedPath = linkGenerator.GetPathByRouteValues(
- httpContext,
- routeName: null,
- values: values);
+ var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin);
- // Assert
- Assert.Equal(expectedPath, generatedPath);
- }
+ var httpContext = CreateHttpContext();
- protected override void AddAdditionalServices(IServiceCollection services)
+ var values = new RouteValueDictionary();
+ for (int i = 0; i < routeNames.Length; i++)
{
- services.AddSingleton<IEndpointAddressScheme<int>, IntAddressScheme>();
+ values[routeNames[i]] = routeValues[i];
}
- private class IntAddressScheme : IEndpointAddressScheme<int>
- {
- private readonly EndpointDataSource _dataSource;
+ // Act
+ var generatedPath = linkGenerator.GetPathByRouteValues(
+ httpContext,
+ routeName: null,
+ values: values);
- public IntAddressScheme(EndpointDataSource dataSource)
- {
- _dataSource = dataSource;
- }
+ // Assert
+ Assert.Equal(expectedPath, generatedPath);
+ }
- public IEnumerable<Endpoint> FindEndpoints(int address)
- {
- return _dataSource.Endpoints.Where(e => e.Metadata.GetMetadata<IntMetadata>().Value == address);
- }
+ protected override void AddAdditionalServices(IServiceCollection services)
+ {
+ services.AddSingleton<IEndpointAddressScheme<int>, IntAddressScheme>();
+ }
+
+ private class IntAddressScheme : IEndpointAddressScheme<int>
+ {
+ private readonly EndpointDataSource _dataSource;
+
+ public IntAddressScheme(EndpointDataSource dataSource)
+ {
+ _dataSource = dataSource;
}
- private class IntMetadata
+ public IEnumerable<Endpoint> FindEndpoints(int address)
{
- public IntMetadata(int value)
- {
- Value = value;
- }
- public int Value { get; }
+ return _dataSource.Endpoints.Where(e => e.Metadata.GetMetadata<IntMetadata>().Value == address);
+ }
+ }
+
+ private class IntMetadata
+ {
+ public IntMetadata(int value)
+ {
+ Value = value;
}
+ public int Value { get; }
}
}
diff --git a/src/Http/Routing/test/UnitTests/DefaultLinkParserTest.cs b/src/Http/Routing/test/UnitTests/DefaultLinkParserTest.cs
index 08ac1eebfb..46a8170876 100644
--- a/src/Http/Routing/test/UnitTests/DefaultLinkParserTest.cs
+++ b/src/Http/Routing/test/UnitTests/DefaultLinkParserTest.cs
@@ -13,180 +13,179 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// Tests LinkParser functionality using ParsePathByAddress - see tests for the extension
+// methods for more E2E tests.
+//
+// Does not cover template processing in detail, those scenarios are validated by other tests.
+public class DefaultLinkParserTest : LinkParserTestBase
{
- // Tests LinkParser functionality using ParsePathByAddress - see tests for the extension
- // methods for more E2E tests.
- //
- // Does not cover template processing in detail, those scenarios are validated by other tests.
- public class DefaultLinkParserTest : LinkParserTestBase
+ [Fact]
+ public void ParsePathByAddresss_NoMatchingEndpoint_ReturnsNull()
{
- [Fact]
- public void ParsePathByAddresss_NoMatchingEndpoint_ReturnsNull()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", displayName: "Test1", metadata: new object[] { new IntMetadata(1), });
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", displayName: "Test1", metadata: new object[] { new IntMetadata(1), });
- var sink = new TestSink();
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- var parser = CreateLinkParser(services => { services.AddSingleton<ILoggerFactory>(loggerFactory); }, endpoint);
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ var parser = CreateLinkParser(services => { services.AddSingleton<ILoggerFactory>(loggerFactory); }, endpoint);
- // Act
- var values = parser.ParsePathByAddress(0, "/Home/Index/17");
+ // Act
+ var values = parser.ParsePathByAddress(0, "/Home/Index/17");
- // Assert
- Assert.Null(values);
+ // Assert
+ Assert.Null(values);
- Assert.Collection(
- sink.Writes,
- w => Assert.Equal("No endpoints found for address 0", w.Message));
- }
+ Assert.Collection(
+ sink.Writes,
+ w => Assert.Equal("No endpoints found for address 0", w.Message));
+ }
- [Fact]
- public void ParsePathByAddresss_HasMatches_ReturnsNullWhenParsingFails()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", displayName: "Test1", metadata: new object[] { new IntMetadata(1), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2}", displayName: "Test2", metadata: new object[] { new IntMetadata(0), });
+ [Fact]
+ public void ParsePathByAddresss_HasMatches_ReturnsNullWhenParsingFails()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", displayName: "Test1", metadata: new object[] { new IntMetadata(1), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2}", displayName: "Test2", metadata: new object[] { new IntMetadata(0), });
- var sink = new TestSink();
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- var parser = CreateLinkParser(services => { services.AddSingleton<ILoggerFactory>(loggerFactory); }, endpoint1, endpoint2);
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ var parser = CreateLinkParser(services => { services.AddSingleton<ILoggerFactory>(loggerFactory); }, endpoint1, endpoint2);
- // Act
- var values = parser.ParsePathByAddress(0, "/");
+ // Act
+ var values = parser.ParsePathByAddress(0, "/");
- // Assert
- Assert.Null(values);
+ // Assert
+ Assert.Null(values);
- Assert.Collection(
- sink.Writes,
- w => Assert.Equal("Found the endpoints Test2 for address 0", w.Message),
- w => Assert.Equal("Path parsing failed for endpoints Test2 and URI path /", w.Message));
- }
+ Assert.Collection(
+ sink.Writes,
+ w => Assert.Equal("Found the endpoints Test2 for address 0", w.Message),
+ w => Assert.Equal("Path parsing failed for endpoints Test2 and URI path /", w.Message));
+ }
- [Fact]
- public void ParsePathByAddresss_HasMatches_ReturnsFirstSuccessfulParse()
- {
- // Arrange
- var endpoint0 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}", displayName: "Test1",metadata: new object[] { new IntMetadata(0), });
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", displayName: "Test2", metadata: new object[] { new IntMetadata(0), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2}", displayName: "Test3", metadata: new object[] { new IntMetadata(0), });
+ [Fact]
+ public void ParsePathByAddresss_HasMatches_ReturnsFirstSuccessfulParse()
+ {
+ // Arrange
+ var endpoint0 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}", displayName: "Test1", metadata: new object[] { new IntMetadata(0), });
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", displayName: "Test2", metadata: new object[] { new IntMetadata(0), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2}", displayName: "Test3", metadata: new object[] { new IntMetadata(0), });
- var sink = new TestSink();
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- var parser = CreateLinkParser(services => { services.AddSingleton<ILoggerFactory>(loggerFactory); }, endpoint0, endpoint1, endpoint2);
+ var sink = new TestSink();
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ var parser = CreateLinkParser(services => { services.AddSingleton<ILoggerFactory>(loggerFactory); }, endpoint0, endpoint1, endpoint2);
- // Act
- var values = parser.ParsePathByAddress(0, "/Home/Index/17");
+ // Act
+ var values = parser.ParsePathByAddress(0, "/Home/Index/17");
- // Assert
- MatcherAssert.AssertRouteValuesEqual(new { controller= "Home", action = "Index", id = "17" }, values);
+ // Assert
+ MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", id = "17" }, values);
- Assert.Collection(
- sink.Writes,
- w => Assert.Equal("Found the endpoints Test1, Test2, Test3 for address 0", w.Message),
- w => Assert.Equal("Path parsing succeeded for endpoint Test2 and URI path /Home/Index/17", w.Message));
- }
+ Assert.Collection(
+ sink.Writes,
+ w => Assert.Equal("Found the endpoints Test1, Test2, Test3 for address 0", w.Message),
+ w => Assert.Equal("Path parsing succeeded for endpoint Test2 and URI path /Home/Index/17", w.Message));
+ }
- [Fact]
- public void ParsePathByAddresss_HasMatches_IncludesDefaults()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller=Home}/{action=Index}/{id?}", metadata: new object[] { new IntMetadata(0), });
+ [Fact]
+ public void ParsePathByAddresss_HasMatches_IncludesDefaults()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller=Home}/{action=Index}/{id?}", metadata: new object[] { new IntMetadata(0), });
- var parser = CreateLinkParser(endpoint);
+ var parser = CreateLinkParser(endpoint);
- // Act
- var values = parser.ParsePathByAddress(0, "/");
+ // Act
+ var values = parser.ParsePathByAddress(0, "/");
- // Assert
- MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", }, values);
- }
+ // Assert
+ MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", }, values);
+ }
- [Fact]
- public void ParsePathByAddresss_HasMatches_RunsConstraints()
- {
- // Arrange
- var endpoint0 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id:int}", metadata: new object[] { new IntMetadata(0), });
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2:alpha}", metadata: new object[] { new IntMetadata(0), });
+ [Fact]
+ public void ParsePathByAddresss_HasMatches_RunsConstraints()
+ {
+ // Arrange
+ var endpoint0 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id:int}", metadata: new object[] { new IntMetadata(0), });
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2:alpha}", metadata: new object[] { new IntMetadata(0), });
- var parser = CreateLinkParser(endpoint0, endpoint1);
+ var parser = CreateLinkParser(endpoint0, endpoint1);
- // Act
- var values = parser.ParsePathByAddress(0, "/Home/Index/abc");
+ // Act
+ var values = parser.ParsePathByAddress(0, "/Home/Index/abc");
- // Assert
- MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", id2 = "abc" }, values);
- }
+ // Assert
+ MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", id2 = "abc" }, values);
+ }
- [Fact]
- public void GetRoutePatternMatcher_CanCache()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var dataSource = new DynamicEndpointDataSource(endpoint1);
+ [Fact]
+ public void GetRoutePatternMatcher_CanCache()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var dataSource = new DynamicEndpointDataSource(endpoint1);
- var parser = CreateLinkParser(dataSources: new[] { dataSource });
+ var parser = CreateLinkParser(dataSources: new[] { dataSource });
- var expected = parser.GetMatcherState(endpoint1);
+ var expected = parser.GetMatcherState(endpoint1);
- // Act
- var actual = parser.GetMatcherState(endpoint1);
+ // Act
+ var actual = parser.GetMatcherState(endpoint1);
- // Assert
- Assert.Same(expected.Matcher, actual.Matcher);
- Assert.Same(expected.Constraints, actual.Constraints);
- }
+ // Assert
+ Assert.Same(expected.Matcher, actual.Matcher);
+ Assert.Same(expected.Constraints, actual.Constraints);
+ }
- [Fact]
- public void GetRoutePatternMatcherr_CanClearCache()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- var dataSource = new DynamicEndpointDataSource(endpoint1);
+ [Fact]
+ public void GetRoutePatternMatcherr_CanClearCache()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ var dataSource = new DynamicEndpointDataSource(endpoint1);
- var parser = CreateLinkParser(dataSources: new[] { dataSource });
- var original = parser.GetMatcherState(endpoint1);
+ var parser = CreateLinkParser(dataSources: new[] { dataSource });
+ var original = parser.GetMatcherState(endpoint1);
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
- dataSource.AddEndpoint(endpoint2);
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new IntMetadata(1), });
+ dataSource.AddEndpoint(endpoint2);
- // Act
- var actual = parser.GetMatcherState(endpoint1);
+ // Act
+ var actual = parser.GetMatcherState(endpoint1);
- // Assert
- Assert.NotSame(original.Matcher, actual.Matcher);
- Assert.NotSame(original.Constraints, actual.Constraints);
- }
+ // Assert
+ Assert.NotSame(original.Matcher, actual.Matcher);
+ Assert.NotSame(original.Constraints, actual.Constraints);
+ }
- protected override void AddAdditionalServices(IServiceCollection services)
+ protected override void AddAdditionalServices(IServiceCollection services)
+ {
+ services.AddSingleton<IEndpointAddressScheme<int>, IntAddressScheme>();
+ }
+
+ private class IntAddressScheme : IEndpointAddressScheme<int>
+ {
+ private readonly EndpointDataSource _dataSource;
+
+ public IntAddressScheme(EndpointDataSource dataSource)
{
- services.AddSingleton<IEndpointAddressScheme<int>, IntAddressScheme>();
+ _dataSource = dataSource;
}
- private class IntAddressScheme : IEndpointAddressScheme<int>
+ public IEnumerable<Endpoint> FindEndpoints(int address)
{
- private readonly EndpointDataSource _dataSource;
-
- public IntAddressScheme(EndpointDataSource dataSource)
- {
- _dataSource = dataSource;
- }
-
- public IEnumerable<Endpoint> FindEndpoints(int address)
- {
- return _dataSource.Endpoints.Where(e => e.Metadata.GetMetadata<IntMetadata>().Value == address);
- }
+ return _dataSource.Endpoints.Where(e => e.Metadata.GetMetadata<IntMetadata>().Value == address);
}
+ }
- private class IntMetadata
+ private class IntMetadata
+ {
+ public IntMetadata(int value)
{
- public IntMetadata(int value)
- {
- Value = value;
- }
- public int Value { get; }
+ Value = value;
}
+ public int Value { get; }
}
}
diff --git a/src/Http/Routing/test/UnitTests/DefaultParameterPolicyFactoryTest.cs b/src/Http/Routing/test/UnitTests/DefaultParameterPolicyFactoryTest.cs
index 9cf2f94ac0..89e1abf4e4 100644
--- a/src/Http/Routing/test/UnitTests/DefaultParameterPolicyFactoryTest.cs
+++ b/src/Http/Routing/test/UnitTests/DefaultParameterPolicyFactoryTest.cs
@@ -10,524 +10,523 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class DefaultParameterPolicyFactoryTest
{
- public class DefaultParameterPolicyFactoryTest
+ [Fact]
+ public void Create_ThrowsException_IfNoConstraintOrParameterPolicy_FoundInMap()
{
- [Fact]
- public void Create_ThrowsException_IfNoConstraintOrParameterPolicy_FoundInMap()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
-
- // Act
- var exception = Assert.Throws<InvalidOperationException>(
- () => factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), @"notpresent(\d+)"));
-
- // Assert
- Assert.Equal(
- "The constraint reference 'notpresent' could not be resolved to a type. " +
- $"Register the constraint type with '{typeof(RouteOptions)}.{nameof(RouteOptions.ConstraintMap)}'.",
- exception.Message);
- }
+ // Arrange
+ var factory = GetParameterPolicyFactory();
+
+ // Act
+ var exception = Assert.Throws<InvalidOperationException>(
+ () => factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), @"notpresent(\d+)"));
+
+ // Assert
+ Assert.Equal(
+ "The constraint reference 'notpresent' could not be resolved to a type. " +
+ $"Register the constraint type with '{typeof(RouteOptions)}.{nameof(RouteOptions.ConstraintMap)}'.",
+ exception.Message);
+ }
- [Fact]
- public void Create_ThrowsException_OnInvalidType()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("bad", typeof(string));
+ [Fact]
+ public void Create_ThrowsException_OnInvalidType()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("bad", typeof(string));
- var services = new ServiceCollection();
+ var services = new ServiceCollection();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var exception = Assert.Throws<RouteCreationException>(
- () => factory.Create(RoutePatternFactory.ParameterPart("id"), @"bad"));
+ // Act
+ var exception = Assert.Throws<RouteCreationException>(
+ () => factory.Create(RoutePatternFactory.ParameterPart("id"), @"bad"));
- // Assert
- Assert.Equal(
- $"The constraint type '{typeof(string)}' which is mapped to constraint key 'bad' must implement the '{nameof(IParameterPolicy)}' interface.",
- exception.Message);
- }
+ // Assert
+ Assert.Equal(
+ $"The constraint type '{typeof(string)}' which is mapped to constraint key 'bad' must implement the '{nameof(IParameterPolicy)}' interface.",
+ exception.Message);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromRoutePattern_String()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromRoutePattern_String()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- var parameter = RoutePatternFactory.ParameterPart(
- "id",
- @default: null,
- parameterKind: RoutePatternParameterKind.Standard,
- parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), });
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), });
- // Act
- var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
+ // Act
+ var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
- // Assert
- Assert.IsType<IntRouteConstraint>(parameterPolicy);
- }
+ // Assert
+ Assert.IsType<IntRouteConstraint>(parameterPolicy);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromRoutePattern_String_Optional()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromRoutePattern_String_Optional()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- var parameter = RoutePatternFactory.ParameterPart(
- "id",
- @default: null,
- parameterKind: RoutePatternParameterKind.Optional,
- parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), });
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Optional,
+ parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), });
- // Act
- var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
+ // Act
+ var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
- // Assert
- var optionalConstraint = Assert.IsType<OptionalRouteConstraint>(parameterPolicy);
- Assert.IsType<IntRouteConstraint>(optionalConstraint.InnerConstraint);
- }
+ // Assert
+ var optionalConstraint = Assert.IsType<OptionalRouteConstraint>(parameterPolicy);
+ Assert.IsType<IntRouteConstraint>(optionalConstraint.InnerConstraint);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- var parameter = RoutePatternFactory.ParameterPart(
- "id",
- @default: null,
- parameterKind: RoutePatternParameterKind.Standard,
- parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), });
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), });
- // Act
- var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
+ // Act
+ var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
- // Assert
- Assert.IsType<IntRouteConstraint>(parameterPolicy);
- }
+ // Assert
+ Assert.IsType<IntRouteConstraint>(parameterPolicy);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint_Optional()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint_Optional()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- var parameter = RoutePatternFactory.ParameterPart(
- "id",
- @default: null,
- parameterKind: RoutePatternParameterKind.Optional,
- parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), });
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Optional,
+ parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), });
- // Act
- var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
+ // Act
+ var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
- // Assert
- var optionalConstraint = Assert.IsType<OptionalRouteConstraint>(parameterPolicy);
- Assert.IsType<IntRouteConstraint>(optionalConstraint.InnerConstraint);
- }
+ // Assert
+ var optionalConstraint = Assert.IsType<OptionalRouteConstraint>(parameterPolicy);
+ Assert.IsType<IntRouteConstraint>(optionalConstraint.InnerConstraint);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromRoutePattern_ParameterPolicy()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromRoutePattern_ParameterPolicy()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- var parameter = RoutePatternFactory.ParameterPart(
- "id",
- @default: null,
- parameterKind: RoutePatternParameterKind.Standard,
- parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new CustomParameterPolicy()), });
+ var parameter = RoutePatternFactory.ParameterPart(
+ "id",
+ @default: null,
+ parameterKind: RoutePatternParameterKind.Standard,
+ parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new CustomParameterPolicy()), });
- // Act
- var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
+ // Act
+ var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]);
- // Assert
- Assert.IsType<CustomParameterPolicy>(parameterPolicy);
- }
+ // Assert
+ Assert.IsType<CustomParameterPolicy>(parameterPolicy);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "int");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "int");
- // Assert
- Assert.IsType<IntRouteConstraint>(parameterPolicy);
- }
+ // Assert
+ Assert.IsType<IntRouteConstraint>(parameterPolicy);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraintWithArgument()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraintWithArgument()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "range(1,20)");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "range(1,20)");
- // Assert
- var constraint = Assert.IsType<RangeRouteConstraint>(parameterPolicy);
- Assert.Equal(1, constraint.Min);
- Assert.Equal(20, constraint.Max);
- }
+ // Assert
+ var constraint = Assert.IsType<RangeRouteConstraint>(parameterPolicy);
+ Assert.Equal(1, constraint.Min);
+ Assert.Equal(20, constraint.Max);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint_Optional()
- {
- // Arrange
- var factory = GetParameterPolicyFactory();
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint_Optional()
+ {
+ // Arrange
+ var factory = GetParameterPolicyFactory();
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "int");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "int");
- // Assert
- var optionalConstraint = Assert.IsType<OptionalRouteConstraint>(parameterPolicy);
- Assert.IsType<IntRouteConstraint>(optionalConstraint.InnerConstraint);
- }
+ // Assert
+ var optionalConstraint = Assert.IsType<OptionalRouteConstraint>(parameterPolicy);
+ Assert.IsType<IntRouteConstraint>(optionalConstraint.InnerConstraint);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy));
- var services = new ServiceCollection();
- services.AddTransient<CustomParameterPolicy>();
+ var services = new ServiceCollection();
+ services.AddTransient<CustomParameterPolicy>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy");
- // Assert
- Assert.IsType<CustomParameterPolicy>(parameterPolicy);
- }
+ // Assert
+ Assert.IsType<CustomParameterPolicy>(parameterPolicy);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndServices()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithArguments));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndServices()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithArguments));
- var services = new ServiceCollection();
- services.AddTransient<ITestService, TestService>();
+ var services = new ServiceCollection();
+ services.AddTransient<ITestService, TestService>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20)");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20)");
- // Assert
- var constraint = Assert.IsType<CustomParameterPolicyWithArguments>(parameterPolicy);
- Assert.Equal(20, constraint.Count);
- Assert.NotNull(constraint.TestService);
- }
+ // Assert
+ var constraint = Assert.IsType<CustomParameterPolicyWithArguments>(parameterPolicy);
+ Assert.Equal(20, constraint.Count);
+ Assert.NotNull(constraint.TestService);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndMultipleServices()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultipleArguments));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndMultipleServices()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultipleArguments));
- var services = new ServiceCollection();
- services.AddTransient<ITestService, TestService>();
+ var services = new ServiceCollection();
+ services.AddTransient<ITestService, TestService>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20,-1)");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20,-1)");
- // Assert
- var constraint = Assert.IsType<CustomParameterPolicyWithMultipleArguments>(parameterPolicy);
- Assert.Equal(20, constraint.First);
- Assert.Equal(-1, constraint.Second);
- Assert.NotNull(constraint.TestService1);
- Assert.NotNull(constraint.TestService2);
- }
+ // Assert
+ var constraint = Assert.IsType<CustomParameterPolicyWithMultipleArguments>(parameterPolicy);
+ Assert.Equal(20, constraint.First);
+ Assert.Equal(-1, constraint.Second);
+ Assert.NotNull(constraint.TestService1);
+ Assert.NotNull(constraint.TestService2);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithOnlyServiceArguments()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithOnlyServiceArguments));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithOnlyServiceArguments()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithOnlyServiceArguments));
- var services = new ServiceCollection();
- services.AddTransient<ITestService, TestService>();
+ var services = new ServiceCollection();
+ services.AddTransient<ITestService, TestService>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy");
- // Assert
- var constraint = Assert.IsType<CustomParameterPolicyWithOnlyServiceArguments>(parameterPolicy);
- Assert.NotNull(constraint.TestService1);
- Assert.NotNull(constraint.TestService2);
- }
+ // Assert
+ var constraint = Assert.IsType<CustomParameterPolicyWithOnlyServiceArguments>(parameterPolicy);
+ Assert.NotNull(constraint.TestService1);
+ Assert.NotNull(constraint.TestService2);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithMultipleMatchingCtors()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultpleCtors));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithMultipleMatchingCtors()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultpleCtors));
- var services = new ServiceCollection();
- services.AddTransient<ITestService, TestService>();
+ var services = new ServiceCollection();
+ services.AddTransient<ITestService, TestService>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(1)");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(1)");
- // Assert
- var constraint = Assert.IsType<CustomParameterPolicyWithMultpleCtors>(parameterPolicy);
- Assert.NotNull(constraint.TestService);
- Assert.Equal(1, constraint.Count);
- }
+ // Assert
+ var constraint = Assert.IsType<CustomParameterPolicyWithMultpleCtors>(parameterPolicy);
+ Assert.NotNull(constraint.TestService);
+ Assert.Equal(1, constraint.Count);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithAmbigiousMatchingCtors()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithAmbigiousMultpleCtors));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithAmbigiousMatchingCtors()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithAmbigiousMultpleCtors));
- var services = new ServiceCollection();
- services.AddTransient<ITestService, TestService>();
+ var services = new ServiceCollection();
+ services.AddTransient<ITestService, TestService>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var exception = Assert.Throws<RouteCreationException>(
- () => factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(1)"));
+ // Act
+ var exception = Assert.Throws<RouteCreationException>(
+ () => factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(1)"));
- // Assert
- Assert.Equal($"The constructor to use for activating the constraint type '{nameof(CustomParameterPolicyWithAmbigiousMultpleCtors)}' is ambiguous. "
- + "Multiple constructors were found with the following number of parameters: 2.", exception.Message);
- }
+ // Assert
+ Assert.Equal($"The constructor to use for activating the constraint type '{nameof(CustomParameterPolicyWithAmbigiousMultpleCtors)}' is ambiguous. "
+ + "Multiple constructors were found with the following number of parameters: 2.", exception.Message);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithSingleArgumentAndServiceArgument()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("regex-service", typeof(RegexInlineRouteConstraintWithService));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithSingleArgumentAndServiceArgument()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("regex-service", typeof(RegexInlineRouteConstraintWithService));
- var services = new ServiceCollection();
- services.AddTransient<ITestService, TestService>();
+ var services = new ServiceCollection();
+ services.AddTransient<ITestService, TestService>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), @"regex-service(\\d{1,2})");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), @"regex-service(\\d{1,2})");
- // Assert
- var constraint = Assert.IsType<RegexInlineRouteConstraintWithService>(parameterPolicy);
- Assert.NotNull(constraint.TestService);
- Assert.Equal("\\\\d{1,2}", constraint.Constraint.ToString());
- }
+ // Assert
+ var constraint = Assert.IsType<RegexInlineRouteConstraintWithService>(parameterPolicy);
+ Assert.NotNull(constraint.TestService);
+ Assert.Equal("\\\\d{1,2}", constraint.Constraint.ToString());
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndUnresolvedServices_Throw()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithArguments));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndUnresolvedServices_Throw()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithArguments));
- var services = new ServiceCollection();
+ var services = new ServiceCollection();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var exception = Assert.Throws<RouteCreationException>(
- () => factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20)"));
+ // Act
+ var exception = Assert.Throws<RouteCreationException>(
+ () => factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20)"));
- // Assert
- var inner = Assert.IsType<InvalidOperationException>(exception.InnerException);
- Assert.Equal($"No service for type '{typeof(ITestService).FullName}' has been registered.", inner.Message);
- }
+ // Assert
+ var inner = Assert.IsType<InvalidOperationException>(exception.InnerException);
+ Assert.Equal($"No service for type '{typeof(ITestService).FullName}' has been registered.", inner.Message);
+ }
- [Fact]
- public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy_Optional()
- {
- // Arrange
- var options = new RouteOptions();
- options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy));
+ [Fact]
+ public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy_Optional()
+ {
+ // Arrange
+ var options = new RouteOptions();
+ options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy));
- var services = new ServiceCollection();
- services.AddTransient<CustomParameterPolicy>();
+ var services = new ServiceCollection();
+ services.AddTransient<CustomParameterPolicy>();
- var factory = GetParameterPolicyFactory(options, services);
+ var factory = GetParameterPolicyFactory(options, services);
- // Act
- var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy");
+ // Act
+ var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy");
- // Assert
- Assert.IsType<CustomParameterPolicy>(parameterPolicy);
- }
+ // Assert
+ Assert.IsType<CustomParameterPolicy>(parameterPolicy);
+ }
- private DefaultParameterPolicyFactory GetParameterPolicyFactory(
- RouteOptions options = null,
- ServiceCollection services = null)
+ private DefaultParameterPolicyFactory GetParameterPolicyFactory(
+ RouteOptions options = null,
+ ServiceCollection services = null)
+ {
+ if (options == null)
{
- if (options == null)
- {
- options = new RouteOptions();
- }
-
- if (services == null)
- {
- services = new ServiceCollection();
- }
-
- return new DefaultParameterPolicyFactory(
- Options.Create(options),
- services.BuildServiceProvider());
+ options = new RouteOptions();
}
- private class TestRouteConstraint : IRouteConstraint
+ if (services == null)
{
- private TestRouteConstraint() { }
-
- public HttpContext HttpContext { get; private set; }
- public IRouter Route { get; private set; }
- public string RouteKey { get; private set; }
- public RouteValueDictionary Values { get; private set; }
- public RouteDirection RouteDirection { get; private set; }
-
- public static TestRouteConstraint Create()
- {
- return new TestRouteConstraint();
- }
-
- public bool Match(
- HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- HttpContext = httpContext;
- Route = route;
- RouteKey = routeKey;
- Values = values;
- RouteDirection = routeDirection;
- return false;
- }
+ services = new ServiceCollection();
}
- }
- public class CustomParameterPolicy : IParameterPolicy
- {
+ return new DefaultParameterPolicyFactory(
+ Options.Create(options),
+ services.BuildServiceProvider());
}
- public class CustomParameterPolicyWithArguments : IParameterPolicy
+ private class TestRouteConstraint : IRouteConstraint
{
- public CustomParameterPolicyWithArguments(ITestService testService, int count)
- {
- TestService = testService;
- Count = count;
- }
+ private TestRouteConstraint() { }
- public ITestService TestService { get; }
- public int Count { get; }
- }
+ public HttpContext HttpContext { get; private set; }
+ public IRouter Route { get; private set; }
+ public string RouteKey { get; private set; }
+ public RouteValueDictionary Values { get; private set; }
+ public RouteDirection RouteDirection { get; private set; }
- public class CustomParameterPolicyWithMultpleCtors : IParameterPolicy
- {
- public CustomParameterPolicyWithMultpleCtors(ITestService testService, int count)
+ public static TestRouteConstraint Create()
{
- TestService = testService;
- Count = count;
+ return new TestRouteConstraint();
}
- public CustomParameterPolicyWithMultpleCtors(int count)
- : this(testService: null, count)
+ public bool Match(
+ HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
+ HttpContext = httpContext;
+ Route = route;
+ RouteKey = routeKey;
+ Values = values;
+ RouteDirection = routeDirection;
+ return false;
}
-
- public ITestService TestService { get; }
- public int Count { get; }
}
+}
- public class CustomParameterPolicyWithAmbigiousMultpleCtors : IParameterPolicy
- {
- public CustomParameterPolicyWithAmbigiousMultpleCtors(ITestService testService, int count)
- {
- TestService = testService;
- Count = count;
- }
+public class CustomParameterPolicy : IParameterPolicy
+{
+}
- public CustomParameterPolicyWithAmbigiousMultpleCtors(object testService, int count)
- : this(testService: null, count)
- {
- }
+public class CustomParameterPolicyWithArguments : IParameterPolicy
+{
+ public CustomParameterPolicyWithArguments(ITestService testService, int count)
+ {
+ TestService = testService;
+ Count = count;
+ }
- public CustomParameterPolicyWithAmbigiousMultpleCtors(int count)
- : this(testService: null, count)
- {
- }
+ public ITestService TestService { get; }
+ public int Count { get; }
+}
- public ITestService TestService { get; }
- public int Count { get; }
+public class CustomParameterPolicyWithMultpleCtors : IParameterPolicy
+{
+ public CustomParameterPolicyWithMultpleCtors(ITestService testService, int count)
+ {
+ TestService = testService;
+ Count = count;
}
- public class CustomParameterPolicyWithMultipleArguments : IParameterPolicy
+ public CustomParameterPolicyWithMultpleCtors(int count)
+ : this(testService: null, count)
{
- public CustomParameterPolicyWithMultipleArguments(int first, ITestService testService1, int second, ITestService testService2)
- {
- First = first;
- TestService1 = testService1;
- Second = second;
- TestService2 = testService2;
- }
-
- public int First { get; }
- public ITestService TestService1 { get; }
- public int Second { get; }
- public ITestService TestService2 { get; }
}
- public class CustomParameterPolicyWithOnlyServiceArguments : IParameterPolicy
- {
- public CustomParameterPolicyWithOnlyServiceArguments(ITestService testService1, ITestService testService2)
- {
- TestService1 = testService1;
- TestService2 = testService2;
- }
+ public ITestService TestService { get; }
+ public int Count { get; }
+}
- public ITestService TestService1 { get; }
- public ITestService TestService2 { get; }
+public class CustomParameterPolicyWithAmbigiousMultpleCtors : IParameterPolicy
+{
+ public CustomParameterPolicyWithAmbigiousMultpleCtors(ITestService testService, int count)
+ {
+ TestService = testService;
+ Count = count;
}
- public interface ITestService
+ public CustomParameterPolicyWithAmbigiousMultpleCtors(object testService, int count)
+ : this(testService: null, count)
{
}
- public class TestService : ITestService
+ public CustomParameterPolicyWithAmbigiousMultpleCtors(int count)
+ : this(testService: null, count)
{
+ }
+
+ public ITestService TestService { get; }
+ public int Count { get; }
+}
+public class CustomParameterPolicyWithMultipleArguments : IParameterPolicy
+{
+ public CustomParameterPolicyWithMultipleArguments(int first, ITestService testService1, int second, ITestService testService2)
+ {
+ First = first;
+ TestService1 = testService1;
+ Second = second;
+ TestService2 = testService2;
}
- public class RegexInlineRouteConstraintWithService : RegexRouteConstraint
+ public int First { get; }
+ public ITestService TestService1 { get; }
+ public int Second { get; }
+ public ITestService TestService2 { get; }
+}
+
+public class CustomParameterPolicyWithOnlyServiceArguments : IParameterPolicy
+{
+ public CustomParameterPolicyWithOnlyServiceArguments(ITestService testService1, ITestService testService2)
{
- public RegexInlineRouteConstraintWithService(string regexPattern, ITestService testService)
- : base(regexPattern)
- {
- TestService = testService;
- }
+ TestService1 = testService1;
+ TestService2 = testService2;
+ }
+
+ public ITestService TestService1 { get; }
+ public ITestService TestService2 { get; }
+}
+
+public interface ITestService
+{
+}
+
+public class TestService : ITestService
+{
+
+}
- public ITestService TestService { get; }
+public class RegexInlineRouteConstraintWithService : RegexRouteConstraint
+{
+ public RegexInlineRouteConstraintWithService(string regexPattern, ITestService testService)
+ : base(regexPattern)
+ {
+ TestService = testService;
}
+
+ public ITestService TestService { get; }
}
diff --git a/src/Http/Routing/test/UnitTests/EndpointFactory.cs b/src/Http/Routing/test/UnitTests/EndpointFactory.cs
index 7d20a720cd..12d5e11248 100644
--- a/src/Http/Routing/test/UnitTests/EndpointFactory.cs
+++ b/src/Http/Routing/test/UnitTests/EndpointFactory.cs
@@ -11,36 +11,35 @@ using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+internal static class EndpointFactory
{
- internal static class EndpointFactory
+ public static RouteEndpoint CreateRouteEndpoint(
+ string template,
+ object defaults = null,
+ object policies = null,
+ object requiredValues = null,
+ int order = 0,
+ string displayName = null,
+ params object[] metadata)
{
- public static RouteEndpoint CreateRouteEndpoint(
- string template,
- object defaults = null,
- object policies = null,
- object requiredValues = null,
- int order = 0,
- string displayName = null,
- params object[] metadata)
- {
- var routePattern = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
+ var routePattern = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
- return CreateRouteEndpoint(routePattern, order, displayName, metadata);
- }
+ return CreateRouteEndpoint(routePattern, order, displayName, metadata);
+ }
- public static RouteEndpoint CreateRouteEndpoint(
- RoutePattern routePattern = null,
- int order = 0,
- string displayName = null,
- IList<object> metadata = null)
- {
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- routePattern,
- order,
- new EndpointMetadataCollection(metadata ?? Array.Empty<object>()),
- displayName);
- }
+ public static RouteEndpoint CreateRouteEndpoint(
+ RoutePattern routePattern = null,
+ int order = 0,
+ string displayName = null,
+ IList<object> metadata = null)
+ {
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ routePattern,
+ order,
+ new EndpointMetadataCollection(metadata ?? Array.Empty<object>()),
+ displayName);
}
}
diff --git a/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs
index 2f6482ec71..16c412e710 100644
--- a/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs
+++ b/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs
@@ -8,289 +8,288 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class EndpointMiddlewareTest
{
- public class EndpointMiddlewareTest
+ private readonly IOptions<RouteOptions> RouteOptions = Options.Create(new RouteOptions());
+
+ [Fact]
+ public async Task Invoke_NoFeature_NoOps()
{
- private readonly IOptions<RouteOptions> RouteOptions = Options.Create(new RouteOptions());
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceProvider();
- [Fact]
- public async Task Invoke_NoFeature_NoOps()
+ var calledNext = false;
+ RequestDelegate next = (c) =>
{
- // Arrange
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceProvider();
+ calledNext = true;
+ return Task.CompletedTask;
+ };
- var calledNext = false;
- RequestDelegate next = (c) =>
- {
- calledNext = true;
- return Task.CompletedTask;
- };
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
+ // Act
+ await middleware.Invoke(httpContext);
- // Act
- await middleware.Invoke(httpContext);
+ // Assert
+ Assert.True(calledNext);
+ }
- // Assert
- Assert.True(calledNext);
- }
+ [Fact]
+ public async Task Invoke_NoEndpoint_NoOps()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceProvider();
+ httpContext.SetEndpoint(null);
- [Fact]
- public async Task Invoke_NoEndpoint_NoOps()
+ var calledNext = false;
+ RequestDelegate next = (c) =>
{
- // Arrange
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceProvider();
- httpContext.SetEndpoint(null);
+ calledNext = true;
+ return Task.CompletedTask;
+ };
- var calledNext = false;
- RequestDelegate next = (c) =>
- {
- calledNext = true;
- return Task.CompletedTask;
- };
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
+ // Act
+ await middleware.Invoke(httpContext);
- // Act
- await middleware.Invoke(httpContext);
+ // Assert
+ Assert.True(calledNext);
+ }
- // Assert
- Assert.True(calledNext);
- }
+ [Fact]
+ public async Task Invoke_WithEndpoint_InvokesDelegate()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceProvider();
- [Fact]
- public async Task Invoke_WithEndpoint_InvokesDelegate()
+ var calledEndpoint = false;
+ RequestDelegate endpointFunc = (c) =>
{
- // Arrange
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceProvider();
+ calledEndpoint = true;
+ return Task.CompletedTask;
+ };
- var calledEndpoint = false;
- RequestDelegate endpointFunc = (c) =>
- {
- calledEndpoint = true;
- return Task.CompletedTask;
- };
+ httpContext.SetEndpoint(new Endpoint(endpointFunc, EndpointMetadataCollection.Empty, "Test"));
- httpContext.SetEndpoint(new Endpoint(endpointFunc, EndpointMetadataCollection.Empty, "Test"));
+ RequestDelegate next = (c) =>
+ {
+ throw new InvalidTimeZoneException("Should not be called");
+ };
- RequestDelegate next = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
+ // Act
+ await middleware.Invoke(httpContext);
- // Act
- await middleware.Invoke(httpContext);
+ // Assert
+ Assert.True(calledEndpoint);
+ }
- // Assert
- Assert.True(calledEndpoint);
- }
+ [Fact]
+ public async Task Invoke_WithEndpoint_ThrowsIfAuthAttributesWereFound_ButAuthMiddlewareNotInvoked()
+ {
+ // Arrange
+ var expected = "Endpoint Test contains authorization metadata, but a middleware was not found that supports authorization." +
+ Environment.NewLine +
+ "Configure your application startup by adding app.UseAuthorization() in the application startup code. " +
+ "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.";
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = new ServiceProvider()
+ };
- [Fact]
- public async Task Invoke_WithEndpoint_ThrowsIfAuthAttributesWereFound_ButAuthMiddlewareNotInvoked()
+ RequestDelegate throwIfCalled = (c) =>
{
- // Arrange
- var expected = "Endpoint Test contains authorization metadata, but a middleware was not found that supports authorization." +
- Environment.NewLine +
- "Configure your application startup by adding app.UseAuthorization() in the application startup code. " +
- "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.";
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceProvider()
- };
-
- RequestDelegate throwIfCalled = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
-
- httpContext.SetEndpoint(new Endpoint(throwIfCalled, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
-
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, throwIfCalled, RouteOptions);
-
- // Act & Assert
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
-
- // Assert
- Assert.Equal(expected, ex.Message);
- }
+ throw new InvalidTimeZoneException("Should not be called");
+ };
+
+ httpContext.SetEndpoint(new Endpoint(throwIfCalled, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
+
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, throwIfCalled, RouteOptions);
+
+ // Act & Assert
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
+
+ // Assert
+ Assert.Equal(expected, ex.Message);
+ }
- [Fact]
- public async Task Invoke_WithEndpoint_WorksIfAuthAttributesWereFound_AndAuthMiddlewareInvoked()
+ [Fact]
+ public async Task Invoke_WithEndpoint_WorksIfAuthAttributesWereFound_AndAuthMiddlewareInvoked()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext
{
- // Arrange
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceProvider()
- };
+ RequestServices = new ServiceProvider()
+ };
- var calledEndpoint = false;
- RequestDelegate endpointFunc = (c) =>
- {
- calledEndpoint = true;
- return Task.CompletedTask;
- };
+ var calledEndpoint = false;
+ RequestDelegate endpointFunc = (c) =>
+ {
+ calledEndpoint = true;
+ return Task.CompletedTask;
+ };
- httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
+ httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
- httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true;
+ httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true;
- RequestDelegate next = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
+ RequestDelegate next = (c) =>
+ {
+ throw new InvalidTimeZoneException("Should not be called");
+ };
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
- // Act
- await middleware.Invoke(httpContext);
+ // Act
+ await middleware.Invoke(httpContext);
- // Assert
- Assert.True(calledEndpoint);
- }
+ // Assert
+ Assert.True(calledEndpoint);
+ }
- [Fact]
- public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledAuthAttributesWereFound_ButSuppressedViaOptions()
+ [Fact]
+ public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledAuthAttributesWereFound_ButSuppressedViaOptions()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext
{
- // Arrange
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceProvider()
- };
+ RequestServices = new ServiceProvider()
+ };
- var calledEndpoint = false;
- RequestDelegate endpointFunc = (c) =>
- {
- calledEndpoint = true;
- return Task.CompletedTask;
- };
+ var calledEndpoint = false;
+ RequestDelegate endpointFunc = (c) =>
+ {
+ calledEndpoint = true;
+ return Task.CompletedTask;
+ };
- httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
+ httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
- var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
+ var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
- RequestDelegate next = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
+ RequestDelegate next = (c) =>
+ {
+ throw new InvalidTimeZoneException("Should not be called");
+ };
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, routeOptions);
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, routeOptions);
- // Act
- await middleware.Invoke(httpContext);
+ // Act
+ await middleware.Invoke(httpContext);
- // Assert
- Assert.True(calledEndpoint);
- }
+ // Assert
+ Assert.True(calledEndpoint);
+ }
- [Fact]
- public async Task Invoke_WithEndpoint_ThrowsIfCorsMetadataWasFound_ButCorsMiddlewareNotInvoked()
+ [Fact]
+ public async Task Invoke_WithEndpoint_ThrowsIfCorsMetadataWasFound_ButCorsMiddlewareNotInvoked()
+ {
+ // Arrange
+ var expected = "Endpoint Test contains CORS metadata, but a middleware was not found that supports CORS." +
+ Environment.NewLine +
+ "Configure your application startup by adding app.UseCors() in the application startup code. " +
+ "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.";
+ var httpContext = new DefaultHttpContext
{
- // Arrange
- var expected = "Endpoint Test contains CORS metadata, but a middleware was not found that supports CORS." +
- Environment.NewLine +
- "Configure your application startup by adding app.UseCors() in the application startup code. " +
- "If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseCors() must go between them.";
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceProvider()
- };
-
- RequestDelegate throwIfCalled = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
-
- httpContext.SetEndpoint(new Endpoint(throwIfCalled, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"));
-
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, throwIfCalled, RouteOptions);
-
- // Act & Assert
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
-
- // Assert
- Assert.Equal(expected, ex.Message);
- }
+ RequestServices = new ServiceProvider()
+ };
- [Fact]
- public async Task Invoke_WithEndpoint_WorksIfCorsMetadataWasFound_AndCorsMiddlewareInvoked()
+ RequestDelegate throwIfCalled = (c) =>
{
- // Arrange
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceProvider()
- };
+ throw new InvalidTimeZoneException("Should not be called");
+ };
- var calledEndpoint = false;
- RequestDelegate endpointFunc = (c) =>
- {
- calledEndpoint = true;
- return Task.CompletedTask;
- };
+ httpContext.SetEndpoint(new Endpoint(throwIfCalled, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"));
- httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"));
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, throwIfCalled, RouteOptions);
- httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true;
+ // Act & Assert
+ var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
- RequestDelegate next = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
+ // Assert
+ Assert.Equal(expected, ex.Message);
+ }
+
+ [Fact]
+ public async Task Invoke_WithEndpoint_WorksIfCorsMetadataWasFound_AndCorsMiddlewareInvoked()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = new ServiceProvider()
+ };
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
+ var calledEndpoint = false;
+ RequestDelegate endpointFunc = (c) =>
+ {
+ calledEndpoint = true;
+ return Task.CompletedTask;
+ };
- // Act
- await middleware.Invoke(httpContext);
+ httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"));
- // Assert
- Assert.True(calledEndpoint);
- }
+ httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true;
- [Fact]
- public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledCorsAttributesWereFound_ButSuppressedViaOptions()
+ RequestDelegate next = (c) =>
{
- // Arrange
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new ServiceProvider()
- };
+ throw new InvalidTimeZoneException("Should not be called");
+ };
- var calledEndpoint = false;
- RequestDelegate endpointFunc = (c) =>
- {
- calledEndpoint = true;
- return Task.CompletedTask;
- };
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
- httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
+ // Act
+ await middleware.Invoke(httpContext);
- var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
+ // Assert
+ Assert.True(calledEndpoint);
+ }
+
+ [Fact]
+ public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledCorsAttributesWereFound_ButSuppressedViaOptions()
+ {
+ // Arrange
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = new ServiceProvider()
+ };
- RequestDelegate next = (c) =>
- {
- throw new InvalidTimeZoneException("Should not be called");
- };
+ var calledEndpoint = false;
+ RequestDelegate endpointFunc = (c) =>
+ {
+ calledEndpoint = true;
+ return Task.CompletedTask;
+ };
- var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, routeOptions);
+ httpContext.SetEndpoint(new Endpoint(endpointFunc, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
- // Act
- await middleware.Invoke(httpContext);
+ var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
- // Assert
- Assert.True(calledEndpoint);
- }
+ RequestDelegate next = (c) =>
+ {
+ throw new InvalidTimeZoneException("Should not be called");
+ };
+
+ var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, routeOptions);
+
+ // Act
+ await middleware.Invoke(httpContext);
- private class ServiceProvider : IServiceProvider
+ // Assert
+ Assert.True(calledEndpoint);
+ }
+
+ private class ServiceProvider : IServiceProvider
+ {
+ public object GetService(Type serviceType)
{
- public object GetService(Type serviceType)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/EndpointNameAddressSchemeTest.cs b/src/Http/Routing/test/UnitTests/EndpointNameAddressSchemeTest.cs
index 330a4450ac..82d703b18b 100644
--- a/src/Http/Routing/test/UnitTests/EndpointNameAddressSchemeTest.cs
+++ b/src/Http/Routing/test/UnitTests/EndpointNameAddressSchemeTest.cs
@@ -7,155 +7,155 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.TestObjects;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class EndpointNameAddressSchemeTest
{
- public class EndpointNameAddressSchemeTest
+ [Fact]
+ public void AddressScheme_Match_ReturnsMatchingEndpoint()
{
- [Fact]
- public void AddressScheme_Match_ReturnsMatchingEndpoint()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new EndpointNameMetadata("name1"), });
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new EndpointNameMetadata("name1"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "/b",
- metadata: new object[] { new EndpointNameMetadata("name2"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "/b",
+ metadata: new object[] { new EndpointNameMetadata("name2"), });
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
- // Act
- var endpoints = addressScheme.FindEndpoints("name2");
+ // Act
+ var endpoints = addressScheme.FindEndpoints("name2");
- // Assert
- Assert.Collection(
- endpoints,
- e => Assert.Same(endpoint2, e));
- }
+ // Assert
+ Assert.Collection(
+ endpoints,
+ e => Assert.Same(endpoint2, e));
+ }
- [Fact]
- public void AddressScheme_NoMatch_ReturnsEmptyCollection()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
+ [Fact]
+ public void AddressScheme_NoMatch_ReturnsEmptyCollection()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
- var addressScheme = CreateAddressScheme(endpoint);
+ var addressScheme = CreateAddressScheme(endpoint);
- // Act
- var endpoints = addressScheme.FindEndpoints("name2");
+ // Act
+ var endpoints = addressScheme.FindEndpoints("name2");
- // Assert
- Assert.Empty(endpoints);
- }
+ // Assert
+ Assert.Empty(endpoints);
+ }
- [Fact]
- public void AddressScheme_NoMatch_CaseSensitive()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
+ [Fact]
+ public void AddressScheme_NoMatch_CaseSensitive()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
- var addressScheme = CreateAddressScheme(endpoint);
+ var addressScheme = CreateAddressScheme(endpoint);
- // Act
- var endpoints = addressScheme.FindEndpoints("NAME1");
+ // Act
+ var endpoints = addressScheme.FindEndpoints("NAME1");
- // Assert
- Assert.Empty(endpoints);
- }
+ // Assert
+ Assert.Empty(endpoints);
+ }
- [Fact]
- public void AddressScheme_UpdatesWhenDataSourceChanges()
- {
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new EndpointNameMetadata("name1"), });
- var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
-
- // Act 1
- var addressScheme = CreateAddressScheme(dynamicDataSource);
-
- // Assert 1
- var match = Assert.Single(addressScheme.Entries);
- Assert.Same(endpoint1, match.Value.Single());
-
- // Arrange 2
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "/b",
- metadata: new object[] { new EndpointNameMetadata("name2"), });
-
- // Act 2
- // Trigger change
- dynamicDataSource.AddEndpoint(endpoint2);
-
- // Assert 2
- Assert.Collection(
- addressScheme.Entries.OrderBy(kvp => kvp.Key),
- (m) =>
- {
- Assert.Same(endpoint1, m.Value.Single());
- },
- (m) =>
- {
- Assert.Same(endpoint2, m.Value.Single());
- });
- }
-
- [Fact]
- public void AddressScheme_IgnoresEndpointsWithSuppressLinkGeneration()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
+ [Fact]
+ public void AddressScheme_UpdatesWhenDataSourceChanges()
+ {
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new EndpointNameMetadata("name1"), });
+ var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
+
+ // Act 1
+ var addressScheme = CreateAddressScheme(dynamicDataSource);
+
+ // Assert 1
+ var match = Assert.Single(addressScheme.Entries);
+ Assert.Same(endpoint1, match.Value.Single());
+
+ // Arrange 2
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "/b",
+ metadata: new object[] { new EndpointNameMetadata("name2"), });
+
+ // Act 2
+ // Trigger change
+ dynamicDataSource.AddEndpoint(endpoint2);
+
+ // Assert 2
+ Assert.Collection(
+ addressScheme.Entries.OrderBy(kvp => kvp.Key),
+ (m) =>
+ {
+ Assert.Same(endpoint1, m.Value.Single());
+ },
+ (m) =>
+ {
+ Assert.Same(endpoint2, m.Value.Single());
+ });
+ }
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
+ [Fact]
+ public void AddressScheme_IgnoresEndpointsWithSuppressLinkGeneration()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), });
- // Assert
- Assert.Empty(addressScheme.Entries);
- }
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
- [Fact]
- public void AddressScheme_UnsuppressedEndpoint_IsUsed()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), });
+ // Assert
+ Assert.Empty(addressScheme.Entries);
+ }
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
+ [Fact]
+ public void AddressScheme_UnsuppressedEndpoint_IsUsed()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new EndpointNameMetadata("name1"), new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), });
- // Assert
- Assert.Same(endpoint, Assert.Single(Assert.Single(addressScheme.Entries).Value));
- }
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
- [Fact]
- public void AddressScheme_IgnoresEndpointsWithoutEndpointName()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { });
+ // Assert
+ Assert.Same(endpoint, Assert.Single(Assert.Single(addressScheme.Entries).Value));
+ }
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
+ [Fact]
+ public void AddressScheme_IgnoresEndpointsWithoutEndpointName()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { });
- // Assert
- Assert.Empty(addressScheme.Entries);
- }
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
- [Fact]
- public void AddressScheme_ThrowsExceptionForDuplicateEndpoints()
+ // Assert
+ Assert.Empty(addressScheme.Entries);
+ }
+
+ [Fact]
+ public void AddressScheme_ThrowsExceptionForDuplicateEndpoints()
+ {
+ // Arrange
+ var endpoints = new Endpoint[]
{
- // Arrange
- var endpoints = new Endpoint[]
- {
EndpointFactory.CreateRouteEndpoint("/a", displayName: "a", metadata: new object[] { new EndpointNameMetadata("name1"), }),
EndpointFactory.CreateRouteEndpoint("/b", displayName: "b", metadata: new object[] { new EndpointNameMetadata("name1"), }),
EndpointFactory.CreateRouteEndpoint("/c", displayName: "c", metadata: new object[] { new EndpointNameMetadata("name1"), }),
@@ -165,15 +165,15 @@ namespace Microsoft.AspNetCore.Routing
EndpointFactory.CreateRouteEndpoint("/e", displayName: "e", metadata: new object[] { new EndpointNameMetadata("name2"), }),
EndpointFactory.CreateRouteEndpoint("/f", displayName: "f", metadata: new object[] { new EndpointNameMetadata("name2"), }),
- };
+ };
- var addressScheme = CreateAddressScheme(endpoints);
+ var addressScheme = CreateAddressScheme(endpoints);
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => addressScheme.FindEndpoints("any name"));
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => addressScheme.FindEndpoints("any name"));
- // Assert
- Assert.Equal(String.Join(Environment.NewLine, @"The following endpoints with a duplicate endpoint name were found.",
+ // Assert
+ Assert.Equal(String.Join(Environment.NewLine, @"The following endpoints with a duplicate endpoint name were found.",
"",
"Endpoints with endpoint name 'name1':",
"a",
@@ -184,21 +184,20 @@ namespace Microsoft.AspNetCore.Routing
"e",
"f",
""), ex.Message);
- }
+ }
- private EndpointNameAddressScheme CreateAddressScheme(params Endpoint[] endpoints)
- {
- return CreateAddressScheme(new DefaultEndpointDataSource(endpoints));
- }
+ private EndpointNameAddressScheme CreateAddressScheme(params Endpoint[] endpoints)
+ {
+ return CreateAddressScheme(new DefaultEndpointDataSource(endpoints));
+ }
- private EndpointNameAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources)
- {
- return new EndpointNameAddressScheme(new CompositeEndpointDataSource(dataSources));
- }
+ private EndpointNameAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources)
+ {
+ return new EndpointNameAddressScheme(new CompositeEndpointDataSource(dataSources));
+ }
- private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata
- {
- public bool SuppressLinkGeneration => false;
- }
+ private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata
+ {
+ public bool SuppressLinkGeneration => false;
}
}
diff --git a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs
index f09878453d..3a2e6c8c46 100644
--- a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs
+++ b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs
@@ -16,202 +16,201 @@ using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class EndpointRoutingMiddlewareTest
{
- public class EndpointRoutingMiddlewareTest
+ [Fact]
+ public async Task Invoke_OnCall_SetsEndpointFeature()
{
- [Fact]
- public async Task Invoke_OnCall_SetsEndpointFeature()
- {
- // Arrange
- var httpContext = CreateHttpContext();
+ // Arrange
+ var httpContext = CreateHttpContext();
- var middleware = CreateMiddleware();
+ var middleware = CreateMiddleware();
- // Act
- await middleware.Invoke(httpContext);
+ // Act
+ await middleware.Invoke(httpContext);
- // Assert
- var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
- Assert.NotNull(endpointFeature);
- }
+ // Assert
+ var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
+ Assert.NotNull(endpointFeature);
+ }
- [Fact]
- public async Task Invoke_SkipsRouting_IfEndpointSet()
- {
- // Arrange
- var httpContext = CreateHttpContext();
- httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "myapp"));
+ [Fact]
+ public async Task Invoke_SkipsRouting_IfEndpointSet()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+ httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "myapp"));
- var middleware = CreateMiddleware();
+ var middleware = CreateMiddleware();
- // Act
- await middleware.Invoke(httpContext);
+ // Act
+ await middleware.Invoke(httpContext);
- // Assert
- var endpoint = httpContext.GetEndpoint();
- Assert.NotNull(endpoint);
- Assert.Equal("myapp", endpoint.DisplayName);
- }
+ // Assert
+ var endpoint = httpContext.GetEndpoint();
+ Assert.NotNull(endpoint);
+ Assert.Equal("myapp", endpoint.DisplayName);
+ }
- [Fact]
- public async Task Invoke_OnCall_WritesToConfiguredLogger()
- {
- // Arrange
- var expectedMessage = "Request matched endpoint 'Test endpoint'";
- bool eventFired = false;
+ [Fact]
+ public async Task Invoke_OnCall_WritesToConfiguredLogger()
+ {
+ // Arrange
+ var expectedMessage = "Request matched endpoint 'Test endpoint'";
+ bool eventFired = false;
- var sink = new TestSink(
- TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
- TestSink.EnableWithTypeName<EndpointRoutingMiddleware>);
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- var listener = new DiagnosticListener("TestListener");
+ var sink = new TestSink(
+ TestSink.EnableWithTypeName<EndpointRoutingMiddleware>,
+ TestSink.EnableWithTypeName<EndpointRoutingMiddleware>);
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ var listener = new DiagnosticListener("TestListener");
- using var subscription = listener.Subscribe(new DelegateObserver(pair =>
- {
- eventFired = true;
+ using var subscription = listener.Subscribe(new DelegateObserver(pair =>
+ {
+ eventFired = true;
- Assert.Equal("Microsoft.AspNetCore.Routing.EndpointMatched", pair.Key);
- Assert.IsAssignableFrom<HttpContext>(pair.Value);
- }));
+ Assert.Equal("Microsoft.AspNetCore.Routing.EndpointMatched", pair.Key);
+ Assert.IsAssignableFrom<HttpContext>(pair.Value);
+ }));
- var httpContext = CreateHttpContext();
+ var httpContext = CreateHttpContext();
- var logger = new Logger<EndpointRoutingMiddleware>(loggerFactory);
- var middleware = CreateMiddleware(logger, listener: listener);
+ var logger = new Logger<EndpointRoutingMiddleware>(loggerFactory);
+ var middleware = CreateMiddleware(logger, listener: listener);
- // Act
- await middleware.Invoke(httpContext);
+ // Act
+ await middleware.Invoke(httpContext);
- // Assert
- Assert.Empty(sink.Scopes);
- var write = Assert.Single(sink.Writes);
- Assert.Equal(expectedMessage, write.State?.ToString());
- Assert.True(eventFired);
- }
+ // Assert
+ Assert.Empty(sink.Scopes);
+ var write = Assert.Single(sink.Writes);
+ Assert.Equal(expectedMessage, write.State?.ToString());
+ Assert.True(eventFired);
+ }
- [Fact]
- public async Task Invoke_BackCompatGetRouteValue_ValueUsedFromEndpointFeature()
- {
- // Arrange
- var httpContext = CreateHttpContext();
+ [Fact]
+ public async Task Invoke_BackCompatGetRouteValue_ValueUsedFromEndpointFeature()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
- var middleware = CreateMiddleware();
+ var middleware = CreateMiddleware();
- // Act
- await middleware.Invoke(httpContext);
- var routeData = httpContext.GetRouteData();
- var routeValue = httpContext.GetRouteValue("controller");
- var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
+ // Act
+ await middleware.Invoke(httpContext);
+ var routeData = httpContext.GetRouteData();
+ var routeValue = httpContext.GetRouteValue("controller");
+ var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
- // Assert
- Assert.NotNull(routeData);
- Assert.Equal("Home", (string)routeValue);
+ // Assert
+ Assert.NotNull(routeData);
+ Assert.Equal("Home", (string)routeValue);
- // changing route data value is reflected in endpoint feature values
- routeData.Values["testKey"] = "testValue";
- Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
- }
+ // changing route data value is reflected in endpoint feature values
+ routeData.Values["testKey"] = "testValue";
+ Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
+ }
- [Fact]
- public async Task Invoke_BackCompatGetDataTokens_ValueUsedFromEndpointMetadata()
- {
- // Arrange
- var httpContext = CreateHttpContext();
+ [Fact]
+ public async Task Invoke_BackCompatGetDataTokens_ValueUsedFromEndpointMetadata()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
- var middleware = CreateMiddleware();
+ var middleware = CreateMiddleware();
- // Act
- await middleware.Invoke(httpContext);
- var routeData = httpContext.GetRouteData();
- var routeValue = httpContext.GetRouteValue("controller");
- var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
+ // Act
+ await middleware.Invoke(httpContext);
+ var routeData = httpContext.GetRouteData();
+ var routeValue = httpContext.GetRouteValue("controller");
+ var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
- // Assert
- Assert.NotNull(routeData);
- Assert.Equal("Home", (string)routeValue);
+ // Assert
+ Assert.NotNull(routeData);
+ Assert.Equal("Home", (string)routeValue);
- // changing route data value is reflected in endpoint feature values
- routeData.Values["testKey"] = "testValue";
- Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
- }
+ // changing route data value is reflected in endpoint feature values
+ routeData.Values["testKey"] = "testValue";
+ Assert.Equal("testValue", routeValuesFeature.RouteValues["testKey"]);
+ }
- [Fact]
- public async Task Invoke_InitializationFailure_AllowsReinitialization()
- {
- // Arrange
- var httpContext = CreateHttpContext();
+ [Fact]
+ public async Task Invoke_InitializationFailure_AllowsReinitialization()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
- var matcherFactory = new Mock<MatcherFactory>();
- matcherFactory
- .Setup(f => f.CreateMatcher(It.IsAny<EndpointDataSource>()))
- .Throws(new InvalidTimeZoneException())
- .Verifiable();
+ var matcherFactory = new Mock<MatcherFactory>();
+ matcherFactory
+ .Setup(f => f.CreateMatcher(It.IsAny<EndpointDataSource>()))
+ .Throws(new InvalidTimeZoneException())
+ .Verifiable();
- var middleware = CreateMiddleware(matcherFactory: matcherFactory.Object);
+ var middleware = CreateMiddleware(matcherFactory: matcherFactory.Object);
- // Act
- await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await middleware.Invoke(httpContext));
- await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await middleware.Invoke(httpContext));
+ // Act
+ await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await middleware.Invoke(httpContext));
+ await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await middleware.Invoke(httpContext));
- // Assert
- matcherFactory
- .Verify(f => f.CreateMatcher(It.IsAny<EndpointDataSource>()), Times.Exactly(2));
- }
+ // Assert
+ matcherFactory
+ .Verify(f => f.CreateMatcher(It.IsAny<EndpointDataSource>()), Times.Exactly(2));
+ }
- private HttpContext CreateHttpContext()
+ private HttpContext CreateHttpContext()
+ {
+ var httpContext = new DefaultHttpContext
{
- var httpContext = new DefaultHttpContext
- {
- RequestServices = new TestServiceProvider()
- };
+ RequestServices = new TestServiceProvider()
+ };
- return httpContext;
- }
+ return httpContext;
+ }
- private EndpointRoutingMiddleware CreateMiddleware(
- Logger<EndpointRoutingMiddleware> logger = null,
- MatcherFactory matcherFactory = null,
- DiagnosticListener listener = null,
- RequestDelegate next = null)
+ private EndpointRoutingMiddleware CreateMiddleware(
+ Logger<EndpointRoutingMiddleware> logger = null,
+ MatcherFactory matcherFactory = null,
+ DiagnosticListener listener = null,
+ RequestDelegate next = null)
+ {
+ next ??= c => Task.CompletedTask;
+ logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
+ matcherFactory ??= new TestMatcherFactory(true);
+ listener ??= new DiagnosticListener("Microsoft.AspNetCore");
+
+ var middleware = new EndpointRoutingMiddleware(
+ matcherFactory,
+ logger,
+ new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>()),
+ listener,
+ next);
+
+ return middleware;
+ }
+
+ private class DelegateObserver : IObserver<KeyValuePair<string, object>>
+ {
+ private readonly Action<KeyValuePair<string, object>> _onNext;
+
+ public DelegateObserver(Action<KeyValuePair<string, object>> onNext)
{
- next ??= c => Task.CompletedTask;
- logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
- matcherFactory ??= new TestMatcherFactory(true);
- listener ??= new DiagnosticListener("Microsoft.AspNetCore");
-
- var middleware = new EndpointRoutingMiddleware(
- matcherFactory,
- logger,
- new DefaultEndpointRouteBuilder(Mock.Of<IApplicationBuilder>()),
- listener,
- next);
-
- return middleware;
+ _onNext = onNext;
}
-
- private class DelegateObserver : IObserver<KeyValuePair<string, object>>
+ public void OnCompleted()
{
- private readonly Action<KeyValuePair<string, object>> _onNext;
-
- public DelegateObserver(Action<KeyValuePair<string, object>> onNext)
- {
- _onNext = onNext;
- }
- public void OnCompleted()
- {
- }
+ }
- public void OnError(Exception error)
- {
+ public void OnError(Exception error)
+ {
- }
+ }
- public void OnNext(KeyValuePair<string, object> value)
- {
- _onNext(value);
- }
+ public void OnNext(KeyValuePair<string, object> value)
+ {
+ _onNext(value);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/InlineRouteParameterParserTests.cs b/src/Http/Routing/test/UnitTests/InlineRouteParameterParserTests.cs
index 4a3616540d..627120d28d 100644
--- a/src/Http/Routing/test/UnitTests/InlineRouteParameterParserTests.cs
+++ b/src/Http/Routing/test/UnitTests/InlineRouteParameterParserTests.cs
@@ -10,983 +10,982 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class InlineRouteParameterParserTests
{
- public class InlineRouteParameterParserTests
+ [Theory]
+ [InlineData("=")]
+ [InlineData(":")]
+ public void ParseRouteParameter_WithoutADefaultValue(string parameterName)
{
- [Theory]
- [InlineData("=")]
- [InlineData(":")]
- public void ParseRouteParameter_WithoutADefaultValue(string parameterName)
- {
- // Arrange & Act
- var templatePart = ParseParameter(parameterName);
+ // Arrange & Act
+ var templatePart = ParseParameter(parameterName);
- // Assert
- Assert.Equal(parameterName, templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.Empty(templatePart.InlineConstraints);
- }
+ // Assert
+ Assert.Equal(parameterName, templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.Empty(templatePart.InlineConstraints);
+ }
- [Fact]
- public void ParseRouteParameter_WithEmptyDefaultValue()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param=");
+ [Fact]
+ public void ParseRouteParameter_WithEmptyDefaultValue()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param=");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("", templatePart.DefaultValue);
- Assert.Empty(templatePart.InlineConstraints);
- }
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("", templatePart.DefaultValue);
+ Assert.Empty(templatePart.InlineConstraints);
+ }
- [Fact]
- public void ParseRouteParameter_WithoutAConstraintName()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param:");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Empty(constraint.Constraint);
- }
+ [Fact]
+ public void ParseRouteParameter_WithoutAConstraintName()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param:");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Empty(constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_WithoutAConstraintNameOrParameterName()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param:=");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Empty(constraint.Constraint);
- }
+ [Fact]
+ public void ParseRouteParameter_WithoutAConstraintNameOrParameterName()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param:=");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("", templatePart.DefaultValue);
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Empty(constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_WithADefaultValueContainingConstraintSeparator()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param=:");
+ [Fact]
+ public void ParseRouteParameter_WithADefaultValueContainingConstraintSeparator()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param=:");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal(":", templatePart.DefaultValue);
- Assert.Empty(templatePart.InlineConstraints);
- }
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal(":", templatePart.DefaultValue);
+ Assert.Empty(templatePart.InlineConstraints);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintAndDefault_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param:int=111111");
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndDefault_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param:int=111111");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("111111", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("111111", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("int", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("int", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithArgumentsAndDefault_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)=111111");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithArgumentsAndDefault_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)=111111");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("111111", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("111111", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\d+)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\d+)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintAndOptional_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndOptional_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("int", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("int", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int=12?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int=12?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("12", templatePart.DefaultValue);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("12", templatePart.DefaultValue);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("int", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("int", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValueWithQuestionMark_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int=12??");
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValueWithQuestionMark_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int=12??");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("12?", templatePart.DefaultValue);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("12?", templatePart.DefaultValue);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("int", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("int", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\d+)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\d+)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)=abc?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)=abc?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.True(templatePart.IsOptional);
- Assert.Equal("abc", templatePart.DefaultValue);
+ Assert.Equal("abc", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\d+)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\d+)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(d+):test(w+)");
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(d+):test(w+)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
- constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
+ constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_DoubleDelimiters_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param::test(d+)::test(w+)");
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_DoubleDelimiters_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param::test(d+)::test(w+)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Empty(constraint.Constraint),
- constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
- constraint => Assert.Empty(constraint.Constraint),
- constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Empty(constraint.Constraint),
+ constraint => Assert.Equal(@"test(d+)", constraint.Constraint),
+ constraint => Assert.Empty(constraint.Constraint),
+ constraint => Assert.Equal(@"test(w+)", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_ColonInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+):test(\w:+)");
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_ColonInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+):test(\w:+)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
- constraint => Assert.Equal(@"test(\w:+)", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
+ constraint => Assert.Equal(@"test(\w:+)", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+):test(\w+)=qwer");
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+):test(\w+)=qwer");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("qwer", templatePart.DefaultValue);
+ Assert.Equal("qwer", templatePart.DefaultValue);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
- constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
+ constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_DoubleDelimiters_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)::test(\w+)==qwer");
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_DoubleDelimiters_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)::test(\w+)==qwer");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("=qwer", templatePart.DefaultValue);
+ Assert.Equal("=qwer", templatePart.DefaultValue);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
- constraint => Assert.Empty(constraint.Constraint),
- constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(\d+)", constraint.Constraint),
+ constraint => Assert.Empty(constraint.Constraint),
+ constraint => Assert.Equal(@"test(\w+)", constraint.Constraint));
+ }
- [Theory]
- [InlineData("=")]
- [InlineData("+=")]
- [InlineData(">= || <= || ==")]
- public void ParseRouteParameter_WithDefaultValue_ContainingDelimiter(string defaultValue)
- {
- // Arrange & Act
- var templatePart = ParseParameter($"comparison-operator:length(6)={defaultValue}");
+ [Theory]
+ [InlineData("=")]
+ [InlineData("+=")]
+ [InlineData(">= || <= || ==")]
+ public void ParseRouteParameter_WithDefaultValue_ContainingDelimiter(string defaultValue)
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter($"comparison-operator:length(6)={defaultValue}");
- // Assert
- Assert.Equal("comparison-operator", templatePart.Name);
- Assert.Equal(defaultValue, templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("comparison-operator", templatePart.Name);
+ Assert.Equal(defaultValue, templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("length(6)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("length(6)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
- {
- // Arrange & Act
- var template = ParseRouteTemplate(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
-
- // Assert
- var parameters = template.Parameters.ToArray();
-
- var param1 = parameters[0];
- Assert.Equal("p1", param1.Name);
- Assert.Equal("hello", param1.DefaultValue);
- Assert.False(param1.IsOptional);
-
- Assert.Collection(param1.InlineConstraints,
- constraint => Assert.Equal("int", constraint.Constraint),
- constraint => Assert.Equal("test(3)", constraint.Constraint)
- );
-
- var param2 = parameters[1];
- Assert.Equal("p2", param2.Name);
- Assert.Equal("abc", param2.DefaultValue);
- Assert.False(param2.IsOptional);
-
- var param3 = parameters[2];
- Assert.Equal("p3", param3.Name);
- Assert.True(param3.IsOptional);
- }
+ [Fact]
+ public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var template = ParseRouteTemplate(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
+
+ // Assert
+ var parameters = template.Parameters.ToArray();
+
+ var param1 = parameters[0];
+ Assert.Equal("p1", param1.Name);
+ Assert.Equal("hello", param1.DefaultValue);
+ Assert.False(param1.IsOptional);
+
+ Assert.Collection(param1.InlineConstraints,
+ constraint => Assert.Equal("int", constraint.Constraint),
+ constraint => Assert.Equal("test(3)", constraint.Constraint)
+ );
+
+ var param2 = parameters[1];
+ Assert.Equal("p2", param2.Name);
+ Assert.Equal("abc", param2.DefaultValue);
+ Assert.False(param2.IsOptional);
+
+ var param3 = parameters[2];
+ Assert.Equal("p3", param3.Name);
+ Assert.True(param3.IsOptional);
+ }
- [Fact]
- public void ParseRouteParameter_NoTokens_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter("world");
+ [Fact]
+ public void ParseRouteParameter_NoTokens_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("world");
- // Assert
- Assert.Equal("world", templatePart.Name);
- }
+ // Assert
+ Assert.Equal("world", templatePart.Name);
+ }
- [Fact]
- public void ParseRouteParameter_ParamDefault_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param=world");
+ [Fact]
+ public void ParseRouteParameter_ParamDefault_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param=world");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("world", templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("world", templatePart.DefaultValue);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_ClosingBraceIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\})");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_ClosingBraceIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\})");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\})", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\})", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\})=wer");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\})=wer");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("wer", templatePart.DefaultValue);
+ Assert.Equal("wer", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\})", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\})", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingParenInPattern_ClosingParenIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\))");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingParenInPattern_ClosingParenIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\))");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\))", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\))", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingParenInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\))=fsd");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingParenInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\))=fsd");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("fsd", templatePart.DefaultValue);
+ Assert.Equal("fsd", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\))", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\))", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonInPattern_ColonIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(:)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonInPattern_ColonIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(:)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(:)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(:)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(:)=mnf");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(:)=mnf");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("mnf", templatePart.DefaultValue);
+ Assert.Equal("mnf", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(:)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(:)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonsInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(a:b:c)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonsInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(a:b:c)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(a:b:c)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(a:b:c)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonInParamName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@":param:test=12");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonInParamName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@":param:test=12");
- // Assert
- Assert.Equal(":param", templatePart.Name);
+ // Assert
+ Assert.Equal(":param", templatePart.Name);
- Assert.Equal("12", templatePart.DefaultValue);
+ Assert.Equal("12", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("test", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("test", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithTwoColonInParamName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@":param::test=12");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithTwoColonInParamName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@":param::test=12");
- // Assert
- Assert.Equal(":param", templatePart.Name);
+ // Assert
+ Assert.Equal(":param", templatePart.Name);
- Assert.Equal("12", templatePart.DefaultValue);
+ Assert.Equal("12", templatePart.DefaultValue);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Empty(constraint.Constraint),
- constraint => Assert.Equal("test", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Empty(constraint.Constraint),
+ constraint => Assert.Equal("test", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_EmptyConstraint_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@":param:test:");
+ [Fact]
+ public void ParseRouteParameter_EmptyConstraint_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@":param:test:");
- // Assert
- Assert.Equal(":param", templatePart.Name);
+ // Assert
+ Assert.Equal(":param", templatePart.Name);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal("test", constraint.Constraint),
- constraint => Assert.Empty(constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal("test", constraint.Constraint),
+ constraint => Assert.Empty(constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithCommaInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\w,\w)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithCommaInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\w,\w)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\w,\w)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\w,\w)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithCommaInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par,am:test(\w)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithCommaInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par,am:test(\w)");
- // Assert
- Assert.Equal("par,am", templatePart.Name);
+ // Assert
+ Assert.Equal("par,am", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\w)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\w)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithCommaInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\w,\w)=jsd");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithCommaInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\w,\w)=jsd");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("jsd", templatePart.DefaultValue);
+ Assert.Equal("jsd", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\w,\w)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\w,\w)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualsFollowedByQuestionMark_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int=?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualsFollowedByQuestionMark_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int=?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("", templatePart.DefaultValue);
- Assert.True(templatePart.IsOptional);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("int", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("int", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(=)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(=)");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("test(=)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("test(=)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_EqualsSignInDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param=test=bar");
+ [Fact]
+ public void ParseRouteParameter_EqualsSignInDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param=test=bar");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("test=bar", templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("test=bar", templatePart.DefaultValue);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(a==b)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(a==b)");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("test(a==b)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("test(a==b)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(a==b)=dvds");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(a==b)=dvds");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("dvds", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("dvds", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("test(a==b)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("test(a==b)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_EqualEqualSignInName_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par==am:test=dvds");
+ [Fact]
+ public void ParseRouteParameter_EqualEqualSignInName_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par==am:test=dvds");
- // Assert
- Assert.Equal("par", templatePart.Name);
- Assert.Equal("=am:test=dvds", templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal("par", templatePart.Name);
+ Assert.Equal("=am:test=dvds", templatePart.DefaultValue);
+ }
- [Fact]
- public void ParseRouteParameter_EqualEqualSignInDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test==dvds");
+ [Fact]
+ public void ParseRouteParameter_EqualEqualSignInDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test==dvds");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("=dvds", templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("=dvds", templatePart.DefaultValue);
+ }
- [Fact]
- public void ParseRouteParameter_DefaultValueWithColonAndParens_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par=am:test(asd)");
+ [Fact]
+ public void ParseRouteParameter_DefaultValueWithColonAndParens_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par=am:test(asd)");
- // Assert
- Assert.Equal("par", templatePart.Name);
- Assert.Equal("am:test(asd)", templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal("par", templatePart.Name);
+ Assert.Equal("am:test(asd)", templatePart.DefaultValue);
+ }
- [Fact]
- public void ParseRouteParameter_DefaultValueWithEqualsSignIn_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par=test(am):est=asd");
+ [Fact]
+ public void ParseRouteParameter_DefaultValueWithEqualsSignIn_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par=test(am):est=asd");
- // Assert
- Assert.Equal("par", templatePart.Name);
- Assert.Equal("test(am):est=asd", templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal("par", templatePart.Name);
+ Assert.Equal("test(am):est=asd", templatePart.DefaultValue);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(=)=sds");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(=)=sds");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("sds", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("sds", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("test(=)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("test(=)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\{)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\{)");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\{)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\{)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenBraceInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par{am:test(\sd)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenBraceInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par{am:test(\sd)");
- // Assert
- Assert.Equal("par{am", templatePart.Name);
+ // Assert
+ Assert.Equal("par{am", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\sd)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\sd)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\{)=xvc");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\{)=xvc");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("xvc", templatePart.DefaultValue);
+ Assert.Equal("xvc", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\{)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\{)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par(am:test(\()");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par(am:test(\()");
- // Assert
- Assert.Equal("par(am", templatePart.Name);
+ // Assert
+ Assert.Equal("par(am", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\()", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\()", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\()");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\()");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\()", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\()", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenNoCloseParen_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#$%");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenNoCloseParen_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#$%");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal("test(#$%", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal("test(#$%", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenAndColon_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#:test1");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenAndColon_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#:test1");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(#", constraint.Constraint),
- constraint => Assert.Equal(@"test1", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(#", constraint.Constraint),
+ constraint => Assert.Equal(@"test1", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenAndColonWithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(abc:somevalue):name(test1:differentname=default-value");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenAndColonWithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(abc:somevalue):name(test1:differentname=default-value");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("default-value", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("default-value", templatePart.DefaultValue);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Constraint),
- constraint => Assert.Equal(@"name(test1", constraint.Constraint),
- constraint => Assert.Equal(@"differentname", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Constraint),
+ constraint => Assert.Equal(@"name(test1", constraint.Constraint),
+ constraint => Assert.Equal(@"differentname", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenAndDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(constraintvalue=test1");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenAndDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(constraintvalue=test1");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("test1", templatePart.DefaultValue);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("test1", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(constraintvalue", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(constraintvalue", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\()=djk");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\()=djk");
- // Assert
- Assert.Equal("param", templatePart.Name);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
- Assert.Equal("djk", templatePart.DefaultValue);
+ Assert.Equal("djk", templatePart.DefaultValue);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\()", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\()", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\?)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\?)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\?)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\?)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)=sdf");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)=sdf");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("sdf", templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("sdf", templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\?)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\?)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)=sdf?");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)=sdf?");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("sdf", templatePart.DefaultValue);
- Assert.True(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("sdf", templatePart.DefaultValue);
+ Assert.True(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\?)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\?)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par?am:test(\?)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par?am:test(\?)");
- // Assert
- Assert.Equal("par?am", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("par?am", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(\?)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(\?)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosedParenAndColonInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#):$)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosedParenAndColonInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#):$)");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- Assert.Collection(templatePart.InlineConstraints,
- constraint => Assert.Equal(@"test(#)", constraint.Constraint),
- constraint => Assert.Equal(@"$)", constraint.Constraint));
- }
+ Assert.Collection(templatePart.InlineConstraints,
+ constraint => Assert.Equal(@"test(#)", constraint.Constraint),
+ constraint => Assert.Equal(@"$)", constraint.Constraint));
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonAndClosedParenInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#:)$)");
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonAndClosedParenInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#:)$)");
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"test(#:)$)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"test(#:)$)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ContainingMultipleUnclosedParenthesisInConstraint()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"foo:regex(\\(\\(\\(\\()");
+ [Fact]
+ public void ParseRouteParameter_ContainingMultipleUnclosedParenthesisInConstraint()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"foo:regex(\\(\\(\\(\\()");
- // Assert
- Assert.Equal("foo", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("foo", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithBraces_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)"); // ssn
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithBraces_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)"); // ssn
- // Assert
- Assert.Equal("p1", templatePart.Name);
- Assert.Null(templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("p1", templatePart.Name);
+ Assert.Null(templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
+ }
- [Fact]
- public void ParseRouteParameter_ConstraintWithBraces_WithDefaultValue()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)=123-456-7890"); // ssn
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithBraces_WithDefaultValue()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)=123-456-7890"); // ssn
- // Assert
- Assert.Equal("p1", templatePart.Name);
- Assert.Equal("123-456-7890", templatePart.DefaultValue);
- Assert.False(templatePart.IsOptional);
+ // Assert
+ Assert.Equal("p1", templatePart.Name);
+ Assert.Equal("123-456-7890", templatePart.DefaultValue);
+ Assert.False(templatePart.IsOptional);
- var constraint = Assert.Single(templatePart.InlineConstraints);
- Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
- }
+ var constraint = Assert.Single(templatePart.InlineConstraints);
+ Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Constraint);
+ }
- [Theory]
- [InlineData("", "")]
- [InlineData("?", "")]
- [InlineData("*", "")]
- [InlineData(" ", " ")]
- [InlineData("\t", "\t")]
- [InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
- [InlineData(",,,", ",,,")]
- public void ParseRouteParameter_ParameterWithoutInlineConstraint_ReturnsTemplatePartWithEmptyInlineValues(
- string parameter,
- string expectedParameterName)
- {
- // Arrange & Act
- var templatePart = ParseParameter(parameter);
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("?", "")]
+ [InlineData("*", "")]
+ [InlineData(" ", " ")]
+ [InlineData("\t", "\t")]
+ [InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
+ [InlineData(",,,", ",,,")]
+ public void ParseRouteParameter_ParameterWithoutInlineConstraint_ReturnsTemplatePartWithEmptyInlineValues(
+ string parameter,
+ string expectedParameterName)
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(parameter);
- // Assert
- Assert.Equal(expectedParameterName, templatePart.Name);
- Assert.Empty(templatePart.InlineConstraints);
- Assert.Null(templatePart.DefaultValue);
- }
+ // Assert
+ Assert.Equal(expectedParameterName, templatePart.Name);
+ Assert.Empty(templatePart.InlineConstraints);
+ Assert.Null(templatePart.DefaultValue);
+ }
- private TemplatePart ParseParameter(string routeParameter)
- {
- var _constraintResolver = GetConstraintResolver();
- var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
- return templatePart;
- }
+ private TemplatePart ParseParameter(string routeParameter)
+ {
+ var _constraintResolver = GetConstraintResolver();
+ var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter);
+ return templatePart;
+ }
- private static RouteTemplate ParseRouteTemplate(string template)
- {
- var _constraintResolver = GetConstraintResolver();
- return TemplateParser.Parse(template);
- }
+ private static RouteTemplate ParseRouteTemplate(string template)
+ {
+ var _constraintResolver = GetConstraintResolver();
+ return TemplateParser.Parse(template);
+ }
- private static IInlineConstraintResolver GetConstraintResolver()
+ private static IInlineConstraintResolver GetConstraintResolver()
+ {
+ var services = new ServiceCollection().AddOptions();
+ services.Configure<RouteOptions>(options =>
+ options
+ .ConstraintMap
+ .Add("test", typeof(TestRouteConstraint)));
+ var serviceProvider = services.BuildServiceProvider();
+ var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
+ return new DefaultInlineConstraintResolver(accessor, serviceProvider);
+ }
+
+ private class TestRouteConstraint : IRouteConstraint
+ {
+ public TestRouteConstraint(string pattern)
{
- var services = new ServiceCollection().AddOptions();
- services.Configure<RouteOptions>(options =>
- options
- .ConstraintMap
- .Add("test", typeof(TestRouteConstraint)));
- var serviceProvider = services.BuildServiceProvider();
- var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
- return new DefaultInlineConstraintResolver(accessor, serviceProvider);
+ Pattern = pattern;
}
- private class TestRouteConstraint : IRouteConstraint
+ public string Pattern { get; private set; }
+ public bool Match(HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
{
- public TestRouteConstraint(string pattern)
- {
- Pattern = pattern;
- }
-
- public string Pattern { get; private set; }
- public bool Match(HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Internal/DfaGraphWriterTest.cs b/src/Http/Routing/test/UnitTests/Internal/DfaGraphWriterTest.cs
index cd47f54953..1b55344ba6 100644
--- a/src/Http/Routing/test/UnitTests/Internal/DfaGraphWriterTest.cs
+++ b/src/Http/Routing/test/UnitTests/Internal/DfaGraphWriterTest.cs
@@ -8,85 +8,84 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Internal
+namespace Microsoft.AspNetCore.Routing.Internal;
+
+public class DfaGraphWriterTest
{
- public class DfaGraphWriterTest
+ private DfaGraphWriter CreateGraphWriter()
{
- private DfaGraphWriter CreateGraphWriter()
- {
- ServiceCollection services = new ServiceCollection();
- services.AddLogging();
- services.AddRouting();
+ ServiceCollection services = new ServiceCollection();
+ services.AddLogging();
+ services.AddRouting();
- return new DfaGraphWriter(services.BuildServiceProvider());
- }
+ return new DfaGraphWriter(services.BuildServiceProvider());
+ }
- [Fact]
- public void Write_ExcludeNonRouteEndpoint()
- {
- // Arrange
- var graphWriter = CreateGraphWriter();
- var writer = new StringWriter();
- var endpointsDataSource = new DefaultEndpointDataSource(new Endpoint((context) => null, EndpointMetadataCollection.Empty, string.Empty));
+ [Fact]
+ public void Write_ExcludeNonRouteEndpoint()
+ {
+ // Arrange
+ var graphWriter = CreateGraphWriter();
+ var writer = new StringWriter();
+ var endpointsDataSource = new DefaultEndpointDataSource(new Endpoint((context) => null, EndpointMetadataCollection.Empty, string.Empty));
- // Act
- graphWriter.Write(endpointsDataSource, writer);
+ // Act
+ graphWriter.Write(endpointsDataSource, writer);
- // Assert
- Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
+ // Assert
+ Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
@"0 [label=""/""]",
"}") + Environment.NewLine, writer.ToString());
- }
+ }
- [Fact]
- public void Write_ExcludeRouteEndpointWithSuppressMatchingMetadata()
- {
- // Arrange
- var graphWriter = CreateGraphWriter();
- var writer = new StringWriter();
- var endpointsDataSource = new DefaultEndpointDataSource(
- new RouteEndpoint(
- (context) => null,
- RoutePatternFactory.Parse("/"),
- 0,
- new EndpointMetadataCollection(new SuppressMatchingMetadata()),
- string.Empty));
+ [Fact]
+ public void Write_ExcludeRouteEndpointWithSuppressMatchingMetadata()
+ {
+ // Arrange
+ var graphWriter = CreateGraphWriter();
+ var writer = new StringWriter();
+ var endpointsDataSource = new DefaultEndpointDataSource(
+ new RouteEndpoint(
+ (context) => null,
+ RoutePatternFactory.Parse("/"),
+ 0,
+ new EndpointMetadataCollection(new SuppressMatchingMetadata()),
+ string.Empty));
- // Act
- graphWriter.Write(endpointsDataSource, writer);
+ // Act
+ graphWriter.Write(endpointsDataSource, writer);
- // Assert
- Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
+ // Assert
+ Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
@"0 [label=""/""]",
@"}") + Environment.NewLine, writer.ToString());
- }
+ }
- [Fact]
- public void Write_IncludeRouteEndpointWithPolicy()
- {
- // Arrange
- var graphWriter = CreateGraphWriter();
- var writer = new StringWriter();
- var endpointsDataSource = new DefaultEndpointDataSource(
- new RouteEndpoint(
- (context) => null,
- RoutePatternFactory.Parse("/"),
- 0,
- new EndpointMetadataCollection(new HttpMethodMetadata(new[] { "GET" })),
- string.Empty));
+ [Fact]
+ public void Write_IncludeRouteEndpointWithPolicy()
+ {
+ // Arrange
+ var graphWriter = CreateGraphWriter();
+ var writer = new StringWriter();
+ var endpointsDataSource = new DefaultEndpointDataSource(
+ new RouteEndpoint(
+ (context) => null,
+ RoutePatternFactory.Parse("/"),
+ 0,
+ new EndpointMetadataCollection(new HttpMethodMetadata(new[] { "GET" })),
+ string.Empty));
- // Act
- graphWriter.Write(endpointsDataSource, writer);
+ // Act
+ graphWriter.Write(endpointsDataSource, writer);
- // Assert
- var sdf = writer.ToString();
- Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
+ // Assert
+ var sdf = writer.ToString();
+ Assert.Equal(String.Join(Environment.NewLine, @"digraph DFA {",
@"0 [label=""/ HTTP: GET""]",
@"1 [label=""/ HTTP: *""]",
@"2 -> 0 [label=""HTTP: GET""]",
@"2 -> 1 [label=""HTTP: *""]",
@"2 [label=""/""]",
@"}") + Environment.NewLine, sdf);
- }
}
}
diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs
index 82d998d241..679a19963b 100644
--- a/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs
@@ -6,144 +6,143 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// Integration tests for GetXyzByName. These are basic because important behavioral details
+// are covered elsewhere.
+//
+// Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
+// and DefaultLinkGeneratorProcessTemplateTest
+//
+// Does not cover the EndpointNameAddressScheme in detail. see EndpointNameAddressSchemeTest
+public class LinkGeneratorEndpointNameExtensionsTest : LinkGeneratorTestBase
{
- // Integration tests for GetXyzByName. These are basic because important behavioral details
- // are covered elsewhere.
- //
- // Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
- // and DefaultLinkGeneratorProcessTemplateTest
- //
- // Does not cover the EndpointNameAddressScheme in detail. see EndpointNameAddressSchemeTest
- public class LinkGeneratorEndpointNameExtensionsTest : LinkGeneratorTestBase
+ [Fact]
+ public void GetPathByName_WithHttpContext_DoesNotUseAmbientValues()
{
- [Fact]
- public void GetPathByName_WithHttpContext_DoesNotUseAmbientValues()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues = new RouteValueDictionary(new { p = "5", });
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- var values = new { query = "some?query", };
-
- // Act
- var path = linkGenerator.GetPathByName(
- httpContext,
- endpointName: "name2",
- values,
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Null(path);
- }
-
- [Fact]
- public void GetPathByName_WithoutHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var values = new { p = "In?dex", query = "some?query", };
-
- // Act
- var path = linkGenerator.GetPathByName(
- endpointName: "name2",
- values,
- new PathString("/Foo/Bar?encodeme?"),
- new FragmentString("#Fragment?"),
- new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetPathByName_WithHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- var values = new { p = "In?dex", query = "some?query", };
-
- // Act
- var path = linkGenerator.GetPathByName(
- httpContext,
- endpointName: "name2",
- values,
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetUriByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var values = new { p = "In?dex", query = "some?query", };
-
- // Act
- var path = linkGenerator.GetUriByName(
- endpointName: "name2",
- values,
- "http",
- new HostString("example.com"),
- new PathString("/Foo/Bar?encodeme?"),
- new FragmentString("#Fragment?"),
- new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetUriByName_WithHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- var values = new { p = "In?dex", query = "some?query", };
-
- // Act
- var uri = linkGenerator.GetUriByName(
- httpContext,
- endpointName: "name2",
- values,
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
- }
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues = new RouteValueDictionary(new { p = "5", });
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ var values = new { query = "some?query", };
+
+ // Act
+ var path = linkGenerator.GetPathByName(
+ httpContext,
+ endpointName: "name2",
+ values,
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Null(path);
+ }
+
+ [Fact]
+ public void GetPathByName_WithoutHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var values = new { p = "In?dex", query = "some?query", };
+
+ // Act
+ var path = linkGenerator.GetPathByName(
+ endpointName: "name2",
+ values,
+ new PathString("/Foo/Bar?encodeme?"),
+ new FragmentString("#Fragment?"),
+ new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetPathByName_WithHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ var values = new { p = "In?dex", query = "some?query", };
+
+ // Act
+ var path = linkGenerator.GetPathByName(
+ httpContext,
+ endpointName: "name2",
+ values,
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetUriByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var values = new { p = "In?dex", query = "some?query", };
+
+ // Act
+ var path = linkGenerator.GetUriByName(
+ endpointName: "name2",
+ values,
+ "http",
+ new HostString("example.com"),
+ new PathString("/Foo/Bar?encodeme?"),
+ new FragmentString("#Fragment?"),
+ new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetUriByName_WithHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("some-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name1"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("some#-other-endpoint/{p}", metadata: new[] { new EndpointNameMetadata("name2"), });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ var values = new { p = "In?dex", query = "some?query", };
+
+ // Act
+ var uri = linkGenerator.GetUriByName(
+ httpContext,
+ endpointName: "name2",
+ values,
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/some%23-other-endpoint/In%3Fdex/?query=some%3Fquery#Fragment?", uri);
}
}
diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorIntegrationTest.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorIntegrationTest.cs
index 056d644961..91695aed82 100644
--- a/src/Http/Routing/test/UnitTests/LinkGeneratorIntegrationTest.cs
+++ b/src/Http/Routing/test/UnitTests/LinkGeneratorIntegrationTest.cs
@@ -1,23 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Patterns;
-using System.Collections.Generic;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// This is a set of integration tests that are similar to a typical MVC configuration.
+//
+// We're doing this here because it's relatively expensive to test these scenarios
+// inside MVC - it requires creating actual controllers and pages.
+public class LinkGeneratorIntegrationTest : LinkGeneratorTestBase
{
- // This is a set of integration tests that are similar to a typical MVC configuration.
- //
- // We're doing this here because it's relatively expensive to test these scenarios
- // inside MVC - it requires creating actual controllers and pages.
- public class LinkGeneratorIntegrationTest : LinkGeneratorTestBase
+ public LinkGeneratorIntegrationTest()
{
- public LinkGeneratorIntegrationTest()
- {
- var endpoints = new List<Endpoint>()
+ var endpoints = new List<Endpoint>()
{
// Attribute routed endpoint 1
EndpointFactory.CreateRouteEndpoint(
@@ -202,513 +202,512 @@ namespace Microsoft.AspNetCore.Routing
metadata: new object[] { new SuppressLinkGenerationMetadata(), }),
};
- Endpoints = endpoints;
- LinkGenerator = CreateLinkGenerator(endpoints.ToArray());
- }
+ Endpoints = endpoints;
+ LinkGenerator = CreateLinkGenerator(endpoints.ToArray());
+ }
- private IReadOnlyList<Endpoint> Endpoints { get; }
+ private IReadOnlyList<Endpoint> Endpoints { get; }
- private LinkGenerator LinkGenerator { get; }
+ private LinkGenerator LinkGenerator { get; }
- #region Without ambient values (simple cases)
+ #region Without ambient values (simple cases)
- [Fact]
- public void GetPathByAddress_LinkToAttributedAction_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Pets", action = "GetById", id = "17", };
- var ambientValues = new { };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/api/Pets/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalAction_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Home", action = "Index", };
- var ambientValues = new { };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalActionInArea_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { area = "Admin", controller = "Users", action = "Add", };
- var ambientValues = new { };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Admin/Users/Add", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalRoute_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Store", id = "17", };
- var ambientValues = new { };
- var address = CreateAddress(routeName: "custom", values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/api/Store/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToPage_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { page = "/Pages/Index", };
- var ambientValues = new { };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pages", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToPageInArea_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { area = "Admin", page = "/Pages/Index", };
- var ambientValues = new { };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Admin/Pages", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToNonExistentAction_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
+ [Fact]
+ public void GetPathByAddress_LinkToAttributedAction_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Pets", action = "GetById", id = "17", };
+ var ambientValues = new { };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/api/Pets/17", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalAction_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Home", action = "Index", };
+ var ambientValues = new { };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalActionInArea_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { area = "Admin", controller = "Users", action = "Add", };
+ var ambientValues = new { };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Admin/Users/Add", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalRoute_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Store", id = "17", };
+ var ambientValues = new { };
+ var address = CreateAddress(routeName: "custom", values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/api/Store/17", path);
+ }
- var values = new { controller = "Home", action = "Fake", id = "17", };
- var ambientValues = new { };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
+ [Fact]
+ public void GetPathByAddress_LinkToPage_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { page = "/Pages/Index", };
+ var ambientValues = new { };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pages", path);
+ }
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
+ [Fact]
+ public void GetPathByAddress_LinkToPageInArea_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { area = "Admin", page = "/Pages/Index", };
+ var ambientValues = new { };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Admin/Pages", path);
+ }
- // Assert
- Assert.Equal("/Home/Fake/17", path);
- }
+ [Fact]
+ public void GetPathByAddress_LinkToNonExistentAction_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Home", action = "Fake", id = "17", };
+ var ambientValues = new { };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Home/Fake/17", path);
+ }
- #endregion
+ #endregion
- #region With ambient values
+ #region With ambient values
- [Fact]
- public void GetPathByAddress_LinkToAttributedAction_FromSameAction_KeepsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Pets", action = "GetById", };
- var ambientValues = new { controller = "Pets", action = "GetById", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/api/Pets/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToAttributedAction_FromAnotherAction_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Pets", action = "GetById", };
- var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pets/GetById", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToAttributedAction_FromPage_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Pets", action = "GetById", };
- var ambientValues = new { page = "/Pages/Help", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pets/GetById", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalAction_FromSameAction_KeepsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Home", action = "Index", };
- var ambientValues = new { controller = "Home", action = "Index", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Home/Index/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalAction_FromAnotherAction_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Home", action = "Index", };
- var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalAction_FromPage_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Home", action = "Index", };
- var ambientValues = new { page = "/Pages/Help", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToNonExistentConventionalAction_FromAnotherAction_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Home", action = "Index11", };
- var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Home/Index11", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToNonExistentAreaAction_FromAnotherAction_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { area = "Admin", controller = "Home", action = "Index11", };
- var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Admin/Home/Index11", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalRoute_FromAction_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Store", };
- var ambientValues = new { controller = "Home", action = "Index", id = "17", };
- var address = CreateAddress(routeName: "custom", values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/api/Store", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalRoute_WithAmbientValues_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { controller = "Store", id = "17", };
- var ambientValues = new { controller = "Store", };
- var address = CreateAddress(routeName: "custom", values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/api/Store/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToConventionalRouteWithoutSharedAmbientValues_WithAmbientValues_GeneratesPath()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { custom2 = "17", };
- var ambientValues = new { controller = "Store", };
- var address = CreateAddress(routeName: "custom2", values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/api/Foo/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToPage_FromSamePage_KeepsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { page = "/Pages/Help", };
- var ambientValues = new { page = "/Pages/Help", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pages/Help/17", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToPage_FromAction_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { page = "/Pages/Help", };
- var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pages/Help", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToPage_FromAnotherPage_DiscardsAmbientValues()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { page = "/Pages/Help", };
- var ambientValues = new { page = "/Pages/About", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pages/Help", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToNonExistentPage_FromAction_MatchesActionConventionalRoute()
- {
- // Arrange
- var httpContext = CreateHttpContext();
-
- var values = new { page = "/Pages/Help2", };
- var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
-
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
-
- // Assert
- Assert.Equal("/Pets/Update?page=%2FPages%2FHelp2", path);
- }
-
- [Fact]
- public void GetPathByAddress_LinkToPageInSameArea_FromAction_UsingAreaAmbientValue()
- {
- // Arrange
- var httpContext = CreateHttpContext();
+ [Fact]
+ public void GetPathByAddress_LinkToAttributedAction_FromSameAction_KeepsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Pets", action = "GetById", };
+ var ambientValues = new { controller = "Pets", action = "GetById", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/api/Pets/17", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToAttributedAction_FromAnotherAction_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Pets", action = "GetById", };
+ var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pets/GetById", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToAttributedAction_FromPage_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Pets", action = "GetById", };
+ var ambientValues = new { page = "/Pages/Help", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pets/GetById", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalAction_FromSameAction_KeepsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Home", action = "Index", };
+ var ambientValues = new { controller = "Home", action = "Index", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Home/Index/17", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalAction_FromAnotherAction_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Home", action = "Index", };
+ var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalAction_FromPage_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Home", action = "Index", };
+ var ambientValues = new { page = "/Pages/Help", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToNonExistentConventionalAction_FromAnotherAction_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Home", action = "Index11", };
+ var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Home/Index11", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToNonExistentAreaAction_FromAnotherAction_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { area = "Admin", controller = "Home", action = "Index11", };
+ var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Admin/Home/Index11", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalRoute_FromAction_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Store", };
+ var ambientValues = new { controller = "Home", action = "Index", id = "17", };
+ var address = CreateAddress(routeName: "custom", values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/api/Store", path);
+ }
- var values = new { page = "/Pages/Index", };
- var ambientValues = new { area = "Admin", controller = "Users", action = "Add", };
- var address = CreateAddress(values: values, ambientValues: ambientValues);
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalRoute_WithAmbientValues_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { controller = "Store", id = "17", };
+ var ambientValues = new { controller = "Store", };
+ var address = CreateAddress(routeName: "custom", values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/api/Store/17", path);
+ }
- // Act
- var path = LinkGenerator.GetPathByAddress(
- httpContext,
- address,
- address.ExplicitValues,
- address.AmbientValues);
+ [Fact]
+ public void GetPathByAddress_LinkToConventionalRouteWithoutSharedAmbientValues_WithAmbientValues_GeneratesPath()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { custom2 = "17", };
+ var ambientValues = new { controller = "Store", };
+ var address = CreateAddress(routeName: "custom2", values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/api/Foo/17", path);
+ }
- // Assert
- Assert.Equal("/Admin/Pages", path);
- }
+ [Fact]
+ public void GetPathByAddress_LinkToPage_FromSamePage_KeepsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { page = "/Pages/Help", };
+ var ambientValues = new { page = "/Pages/Help", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pages/Help/17", path);
+ }
- #endregion
+ [Fact]
+ public void GetPathByAddress_LinkToPage_FromAction_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { page = "/Pages/Help", };
+ var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pages/Help", path);
+ }
- private static RouteValuesAddress CreateAddress(string routeName = null, object values = null, object ambientValues = null)
+ [Fact]
+ public void GetPathByAddress_LinkToPage_FromAnotherPage_DiscardsAmbientValues()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { page = "/Pages/Help", };
+ var ambientValues = new { page = "/Pages/About", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pages/Help", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToNonExistentPage_FromAction_MatchesActionConventionalRoute()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { page = "/Pages/Help2", };
+ var ambientValues = new { controller = "Pets", action = "Update", id = "17", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Pets/Update?page=%2FPages%2FHelp2", path);
+ }
+
+ [Fact]
+ public void GetPathByAddress_LinkToPageInSameArea_FromAction_UsingAreaAmbientValue()
+ {
+ // Arrange
+ var httpContext = CreateHttpContext();
+
+ var values = new { page = "/Pages/Index", };
+ var ambientValues = new { area = "Admin", controller = "Users", action = "Add", };
+ var address = CreateAddress(values: values, ambientValues: ambientValues);
+
+ // Act
+ var path = LinkGenerator.GetPathByAddress(
+ httpContext,
+ address,
+ address.ExplicitValues,
+ address.AmbientValues);
+
+ // Assert
+ Assert.Equal("/Admin/Pages", path);
+ }
+
+ #endregion
+
+ private static RouteValuesAddress CreateAddress(string routeName = null, object values = null, object ambientValues = null)
+ {
+ return new RouteValuesAddress()
{
- return new RouteValuesAddress()
- {
- RouteName = routeName,
- ExplicitValues = new RouteValueDictionary(values),
- AmbientValues = new RouteValueDictionary(ambientValues),
- };
- }
+ RouteName = routeName,
+ ExplicitValues = new RouteValueDictionary(values),
+ AmbientValues = new RouteValueDictionary(ambientValues),
+ };
}
}
diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs
index 02c344b499..00e31d0ccb 100644
--- a/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs
@@ -6,196 +6,195 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// Integration tests for GetXyzByRouteValues. These are basic because important behavioral details
+// are covered elsewhere.
+//
+// Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
+// and DefaultLinkGeneratorProcessTemplateTest
+//
+// Does not cover the RouteValuesAddressScheme in detail. see RouteValuesAddressSchemeTest
+public class LinkGeneratorRouteValuesAddressExtensionsTest : LinkGeneratorTestBase
{
- // Integration tests for GetXyzByRouteValues. These are basic because important behavioral details
- // are covered elsewhere.
- //
- // Does not cover template processing in detail, those scenarios are validated by TemplateBinderTests
- // and DefaultLinkGeneratorProcessTemplateTest
- //
- // Does not cover the RouteValuesAddressScheme in detail. see RouteValuesAddressSchemeTest
- public class LinkGeneratorRouteValuesAddressExtensionsTest : LinkGeneratorTestBase
+ [Fact]
+ public void GetPathByRouteValues_WithHttpContext_UsesAmbientValues()
{
- [Fact]
- public void GetPathByRouteValues_WithHttpContext_UsesAmbientValues()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.RouteValues = new RouteValueDictionary(new { action = "Index", });
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- // Act
- var path = linkGenerator.GetPathByRouteValues(
- httpContext,
- routeName: null,
- values: new RouteValueDictionary(new { controller = "Home", query = "some?query" }),
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetPathByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- // Act
- var path = linkGenerator.GetPathByRouteValues(
- routeName: null,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
- new PathString("/Foo/Bar?encodeme?"),
- new FragmentString("#Fragment?"),
- new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetPathByRouteValues_WithHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- // Act
- var path = linkGenerator.GetPathByRouteValues(
- httpContext,
- routeName: null,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetUriByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- // Act
- var path = linkGenerator.GetUriByRouteValues(
- routeName: null,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
- "http",
- new HostString("example.com"),
- new PathString("/Foo/Bar?encodeme?"),
- new FragmentString("#Fragment?"),
- new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
- }
-
- [Fact]
- public void GetUriByRouteValues_WithHttpContext_WithPathBaseAndFragment()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext();
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- // Act
- var uri = linkGenerator.GetUriByRouteValues(
- httpContext,
- routeName: null,
- values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri);
- }
-
- [Fact]
- public void GetUriByRouteValues_WithHttpContext_CanUseAmbientValues()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint(
- "Home/Index/{id?}",
- defaults: new { controller = "Home", action = "Index", },
- requiredValues: new { controller = "Home", action = "Index", });
-
- var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
-
- var httpContext = CreateHttpContext(new { controller = "Home", });
- httpContext.Request.Scheme = "http";
- httpContext.Request.Host = new HostString("example.com");
- httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
-
- // Act
- var uri = linkGenerator.GetUriByRouteValues(
- httpContext,
- routeName: null,
- values: new RouteValueDictionary(new { action = "Index", query = "some?query" }),
- fragment: new FragmentString("#Fragment?"),
- options: new LinkOptions() { AppendTrailingSlash = true, });
-
- // Assert
- Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri);
- }
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.RouteValues = new RouteValueDictionary(new { action = "Index", });
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ // Act
+ var path = linkGenerator.GetPathByRouteValues(
+ httpContext,
+ routeName: null,
+ values: new RouteValueDictionary(new { controller = "Home", query = "some?query" }),
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetPathByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ // Act
+ var path = linkGenerator.GetPathByRouteValues(
+ routeName: null,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
+ new PathString("/Foo/Bar?encodeme?"),
+ new FragmentString("#Fragment?"),
+ new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetPathByRouteValues_WithHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ // Act
+ var path = linkGenerator.GetPathByRouteValues(
+ httpContext,
+ routeName: null,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetUriByRouteValues_WithoutHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ // Act
+ var path = linkGenerator.GetUriByRouteValues(
+ routeName: null,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
+ "http",
+ new HostString("example.com"),
+ new PathString("/Foo/Bar?encodeme?"),
+ new FragmentString("#Fragment?"),
+ new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path);
+ }
+
+ [Fact]
+ public void GetUriByRouteValues_WithHttpContext_WithPathBaseAndFragment()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext();
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ // Act
+ var uri = linkGenerator.GetUriByRouteValues(
+ httpContext,
+ routeName: null,
+ values: new RouteValueDictionary(new { controller = "Home", action = "Index", query = "some?query" }),
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri);
+ }
+
+ [Fact]
+ public void GetUriByRouteValues_WithHttpContext_CanUseAmbientValues()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint(
+ "Home/Index/{id?}",
+ defaults: new { controller = "Home", action = "Index", },
+ requiredValues: new { controller = "Home", action = "Index", });
+
+ var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
+
+ var httpContext = CreateHttpContext(new { controller = "Home", });
+ httpContext.Request.Scheme = "http";
+ httpContext.Request.Host = new HostString("example.com");
+ httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
+
+ // Act
+ var uri = linkGenerator.GetUriByRouteValues(
+ httpContext,
+ routeName: null,
+ values: new RouteValueDictionary(new { action = "Index", query = "some?query" }),
+ fragment: new FragmentString("#Fragment?"),
+ options: new LinkOptions() { AppendTrailingSlash = true, });
+
+ // Assert
+ Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri);
}
}
diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs
index 4dbced25f8..16d70ba912 100644
--- a/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs
+++ b/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs
@@ -8,76 +8,75 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public abstract class LinkGeneratorTestBase
{
- public abstract class LinkGeneratorTestBase
+ protected HttpContext CreateHttpContext(object ambientValues = null)
{
- protected HttpContext CreateHttpContext(object ambientValues = null)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.RouteValues = new RouteValueDictionary(ambientValues);
- return httpContext;
- }
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.RouteValues = new RouteValueDictionary(ambientValues);
+ return httpContext;
+ }
- protected ServiceCollection GetBasicServices()
- {
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- services.AddLogging();
- return services;
- }
+ protected ServiceCollection GetBasicServices()
+ {
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ services.AddLogging();
+ return services;
+ }
- protected virtual void AddAdditionalServices(IServiceCollection services)
- {
- }
+ protected virtual void AddAdditionalServices(IServiceCollection services)
+ {
+ }
- private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
- {
- return CreateLinkGenerator(configureServices: null, endpoints);
- }
+ private protected DefaultLinkGenerator CreateLinkGenerator(params Endpoint[] endpoints)
+ {
+ return CreateLinkGenerator(configureServices: null, endpoints);
+ }
- private protected DefaultLinkGenerator CreateLinkGenerator(
- Action<IServiceCollection> configureServices,
- params Endpoint[] endpoints)
- {
- return CreateLinkGenerator(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
- }
+ private protected DefaultLinkGenerator CreateLinkGenerator(
+ Action<IServiceCollection> configureServices,
+ params Endpoint[] endpoints)
+ {
+ return CreateLinkGenerator(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
+ }
- private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources)
- {
- return CreateLinkGenerator(configureServices: null, dataSources);
- }
+ private protected DefaultLinkGenerator CreateLinkGenerator(EndpointDataSource[] dataSources)
+ {
+ return CreateLinkGenerator(configureServices: null, dataSources);
+ }
- private protected DefaultLinkGenerator CreateLinkGenerator(
- Action<IServiceCollection> configureServices,
- EndpointDataSource[] dataSources)
- {
- var services = GetBasicServices();
- AddAdditionalServices(services);
- configureServices?.Invoke(services);
+ private protected DefaultLinkGenerator CreateLinkGenerator(
+ Action<IServiceCollection> configureServices,
+ EndpointDataSource[] dataSources)
+ {
+ var services = GetBasicServices();
+ AddAdditionalServices(services);
+ configureServices?.Invoke(services);
- services.Configure<RouteOptions>(o =>
+ services.Configure<RouteOptions>(o =>
+ {
+ if (dataSources != null)
{
- if (dataSources != null)
+ foreach (var dataSource in dataSources)
{
- foreach (var dataSource in dataSources)
- {
- o.EndpointDataSources.Add(dataSource);
- }
+ o.EndpointDataSources.Add(dataSource);
}
- });
+ }
+ });
- var serviceProvider = services.BuildServiceProvider();
- var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
+ var serviceProvider = services.BuildServiceProvider();
+ var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
- return new DefaultLinkGenerator(
- new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
- serviceProvider.GetRequiredService<TemplateBinderFactory>(),
- new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
- routeOptions,
- NullLogger<DefaultLinkGenerator>.Instance,
- serviceProvider);
- }
+ return new DefaultLinkGenerator(
+ new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
+ serviceProvider.GetRequiredService<TemplateBinderFactory>(),
+ new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
+ routeOptions,
+ NullLogger<DefaultLinkGenerator>.Instance,
+ serviceProvider);
}
}
diff --git a/src/Http/Routing/test/UnitTests/LinkParserEndpointNameExtensionsTest.cs b/src/Http/Routing/test/UnitTests/LinkParserEndpointNameExtensionsTest.cs
index 096cd45fb5..b6857a24b6 100644
--- a/src/Http/Routing/test/UnitTests/LinkParserEndpointNameExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/LinkParserEndpointNameExtensionsTest.cs
@@ -4,54 +4,53 @@
using Microsoft.AspNetCore.Routing.Matching;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class LinkParserEndpointNameExtensionsTest : LinkParserTestBase
{
- public class LinkParserEndpointNameExtensionsTest : LinkParserTestBase
+ [Fact]
+ public void ParsePathByAddresss_NoMatchingEndpoint_ReturnsNull()
{
- [Fact]
- public void ParsePathByAddresss_NoMatchingEndpoint_ReturnsNull()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new EndpointNameMetadata("Test2"), });
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id?}", metadata: new object[] { new EndpointNameMetadata("Test2"), });
- var parser = CreateLinkParser(endpoint);
+ var parser = CreateLinkParser(endpoint);
- // Act
- var values = parser.ParsePathByEndpointName("Test", "/Home/Index/17");
+ // Act
+ var values = parser.ParsePathByEndpointName("Test", "/Home/Index/17");
- // Assert
- Assert.Null(values);
- }
+ // Assert
+ Assert.Null(values);
+ }
- [Fact]
- public void ParsePathByAddresss_HasMatches_ReturnsNullWhenParsingFails()
- {
- // Arrange
- var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new EndpointNameMetadata("Test2"), });
- var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2}", metadata: new object[] { new EndpointNameMetadata("Test"), });
+ [Fact]
+ public void ParsePathByAddresss_HasMatches_ReturnsNullWhenParsingFails()
+ {
+ // Arrange
+ var endpoint1 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new EndpointNameMetadata("Test2"), });
+ var endpoint2 = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id2}", metadata: new object[] { new EndpointNameMetadata("Test"), });
- var parser = CreateLinkParser(endpoint1, endpoint2);
+ var parser = CreateLinkParser(endpoint1, endpoint2);
- // Act
- var values = parser.ParsePathByEndpointName("Test", "/");
+ // Act
+ var values = parser.ParsePathByEndpointName("Test", "/");
- // Assert
- Assert.Null(values);
- }
+ // Assert
+ Assert.Null(values);
+ }
- [Fact] // Endpoint name does not support multiple matches
- public void ParsePathByAddresss_HasMatches_ReturnsFirstSuccessfulParse()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new EndpointNameMetadata("Test"), });
+ [Fact] // Endpoint name does not support multiple matches
+ public void ParsePathByAddresss_HasMatches_ReturnsFirstSuccessfulParse()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint("{controller}/{action}/{id}", metadata: new object[] { new EndpointNameMetadata("Test"), });
- var parser = CreateLinkParser(endpoint);
+ var parser = CreateLinkParser(endpoint);
- // Act
- var values = parser.ParsePathByEndpointName("Test", "/Home/Index/17");
+ // Act
+ var values = parser.ParsePathByEndpointName("Test", "/Home/Index/17");
- // Assert
- MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", id = "17" }, values);
- }
+ // Assert
+ MatcherAssert.AssertRouteValuesEqual(new { controller = "Home", action = "Index", id = "17" }, values);
}
}
diff --git a/src/Http/Routing/test/UnitTests/LinkParserTestBase.cs b/src/Http/Routing/test/UnitTests/LinkParserTestBase.cs
index b2c6d46b30..571ad7e324 100644
--- a/src/Http/Routing/test/UnitTests/LinkParserTestBase.cs
+++ b/src/Http/Routing/test/UnitTests/LinkParserTestBase.cs
@@ -8,67 +8,66 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public abstract class LinkParserTestBase
{
- public abstract class LinkParserTestBase
+ protected ServiceCollection GetBasicServices()
{
- protected ServiceCollection GetBasicServices()
- {
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- services.AddLogging();
- return services;
- }
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ services.AddLogging();
+ return services;
+ }
- protected virtual void AddAdditionalServices(IServiceCollection services)
- {
- }
+ protected virtual void AddAdditionalServices(IServiceCollection services)
+ {
+ }
- private protected DefaultLinkParser CreateLinkParser(params Endpoint[] endpoints)
- {
- return CreateLinkParser(configureServices: null, endpoints);
- }
+ private protected DefaultLinkParser CreateLinkParser(params Endpoint[] endpoints)
+ {
+ return CreateLinkParser(configureServices: null, endpoints);
+ }
- private protected DefaultLinkParser CreateLinkParser(
- Action<IServiceCollection> configureServices,
- params Endpoint[] endpoints)
- {
- return CreateLinkParser(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
- }
+ private protected DefaultLinkParser CreateLinkParser(
+ Action<IServiceCollection> configureServices,
+ params Endpoint[] endpoints)
+ {
+ return CreateLinkParser(configureServices, new[] { new DefaultEndpointDataSource(endpoints ?? Array.Empty<Endpoint>()) });
+ }
- private protected DefaultLinkParser CreateLinkParser(EndpointDataSource[] dataSources)
- {
- return CreateLinkParser(configureServices: null, dataSources);
- }
+ private protected DefaultLinkParser CreateLinkParser(EndpointDataSource[] dataSources)
+ {
+ return CreateLinkParser(configureServices: null, dataSources);
+ }
- private protected DefaultLinkParser CreateLinkParser(
- Action<IServiceCollection> configureServices,
- EndpointDataSource[] dataSources)
- {
- var services = GetBasicServices();
- AddAdditionalServices(services);
- configureServices?.Invoke(services);
+ private protected DefaultLinkParser CreateLinkParser(
+ Action<IServiceCollection> configureServices,
+ EndpointDataSource[] dataSources)
+ {
+ var services = GetBasicServices();
+ AddAdditionalServices(services);
+ configureServices?.Invoke(services);
- services.Configure<RouteOptions>(o =>
+ services.Configure<RouteOptions>(o =>
+ {
+ if (dataSources != null)
{
- if (dataSources != null)
+ foreach (var dataSource in dataSources)
{
- foreach (var dataSource in dataSources)
- {
- o.EndpointDataSources.Add(dataSource);
- }
+ o.EndpointDataSources.Add(dataSource);
}
- });
+ }
+ });
- var serviceProvider = services.BuildServiceProvider();
- var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
+ var serviceProvider = services.BuildServiceProvider();
+ var routeOptions = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
- return new DefaultLinkParser(
- new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
- new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
- serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<DefaultLinkParser>(),
- serviceProvider);
- }
+ return new DefaultLinkParser(
+ new DefaultParameterPolicyFactory(routeOptions, serviceProvider),
+ new CompositeEndpointDataSource(routeOptions.Value.EndpointDataSources),
+ serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<DefaultLinkParser>(),
+ serviceProvider);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Logging/WriteContext.cs b/src/Http/Routing/test/UnitTests/Logging/WriteContext.cs
index 4d971f1208..96fd8cf104 100644
--- a/src/Http/Routing/test/UnitTests/Logging/WriteContext.cs
+++ b/src/Http/Routing/test/UnitTests/Logging/WriteContext.cs
@@ -4,22 +4,21 @@
using System;
using Microsoft.Extensions.Logging;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class WriteContext
{
- public class WriteContext
- {
- public LogLevel LogLevel { get; set; }
+ public LogLevel LogLevel { get; set; }
- public int EventId { get; set; }
+ public int EventId { get; set; }
- public object State { get; set; }
+ public object State { get; set; }
- public Exception Exception { get; set; }
+ public Exception Exception { get; set; }
- public Func<object, Exception, string> Formatter { get; set; }
+ public Func<object, Exception, string> Formatter { get; set; }
- public object Scope { get; set; }
+ public object Scope { get; set; }
- public string LoggerName { get; set; }
- }
-} \ No newline at end of file
+ public string LoggerName { get; set; }
+}
diff --git a/src/Http/Routing/test/UnitTests/MatcherPolicyTest.cs b/src/Http/Routing/test/UnitTests/MatcherPolicyTest.cs
index cb462e0e90..135cb97549 100644
--- a/src/Http/Routing/test/UnitTests/MatcherPolicyTest.cs
+++ b/src/Http/Routing/test/UnitTests/MatcherPolicyTest.cs
@@ -6,87 +6,86 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class MatcherPolicyTest
{
- public class MatcherPolicyTest
+ [Fact]
+ public void ContainsDynamicEndpoint_FindsDynamicEndpoint()
{
- [Fact]
- public void ContainsDynamicEndpoint_FindsDynamicEndpoint()
+ // Arrange
+ var endpoints = new Endpoint[]
{
- // Arrange
- var endpoints = new Endpoint[]
- {
CreateEndpoint("1"),
CreateEndpoint("2"),
CreateEndpoint("3", new DynamicEndpointMetadata(isDynamic: true)),
- };
+ };
- // Act
- var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
+ // Act
+ var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void ContainsDynamicEndpoint_DoesNotFindDynamicEndpoint()
+ [Fact]
+ public void ContainsDynamicEndpoint_DoesNotFindDynamicEndpoint()
+ {
+ // Arrange
+ var endpoints = new Endpoint[]
{
- // Arrange
- var endpoints = new Endpoint[]
- {
CreateEndpoint("1"),
CreateEndpoint("2"),
CreateEndpoint("3", new DynamicEndpointMetadata(isDynamic: false)),
- };
+ };
- // Act
- var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
+ // Act
+ var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void ContainsDynamicEndpoint_DoesNotFindDynamicEndpoint_Empty()
- {
- // Arrange
- var endpoints = new Endpoint[]{ };
+ [Fact]
+ public void ContainsDynamicEndpoint_DoesNotFindDynamicEndpoint_Empty()
+ {
+ // Arrange
+ var endpoints = new Endpoint[] { };
- // Act
- var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
+ // Act
+ var result = TestMatcherPolicy.ContainsDynamicEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- private RouteEndpoint CreateEndpoint(string template, params object[] metadata)
+ private RouteEndpoint CreateEndpoint(string template, params object[] metadata)
+ {
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template),
+ 0,
+ new EndpointMetadataCollection(metadata),
+ "test");
+ }
+
+ private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ {
+ public DynamicEndpointMetadata(bool isDynamic)
{
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template),
- 0,
- new EndpointMetadataCollection(metadata),
- "test");
+ IsDynamic = isDynamic;
}
- private class DynamicEndpointMetadata : IDynamicEndpointMetadata
- {
- public DynamicEndpointMetadata(bool isDynamic)
- {
- IsDynamic = isDynamic;
- }
+ public bool IsDynamic { get; }
+ }
- public bool IsDynamic { get; }
- }
+ private class TestMatcherPolicy : MatcherPolicy
+ {
+ public override int Order => throw new System.NotImplementedException();
- private class TestMatcherPolicy : MatcherPolicy
+ public static new bool ContainsDynamicEndpoints(IReadOnlyList<Endpoint> endpoints)
{
- public override int Order => throw new System.NotImplementedException();
-
- public static new bool ContainsDynamicEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return MatcherPolicy.ContainsDynamicEndpoints(endpoints);
- }
+ return MatcherPolicy.ContainsDynamicEndpoints(endpoints);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/AcceptsMatcherPolicyTest.cs b/src/Http/Routing/test/UnitTests/Matching/AcceptsMatcherPolicyTest.cs
index dea6bc6bd1..0a86e7144f 100644
--- a/src/Http/Routing/test/UnitTests/Matching/AcceptsMatcherPolicyTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/AcceptsMatcherPolicyTest.cs
@@ -11,160 +11,160 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// There are some unit tests here for the IEndpointSelectorPolicy implementation.
+// The INodeBuilderPolicy implementation is well-tested by functional tests.
+public class AcceptsMatcherPolicyTest
{
- // There are some unit tests here for the IEndpointSelectorPolicy implementation.
- // The INodeBuilderPolicy implementation is well-tested by functional tests.
- public class AcceptsMatcherPolicyTest
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse()
{
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse()
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", null), };
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", null), };
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>())),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>())),
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/json", })),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_WithDynamicMetadata_ReturnsFalse()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_WithDynamicMetadata_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/json", })),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsTrue()
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsTrue()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/json", })),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_WithoutDynamicMetadata_ReturnsFalse()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_WithoutDynamicMetadata_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>())),
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/json", })),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void GetEdges_GroupsByContentType()
+ [Fact]
+ public void GetEdges_GroupsByContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
// These are arrange in an order that we won't actually see in a product scenario. It's done
// this way so we can verify that ordering is preserved by GetEdges.
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/json", "application/*+json", })),
@@ -174,57 +174,57 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new AcceptsMetadata(new[]{ "*/*", })),
};
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- var edges = policy.GetEdges(endpoints);
+ // Act
+ var edges = policy.GetEdges(endpoints);
- // Assert
- Assert.Collection(
- edges.OrderBy(e => e.State),
- e =>
- {
- Assert.Equal(string.Empty, e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("*/*", e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/*", e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/*+json", e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/*+xml", e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/json", e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/xml", e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- });
- }
+ // Assert
+ Assert.Collection(
+ edges.OrderBy(e => e.State),
+ e =>
+ {
+ Assert.Equal(string.Empty, e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("*/*", e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/*", e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/*+json", e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/*+xml", e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/json", e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/xml", e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ });
+ }
- [Fact] // See explanation in GetEdges for how this case is different
- public void GetEdges_GroupsByContentType_CreatesHttp415Endpoint()
+ [Fact] // See explanation in GetEdges for how this case is different
+ public void GetEdges_GroupsByContentType_CreatesHttp415Endpoint()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
// These are arrange in an order that we won't actually see in a product scenario. It's done
// this way so we can verify that ordering is preserved by GetEdges.
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/json", "application/*+json", })),
@@ -232,65 +232,65 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new AcceptsMetadata(new[] { "application/*", })),
};
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- var edges = policy.GetEdges(endpoints);
+ // Act
+ var edges = policy.GetEdges(endpoints);
- // Assert
- Assert.Collection(
- edges.OrderBy(e => e.State),
- e =>
- {
- Assert.Equal(string.Empty, e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("*/*", e.State);
- Assert.Equal(AcceptsMatcherPolicy.Http415EndpointDisplayName, Assert.Single(e.Endpoints).DisplayName);
- },
- e =>
- {
- Assert.Equal("application/*", e.State);
- Assert.Equal(new[] { endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/*+json", e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/*+xml", e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/json", e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("application/xml", e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- });
+ // Assert
+ Assert.Collection(
+ edges.OrderBy(e => e.State),
+ e =>
+ {
+ Assert.Equal(string.Empty, e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("*/*", e.State);
+ Assert.Equal(AcceptsMatcherPolicy.Http415EndpointDisplayName, Assert.Single(e.Endpoints).DisplayName);
+ },
+ e =>
+ {
+ Assert.Equal("application/*", e.State);
+ Assert.Equal(new[] { endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/*+json", e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/*+xml", e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/json", e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("application/xml", e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ });
- }
+ }
- [Theory]
- [InlineData("image/png", 1)]
- [InlineData("application/foo", 2)]
- [InlineData("text/xml", 3)]
- [InlineData("application/product+json", 6)] // application/json will match this
- [InlineData("application/product+xml", 7)] // application/xml will match this
- [InlineData("application/json", 6)]
- [InlineData("application/xml", 7)]
- public void BuildJumpTable_SortsEdgesByPriority(string contentType, int expected)
+ [Theory]
+ [InlineData("image/png", 1)]
+ [InlineData("application/foo", 2)]
+ [InlineData("text/xml", 3)]
+ [InlineData("application/product+json", 6)] // application/json will match this
+ [InlineData("application/product+xml", 7)] // application/xml will match this
+ [InlineData("application/json", 6)]
+ [InlineData("application/xml", 7)]
+ public void BuildJumpTable_SortsEdgesByPriority(string contentType, int expected)
+ {
+ // Arrange
+ var edges = new PolicyJumpTableEdge[]
{
- // Arrange
- var edges = new PolicyJumpTableEdge[]
- {
// In reverse order of how they should be processed
new PolicyJumpTableEdge(string.Empty, 0),
new PolicyJumpTableEdge("*/*", 1),
@@ -300,341 +300,340 @@ namespace Microsoft.AspNetCore.Routing.Matching
new PolicyJumpTableEdge("application/*+json", 5),
new PolicyJumpTableEdge("application/json", 6),
new PolicyJumpTableEdge("application/xml", 7),
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- var jumpTable = policy.BuildJumpTable(-1, edges);
+ var jumpTable = policy.BuildJumpTable(-1, edges);
- var httpContext = new DefaultHttpContext();
- httpContext.Request.ContentType = contentType;
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.ContentType = contentType;
- // Act
- var actual = jumpTable.GetDestination(httpContext);
+ // Act
+ var actual = jumpTable.GetDestination(httpContext);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public async Task ApplyAsync_EndpointWithoutMetadata_MatchWithoutContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointWithoutMetadata_MatchWithoutContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", null),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext();
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext();
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointAllowsAnyContentType_MatchWithoutContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointAllowsAnyContentType_MatchWithoutContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>())),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext();
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext();
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointHasWildcardContentType_MatchWithoutContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointHasWildcardContentType_MatchWithoutContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "*/*" })),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext();
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext();
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointWithoutMetadata_MatchWithAnyContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointWithoutMetadata_MatchWithAnyContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", null),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "text/plain",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointAllowsAnyContentType_MatchWithAnyContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointAllowsAnyContentType_MatchWithAnyContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(Array.Empty<string>())),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "text/plain",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointHasWildcardContentType_MatchWithAnyContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointHasWildcardContentType_MatchWithAnyContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "*/*" })),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "text/plain",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointHasSubTypeWildcard_MatchWithValidContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointHasSubTypeWildcard_MatchWithValidContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "application/*+json", })),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "application/project+json",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointHasMultipleContentType_MatchWithValidContentType()
+ [Fact]
+ public async Task ApplyAsync_EndpointHasMultipleContentType_MatchWithValidContentType()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "text/xml", "application/xml", })),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "application/xml",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.True(candidates.IsValidCandidate(0));
- }
+ // Assert
+ Assert.True(candidates.IsValidCandidate(0));
+ }
- [Fact]
- public async Task ApplyAsync_EndpointDoesNotMatch_Returns415()
+ [Fact]
+ public async Task ApplyAsync_EndpointDoesNotMatch_Returns415()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "text/xml", "application/xml", })),
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "application/json",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.False(candidates.IsValidCandidate(0));
- Assert.NotNull(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.False(candidates.IsValidCandidate(0));
+ Assert.NotNull(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task ApplyAsync_EndpointDoesNotMatch_DoesNotReturns415WithContentTypeObliviousEndpoint()
+ [Fact]
+ public async Task ApplyAsync_EndpointDoesNotMatch_DoesNotReturns415WithContentTypeObliviousEndpoint()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "text/xml", "application/xml", })),
CreateEndpoint("/", null)
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "application/json",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.False(candidates.IsValidCandidate(0));
- Assert.Null(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.False(candidates.IsValidCandidate(0));
+ Assert.Null(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task ApplyAsync_EndpointDoesNotMatch_DoesNotReturns415WithContentTypeWildcardEndpoint()
+ [Fact]
+ public async Task ApplyAsync_EndpointDoesNotMatch_DoesNotReturns415WithContentTypeWildcardEndpoint()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new AcceptsMetadata(new string[] { "text/xml", "application/xml", })),
CreateEndpoint("/", new AcceptsMetadata(new string[] { "*/*", }))
};
- var candidates = CreateCandidateSet(endpoints);
- var httpContext = new DefaultHttpContext()
- {
- Request =
+ var candidates = CreateCandidateSet(endpoints);
+ var httpContext = new DefaultHttpContext()
+ {
+ Request =
{
ContentType = "application/json",
},
- };
+ };
- var policy = CreatePolicy();
+ var policy = CreatePolicy();
- // Act
- await policy.ApplyAsync(httpContext, candidates);
+ // Act
+ await policy.ApplyAsync(httpContext, candidates);
- // Assert
- Assert.False(candidates.IsValidCandidate(0));
- Assert.True(candidates.IsValidCandidate(1));
- Assert.Null(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.False(candidates.IsValidCandidate(0));
+ Assert.True(candidates.IsValidCandidate(1));
+ Assert.Null(httpContext.GetEndpoint());
+ }
- private static RouteEndpoint CreateEndpoint(string template, AcceptsMetadata consumesMetadata, params object[] more)
+ private static RouteEndpoint CreateEndpoint(string template, AcceptsMetadata consumesMetadata, params object[] more)
+ {
+ var metadata = new List<object>();
+ if (consumesMetadata != null)
{
- var metadata = new List<object>();
- if (consumesMetadata != null)
- {
- metadata.Add(consumesMetadata);
- }
-
- if (more != null)
- {
- metadata.AddRange(more);
- }
-
- return new RouteEndpoint(
- (context) => Task.CompletedTask,
- RoutePatternFactory.Parse(template),
- 0,
- new EndpointMetadataCollection(metadata),
- $"test: {template} - {string.Join(", ", consumesMetadata?.ContentTypes ?? Array.Empty<string>())}");
+ metadata.Add(consumesMetadata);
}
- private static CandidateSet CreateCandidateSet(Endpoint[] endpoints)
+ if (more != null)
{
- return new CandidateSet(endpoints, new RouteValueDictionary[endpoints.Length], new int[endpoints.Length]);
+ metadata.AddRange(more);
}
- private static AcceptsMatcherPolicy CreatePolicy()
- {
- return new AcceptsMatcherPolicy();
- }
+ return new RouteEndpoint(
+ (context) => Task.CompletedTask,
+ RoutePatternFactory.Parse(template),
+ 0,
+ new EndpointMetadataCollection(metadata),
+ $"test: {template} - {string.Join(", ", consumesMetadata?.ContentTypes ?? Array.Empty<string>())}");
+ }
- private class DynamicEndpointMetadata : IDynamicEndpointMetadata
- {
- public bool IsDynamic => true;
- }
+ private static CandidateSet CreateCandidateSet(Endpoint[] endpoints)
+ {
+ return new CandidateSet(endpoints, new RouteValueDictionary[endpoints.Length], new int[endpoints.Length]);
+ }
+
+ private static AcceptsMatcherPolicy CreatePolicy()
+ {
+ return new AcceptsMatcherPolicy();
+ }
+
+ private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ {
+ public bool IsDynamic => true;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/AsciiTest.cs b/src/Http/Routing/test/UnitTests/Matching/AsciiTest.cs
index 9ca3ba50ee..fcd2ee5756 100644
--- a/src/Http/Routing/test/UnitTests/Matching/AsciiTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/AsciiTest.cs
@@ -4,111 +4,110 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Note that while we don't intend for this code to be used with non-ASCII test,
+// we still call into these methods with some non-ASCII characters so that
+// we are sure of how it behaves.
+public class AsciiTest
{
- // Note that while we don't intend for this code to be used with non-ASCII test,
- // we still call into these methods with some non-ASCII characters so that
- // we are sure of how it behaves.
- public class AsciiTest
+ [Fact]
+ public void IsAscii_ReturnsTrueForAscii()
+ {
+ // Arrange
+ var text = "abcd\u007F";
+
+ // Act
+ var result = Ascii.IsAscii(text);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsAscii_ReturnsFalseForNonAscii()
+ {
+ // Arrange
+ var text = "abcd\u0080";
+
+ // Act
+ var result = Ascii.IsAscii(text);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Theory]
+
+ // Identity
+ [InlineData('c', 'c')]
+ [InlineData('C', 'C')]
+ [InlineData('#', '#')]
+ [InlineData('\u0080', '\u0080')]
+
+ // Case-insensitive
+ [InlineData('c', 'C')]
+ public void AsciiIgnoreCaseEquals_ReturnsTrue(char x, char y)
+ {
+ // Arrange
+
+ // Act
+ var result = Ascii.AsciiIgnoreCaseEquals(x, y);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Theory]
+
+ // Different letter
+ [InlineData('c', 'd')]
+ [InlineData('C', 'D')]
+
+ // Non-letter + casing difference - 'a' and 'A' are 32 bits apart and so are ' ' and '@'
+ [InlineData(' ', '@')]
+ [InlineData('\u0080', '\u0080' + 32)] // Outside of ASCII range
+ public void AsciiIgnoreCaseEquals_ReturnsFalse(char x, char y)
+ {
+ // Arrange
+
+ // Act
+ var result = Ascii.AsciiIgnoreCaseEquals(x, y);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Theory]
+ [InlineData("", "", 0)]
+ [InlineData("abCD", "abcF", 3)]
+ [InlineData("ab#\u0080-$%", "Ab#\u0080-$%", 7)]
+ public void UnsafeAsciiIgnoreCaseEquals_ReturnsTrue(string x, string y, int length)
+ {
+ // Arrange
+ var spanX = x.AsSpan();
+ var spanY = y.AsSpan();
+
+ // Act
+ var result = Ascii.AsciiIgnoreCaseEquals(spanX, spanY, length);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Theory]
+ [InlineData("abcD", "abCE", 4)]
+ [InlineData("ab#\u0080-$%", "Ab#\u0081-$%", 7)]
+ public void UnsafeAsciiIgnoreCaseEquals_ReturnsFalse(string x, string y, int length)
{
- [Fact]
- public void IsAscii_ReturnsTrueForAscii()
- {
- // Arrange
- var text = "abcd\u007F";
-
- // Act
- var result = Ascii.IsAscii(text);
-
- // Assert
- Assert.True(result);
- }
-
- [Fact]
- public void IsAscii_ReturnsFalseForNonAscii()
- {
- // Arrange
- var text = "abcd\u0080";
-
- // Act
- var result = Ascii.IsAscii(text);
-
- // Assert
- Assert.False(result);
- }
-
- [Theory]
-
- // Identity
- [InlineData('c', 'c')]
- [InlineData('C', 'C')]
- [InlineData('#', '#')]
- [InlineData('\u0080', '\u0080')]
-
- // Case-insensitive
- [InlineData('c', 'C')]
- public void AsciiIgnoreCaseEquals_ReturnsTrue(char x, char y)
- {
- // Arrange
-
- // Act
- var result = Ascii.AsciiIgnoreCaseEquals(x, y);
-
- // Assert
- Assert.True(result);
- }
-
- [Theory]
-
- // Different letter
- [InlineData('c', 'd')]
- [InlineData('C', 'D')]
-
- // Non-letter + casing difference - 'a' and 'A' are 32 bits apart and so are ' ' and '@'
- [InlineData(' ', '@')]
- [InlineData('\u0080', '\u0080' + 32)] // Outside of ASCII range
- public void AsciiIgnoreCaseEquals_ReturnsFalse(char x, char y)
- {
- // Arrange
-
- // Act
- var result = Ascii.AsciiIgnoreCaseEquals(x, y);
-
- // Assert
- Assert.False(result);
- }
-
- [Theory]
- [InlineData("", "", 0)]
- [InlineData("abCD", "abcF", 3)]
- [InlineData("ab#\u0080-$%", "Ab#\u0080-$%", 7)]
- public void UnsafeAsciiIgnoreCaseEquals_ReturnsTrue(string x, string y, int length)
- {
- // Arrange
- var spanX = x.AsSpan();
- var spanY = y.AsSpan();
-
- // Act
- var result = Ascii.AsciiIgnoreCaseEquals(spanX, spanY, length);
-
- // Assert
- Assert.True(result);
- }
-
- [Theory]
- [InlineData("abcD", "abCE", 4)]
- [InlineData("ab#\u0080-$%", "Ab#\u0081-$%", 7)]
- public void UnsafeAsciiIgnoreCaseEquals_ReturnsFalse(string x, string y, int length)
- {
- // Arrange
- var spanX = x.AsSpan();
- var spanY = y.AsSpan();
-
- // Act
- var result = Ascii.AsciiIgnoreCaseEquals(spanX, spanY, length);
-
- // Assert
- Assert.False(result);
- }
+ // Arrange
+ var spanX = x.AsSpan();
+ var spanY = y.AsSpan();
+
+ // Act
+ var result = Ascii.AsciiIgnoreCaseEquals(spanX, spanY, length);
+
+ // Assert
+ Assert.False(result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs
index 5473876c2a..710a3be639 100644
--- a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs
@@ -6,126 +6,125 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// A test-only matcher implementation - used as a baseline for more compilated
+// perf tests. The idea with this matcher is that we can cheat on the requirements
+// to establish a lower bound for perf comparisons.
+internal class BarebonesMatcher : Matcher
{
- // A test-only matcher implementation - used as a baseline for more compilated
- // perf tests. The idea with this matcher is that we can cheat on the requirements
- // to establish a lower bound for perf comparisons.
- internal class BarebonesMatcher : Matcher
+ public readonly InnerMatcher[] Matchers;
+
+ public BarebonesMatcher(InnerMatcher[] matchers)
{
- public readonly InnerMatcher[] Matchers;
+ Matchers = matchers;
+ }
- public BarebonesMatcher(InnerMatcher[] matchers)
+ public override Task MatchAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
{
- Matchers = matchers;
+ throw new ArgumentNullException(nameof(httpContext));
}
- public override Task MatchAsync(HttpContext httpContext)
+ var path = httpContext.Request.Path.Value;
+ for (var i = 0; i < Matchers.Length; i++)
{
- if (httpContext == null)
+ if (Matchers[i].TryMatch(path))
{
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var path = httpContext.Request.Path.Value;
- for (var i = 0; i < Matchers.Length; i++)
- {
- if (Matchers[i].TryMatch(path))
- {
- httpContext.SetEndpoint(Matchers[i].Endpoint);
- httpContext.Request.RouteValues = new RouteValueDictionary();
- }
+ httpContext.SetEndpoint(Matchers[i].Endpoint);
+ httpContext.Request.RouteValues = new RouteValueDictionary();
}
-
- return Task.CompletedTask;
}
- public sealed class InnerMatcher : Matcher
- {
- public readonly RouteEndpoint Endpoint;
+ return Task.CompletedTask;
+ }
- private readonly string[] _segments;
- private readonly Candidate[] _candidates;
+ public sealed class InnerMatcher : Matcher
+ {
+ public readonly RouteEndpoint Endpoint;
- public InnerMatcher(string[] segments, RouteEndpoint endpoint)
- {
- _segments = segments;
- Endpoint = endpoint;
+ private readonly string[] _segments;
+ private readonly Candidate[] _candidates;
- _candidates = new Candidate[] { new Candidate(endpoint), };
- }
+ public InnerMatcher(string[] segments, RouteEndpoint endpoint)
+ {
+ _segments = segments;
+ Endpoint = endpoint;
- public bool TryMatch(string path)
- {
- var segment = 0;
+ _candidates = new Candidate[] { new Candidate(endpoint), };
+ }
- var start = 1; // PathString always has a leading slash
- var end = 0;
- while ((end = path.IndexOf('/', start)) >= 0)
- {
- var comparand = _segments.Length > segment ? _segments[segment] : null;
- if ((comparand == null && end - start == 0) ||
- (comparand != null &&
- (comparand.Length != end - start ||
- string.Compare(
- path,
- start,
- comparand,
- 0,
- comparand.Length,
- StringComparison.OrdinalIgnoreCase) != 0)))
- {
- return false;
- }
-
- start = end + 1;
- segment++;
- }
+ public bool TryMatch(string path)
+ {
+ var segment = 0;
- // residue
- var length = path.Length - start;
- if (length > 0)
- {
- var comparand = _segments.Length > segment ? _segments[segment] : null;
- if (comparand != null &&
- (comparand.Length != length ||
+ var start = 1; // PathString always has a leading slash
+ var end = 0;
+ while ((end = path.IndexOf('/', start)) >= 0)
+ {
+ var comparand = _segments.Length > segment ? _segments[segment] : null;
+ if ((comparand == null && end - start == 0) ||
+ (comparand != null &&
+ (comparand.Length != end - start ||
string.Compare(
path,
start,
comparand,
0,
comparand.Length,
- StringComparison.OrdinalIgnoreCase) != 0))
- {
- return false;
- }
-
- segment++;
+ StringComparison.OrdinalIgnoreCase) != 0)))
+ {
+ return false;
}
- return segment == _segments.Length;
+ start = end + 1;
+ segment++;
}
- internal Candidate[] FindCandidateSet(string path, ReadOnlySpan<PathSegment> segments)
+ // residue
+ var length = path.Length - start;
+ if (length > 0)
{
- if (TryMatch(path))
+ var comparand = _segments.Length > segment ? _segments[segment] : null;
+ if (comparand != null &&
+ (comparand.Length != length ||
+ string.Compare(
+ path,
+ start,
+ comparand,
+ 0,
+ comparand.Length,
+ StringComparison.OrdinalIgnoreCase) != 0))
{
- return _candidates;
+ return false;
}
- return Array.Empty<Candidate>();
+ segment++;
}
- public override Task MatchAsync(HttpContext httpContext)
+ return segment == _segments.Length;
+ }
+
+ internal Candidate[] FindCandidateSet(string path, ReadOnlySpan<PathSegment> segments)
+ {
+ if (TryMatch(path))
{
- if (TryMatch(httpContext.Request.Path.Value))
- {
- httpContext.SetEndpoint(Endpoint);
- httpContext.Request.RouteValues = new RouteValueDictionary();
- }
+ return _candidates;
+ }
+
+ return Array.Empty<Candidate>();
+ }
- return Task.CompletedTask;
+ public override Task MatchAsync(HttpContext httpContext)
+ {
+ if (TryMatch(httpContext.Request.Path.Value))
+ {
+ httpContext.SetEndpoint(Endpoint);
+ httpContext.Request.RouteValues = new RouteValueDictionary();
}
+
+ return Task.CompletedTask;
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherBuilder.cs
index cd295f21a2..0c005ffff2 100644
--- a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherBuilder.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherBuilder.cs
@@ -7,30 +7,29 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
using static Microsoft.AspNetCore.Routing.Matching.BarebonesMatcher;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class BarebonesMatcherBuilder : MatcherBuilder
{
- internal class BarebonesMatcherBuilder : MatcherBuilder
+ private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
+
+ public override void AddEndpoint(RouteEndpoint endpoint)
{
- private readonly List<RouteEndpoint> _endpoints = new List<RouteEndpoint>();
+ _endpoints.Add(endpoint);
+ }
- public override void AddEndpoint(RouteEndpoint endpoint)
+ public override Matcher Build()
+ {
+ var matchers = new InnerMatcher[_endpoints.Count];
+ for (var i = 0; i < _endpoints.Count; i++)
{
- _endpoints.Add(endpoint);
+ var endpoint = _endpoints[i];
+ var pathSegments = endpoint.RoutePattern.PathSegments
+ .Select(s => s.IsSimple && s.Parts[0] is RoutePatternLiteralPart literalPart ? literalPart.Content : null)
+ .ToArray();
+ matchers[i] = new InnerMatcher(pathSegments, _endpoints[i]);
}
- public override Matcher Build()
- {
- var matchers = new InnerMatcher[_endpoints.Count];
- for (var i = 0; i < _endpoints.Count; i++)
- {
- var endpoint = _endpoints[i];
- var pathSegments = endpoint.RoutePattern.PathSegments
- .Select(s => s.IsSimple && s.Parts[0] is RoutePatternLiteralPart literalPart ? literalPart.Content : null)
- .ToArray();
- matchers[i] = new InnerMatcher(pathSegments, _endpoints[i]);
- }
-
- return new BarebonesMatcher(matchers);
- }
+ return new BarebonesMatcher(matchers);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherConformanceTest.cs
index f3cd6b4be6..964a3d221e 100644
--- a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherConformanceTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcherConformanceTest.cs
@@ -5,55 +5,54 @@ using System;
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class BarebonesMatcherConformanceTest : MatcherConformanceTest
{
- public class BarebonesMatcherConformanceTest : MatcherConformanceTest
+ // Route values not supported
+ [Fact]
+ public override Task Match_SingleParameter()
{
- // Route values not supported
- [Fact]
- public override Task Match_SingleParameter()
- {
- return Task.CompletedTask;
- }
+ return Task.CompletedTask;
+ }
- // Route values not supported
- [Fact]
- public override Task Match_SingleParameter_TrailingSlash()
- {
- return Task.CompletedTask;
- }
+ // Route values not supported
+ [Fact]
+ public override Task Match_SingleParameter_TrailingSlash()
+ {
+ return Task.CompletedTask;
+ }
- // Route values not supported
- [Fact]
- public override Task Match_SingleParameter_WeirdNames()
- {
- return Task.CompletedTask;
- }
+ // Route values not supported
+ [Fact]
+ public override Task Match_SingleParameter_WeirdNames()
+ {
+ return Task.CompletedTask;
+ }
- // Route values not supported
- [Theory]
- [InlineData(null, null, null, null)]
- public override Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
- {
- GC.KeepAlive(new object[] { template, path, keys, values });
- return Task.CompletedTask;
- }
+ // Route values not supported
+ [Theory]
+ [InlineData(null, null, null, null)]
+ public override Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
+ {
+ GC.KeepAlive(new object[] { template, path, keys, values });
+ return Task.CompletedTask;
+ }
- // Route constraints not supported
- [Fact]
- public override Task Match_Constraint()
- {
- return Task.CompletedTask;
- }
+ // Route constraints not supported
+ [Fact]
+ public override Task Match_Constraint()
+ {
+ return Task.CompletedTask;
+ }
- internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ {
+ var builder = new BarebonesMatcherBuilder();
+ for (var i = 0; i < endpoints.Length; i++)
{
- var builder = new BarebonesMatcherBuilder();
- for (var i = 0; i < endpoints.Length; i++)
- {
- builder.AddEndpoint(endpoints[i]);
- }
- return builder.Build();
+ builder.AddEndpoint(endpoints[i]);
}
+ return builder.Build();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/CandidateSetTest.cs b/src/Http/Routing/test/UnitTests/Matching/CandidateSetTest.cs
index fef7ab0f8e..923180ee8b 100644
--- a/src/Http/Routing/test/UnitTests/Matching/CandidateSetTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/CandidateSetTest.cs
@@ -10,397 +10,396 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class CandidateSetTest
{
- public class CandidateSetTest
+ [Fact]
+ public void Create_CreatesCandidateSet()
{
- [Fact]
- public void Create_CreatesCandidateSet()
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}");
- }
+ endpoints[i] = CreateEndpoint($"/{i}");
+ }
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
- // Act
- var candidateSet = new CandidateSet(candidates);
+ // Act
+ var candidateSet = new CandidateSet(candidates);
- // Assert
- for (var i = 0; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
- Assert.True(candidateSet.IsValidCandidate(i));
- Assert.Same(endpoints[i], state.Endpoint);
- Assert.Equal(candidates[i].Score, state.Score);
- Assert.Null(state.Values);
-
- candidateSet.SetValidity(i, false);
- Assert.False(candidateSet.IsValidCandidate(i));
- }
+ // Assert
+ for (var i = 0; i < candidateSet.Count; i++)
+ {
+ ref var state = ref candidateSet[i];
+ Assert.True(candidateSet.IsValidCandidate(i));
+ Assert.Same(endpoints[i], state.Endpoint);
+ Assert.Equal(candidates[i].Score, state.Score);
+ Assert.Null(state.Values);
+
+ candidateSet.SetValidity(i, false);
+ Assert.False(candidateSet.IsValidCandidate(i));
}
+ }
- [Fact]
- public void ReplaceEndpoint_WithEndpoint()
+ [Fact]
+ public void ReplaceEndpoint_WithEndpoint()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}");
- }
-
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
-
- var candidateSet = new CandidateSet(candidates);
-
- for (var i = 0; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
-
- var endpoint = CreateEndpoint($"/test{i}");
- var values = new RouteValueDictionary();
-
- // Act
- candidateSet.ReplaceEndpoint(i, endpoint, values);
-
- // Assert
- Assert.Same(endpoint, state.Endpoint);
- Assert.Same(values, state.Values);
- Assert.True(candidateSet.IsValidCandidate(i));
- }
+ endpoints[i] = CreateEndpoint($"/{i}");
}
- [Fact]
- public void ReplaceEndpoint_WithEndpoint_Null()
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
+
+ var candidateSet = new CandidateSet(candidates);
+
+ for (var i = 0; i < candidateSet.Count; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}");
- }
-
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
-
- var candidateSet = new CandidateSet(candidates);
-
- for (var i = 0; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
-
- // Act
- candidateSet.ReplaceEndpoint(i, (Endpoint)null, null);
-
- // Assert
- Assert.Null(state.Endpoint);
- Assert.Null(state.Values);
- Assert.False(candidateSet.IsValidCandidate(i));
- }
+ ref var state = ref candidateSet[i];
+
+ var endpoint = CreateEndpoint($"/test{i}");
+ var values = new RouteValueDictionary();
+
+ // Act
+ candidateSet.ReplaceEndpoint(i, endpoint, values);
+
+ // Assert
+ Assert.Same(endpoint, state.Endpoint);
+ Assert.Same(values, state.Values);
+ Assert.True(candidateSet.IsValidCandidate(i));
}
+ }
- [Fact]
- public void ExpandEndpoint_EmptyList()
+ [Fact]
+ public void ReplaceEndpoint_WithEndpoint_Null()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}", order: i);
- }
+ endpoints[i] = CreateEndpoint($"/{i}");
+ }
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
- var candidateSet = new CandidateSet(candidates);
+ var candidateSet = new CandidateSet(candidates);
- var services = new Mock<IServiceProvider>();
- services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
- var comparer = new EndpointMetadataComparer(services.Object);
+ for (var i = 0; i < candidateSet.Count; i++)
+ {
+ ref var state = ref candidateSet[i];
// Act
- candidateSet.ExpandEndpoint(0, Array.Empty<Endpoint>(), comparer);
+ candidateSet.ReplaceEndpoint(i, (Endpoint)null, null);
// Assert
+ Assert.Null(state.Endpoint);
+ Assert.Null(state.Values);
+ Assert.False(candidateSet.IsValidCandidate(i));
+ }
+ }
+
+ [Fact]
+ public void ExpandEndpoint_EmptyList()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
+ {
+ endpoints[i] = CreateEndpoint($"/{i}", order: i);
+ }
+
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
+
+ var candidateSet = new CandidateSet(candidates);
+
+ var services = new Mock<IServiceProvider>();
+ services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
+ var comparer = new EndpointMetadataComparer(services.Object);
+
+ // Act
+ candidateSet.ExpandEndpoint(0, Array.Empty<Endpoint>(), comparer);
- Assert.Null(candidateSet[0].Endpoint);
- Assert.False(candidateSet.IsValidCandidate(0));
+ // Assert
- for (var i = 1; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
+ Assert.Null(candidateSet[0].Endpoint);
+ Assert.False(candidateSet.IsValidCandidate(0));
- Assert.Same(endpoints[i], state.Endpoint);
- }
+ for (var i = 1; i < candidateSet.Count; i++)
+ {
+ ref var state = ref candidateSet[i];
+
+ Assert.Same(endpoints[i], state.Endpoint);
}
+ }
- [Fact]
- public void ExpandEndpoint_Beginning()
+ [Fact]
+ public void ExpandEndpoint_Beginning()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}", order: i);
- }
+ endpoints[i] = CreateEndpoint($"/{i}", order: i);
+ }
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
- var candidateSet = new CandidateSet(candidates);
+ var candidateSet = new CandidateSet(candidates);
- var replacements = new RouteEndpoint[3]
- {
+ var replacements = new RouteEndpoint[3]
+ {
CreateEndpoint($"new /A", metadata: new object[]{ new TestMetadata(), }),
CreateEndpoint($"new /B", metadata: new object[]{ }),
CreateEndpoint($"new /C", metadata: new object[]{ new TestMetadata(), }),
- };
+ };
- var services = new Mock<IServiceProvider>();
- services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
- var comparer = new EndpointMetadataComparer(services.Object);
+ var services = new Mock<IServiceProvider>();
+ services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
+ var comparer = new EndpointMetadataComparer(services.Object);
- candidateSet.SetValidity(0, false); // Has no effect. We always count new stuff as valid by default.
+ candidateSet.SetValidity(0, false); // Has no effect. We always count new stuff as valid by default.
- // Act
- candidateSet.ExpandEndpoint(0, replacements, comparer);
+ // Act
+ candidateSet.ExpandEndpoint(0, replacements, comparer);
- // Assert
- Assert.Equal(12, candidateSet.Count);
-
- Assert.Same(replacements[0], candidateSet[0].Endpoint);
- Assert.Equal(0, candidateSet[0].Score);
- Assert.Same(replacements[2], candidateSet[1].Endpoint);
- Assert.Equal(0, candidateSet[1].Score);
- Assert.Same(replacements[1], candidateSet[2].Endpoint);
- Assert.Equal(1, candidateSet[2].Score);
-
- for (var i = 3; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
- Assert.Same(endpoints[i - 2], state.Endpoint);
- Assert.Equal(i - 1, candidateSet[i].Score);
- }
+ // Assert
+ Assert.Equal(12, candidateSet.Count);
+
+ Assert.Same(replacements[0], candidateSet[0].Endpoint);
+ Assert.Equal(0, candidateSet[0].Score);
+ Assert.Same(replacements[2], candidateSet[1].Endpoint);
+ Assert.Equal(0, candidateSet[1].Score);
+ Assert.Same(replacements[1], candidateSet[2].Endpoint);
+ Assert.Equal(1, candidateSet[2].Score);
+
+ for (var i = 3; i < candidateSet.Count; i++)
+ {
+ ref var state = ref candidateSet[i];
+ Assert.Same(endpoints[i - 2], state.Endpoint);
+ Assert.Equal(i - 1, candidateSet[i].Score);
}
+ }
- [Fact]
- public void ExpandEndpoint_Middle()
+ [Fact]
+ public void ExpandEndpoint_Middle()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}", order: i);
- }
+ endpoints[i] = CreateEndpoint($"/{i}", order: i);
+ }
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
- var candidateSet = new CandidateSet(candidates);
+ var candidateSet = new CandidateSet(candidates);
- var replacements = new RouteEndpoint[3]
- {
+ var replacements = new RouteEndpoint[3]
+ {
CreateEndpoint($"new /A", metadata: new object[]{ new TestMetadata(), }),
CreateEndpoint($"new /B", metadata: new object[]{ }),
CreateEndpoint($"new /C", metadata: new object[]{ new TestMetadata(), }),
- };
+ };
- var services = new Mock<IServiceProvider>();
- services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
- var comparer = new EndpointMetadataComparer(services.Object);
+ var services = new Mock<IServiceProvider>();
+ services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
+ var comparer = new EndpointMetadataComparer(services.Object);
- candidateSet.SetValidity(5, false); // Has no effect. We always count new stuff as valid by default.
+ candidateSet.SetValidity(5, false); // Has no effect. We always count new stuff as valid by default.
- // Act
- candidateSet.ExpandEndpoint(5, replacements, comparer);
+ // Act
+ candidateSet.ExpandEndpoint(5, replacements, comparer);
- // Assert
- Assert.Equal(12, candidateSet.Count);
-
- for (var i = 0; i < 5; i++)
- {
- ref var state = ref candidateSet[i];
- Assert.Same(endpoints[i], state.Endpoint);
- Assert.Equal(i, candidateSet[i].Score);
- }
-
- Assert.Same(replacements[0], candidateSet[5].Endpoint);
- Assert.Equal(5, candidateSet[5].Score);
- Assert.Same(replacements[2], candidateSet[6].Endpoint);
- Assert.Equal(5, candidateSet[6].Score);
- Assert.Same(replacements[1], candidateSet[7].Endpoint);
- Assert.Equal(6, candidateSet[7].Score);
-
- for (var i = 8; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
- Assert.Same(endpoints[i - 2], state.Endpoint);
- Assert.Equal(i - 1, candidateSet[i].Score);
- }
+ // Assert
+ Assert.Equal(12, candidateSet.Count);
+
+ for (var i = 0; i < 5; i++)
+ {
+ ref var state = ref candidateSet[i];
+ Assert.Same(endpoints[i], state.Endpoint);
+ Assert.Equal(i, candidateSet[i].Score);
}
- [Fact]
- public void ExpandEndpoint_End()
+ Assert.Same(replacements[0], candidateSet[5].Endpoint);
+ Assert.Equal(5, candidateSet[5].Score);
+ Assert.Same(replacements[2], candidateSet[6].Endpoint);
+ Assert.Equal(5, candidateSet[6].Score);
+ Assert.Same(replacements[1], candidateSet[7].Endpoint);
+ Assert.Equal(6, candidateSet[7].Score);
+
+ for (var i = 8; i < candidateSet.Count; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}", order: i);
- }
+ ref var state = ref candidateSet[i];
+ Assert.Same(endpoints[i - 2], state.Endpoint);
+ Assert.Equal(i - 1, candidateSet[i].Score);
+ }
+ }
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
+ [Fact]
+ public void ExpandEndpoint_End()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
+ {
+ endpoints[i] = CreateEndpoint($"/{i}", order: i);
+ }
- var candidateSet = new CandidateSet(candidates);
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
- var replacements = new RouteEndpoint[3]
- {
+ var candidateSet = new CandidateSet(candidates);
+
+ var replacements = new RouteEndpoint[3]
+ {
CreateEndpoint($"new /A", metadata: new object[]{ new TestMetadata(), }),
CreateEndpoint($"new /B", metadata: new object[]{ }),
CreateEndpoint($"new /C", metadata: new object[]{ new TestMetadata(), }),
- };
+ };
- var services = new Mock<IServiceProvider>();
- services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
- var comparer = new EndpointMetadataComparer(services.Object);
+ var services = new Mock<IServiceProvider>();
+ services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
+ var comparer = new EndpointMetadataComparer(services.Object);
- candidateSet.SetValidity(9, false); // Has no effect. We always count new stuff as valid by default.
+ candidateSet.SetValidity(9, false); // Has no effect. We always count new stuff as valid by default.
- // Act
- candidateSet.ExpandEndpoint(9, replacements, comparer);
+ // Act
+ candidateSet.ExpandEndpoint(9, replacements, comparer);
- // Assert
- Assert.Equal(12, candidateSet.Count);
-
- for (var i = 0; i < 9; i++)
- {
- ref var state = ref candidateSet[i];
- Assert.Same(endpoints[i], state.Endpoint);
- Assert.Equal(i, candidateSet[i].Score);
- }
-
- Assert.Same(replacements[0], candidateSet[9].Endpoint);
- Assert.Equal(9, candidateSet[9].Score);
- Assert.Same(replacements[2], candidateSet[10].Endpoint);
- Assert.Equal(9, candidateSet[10].Score);
- Assert.Same(replacements[1], candidateSet[11].Endpoint);
- Assert.Equal(10, candidateSet[11].Score);
+ // Assert
+ Assert.Equal(12, candidateSet.Count);
+
+ for (var i = 0; i < 9; i++)
+ {
+ ref var state = ref candidateSet[i];
+ Assert.Same(endpoints[i], state.Endpoint);
+ Assert.Equal(i, candidateSet[i].Score);
}
- [Fact]
- public void ExpandEndpoint_ThrowsForDuplicateScore()
+ Assert.Same(replacements[0], candidateSet[9].Endpoint);
+ Assert.Equal(9, candidateSet[9].Score);
+ Assert.Same(replacements[2], candidateSet[10].Endpoint);
+ Assert.Equal(9, candidateSet[10].Score);
+ Assert.Same(replacements[1], candidateSet[11].Endpoint);
+ Assert.Equal(10, candidateSet[11].Score);
+ }
+
+ [Fact]
+ public void ExpandEndpoint_ThrowsForDuplicateScore()
+ {
+ // Arrange
+ var count = 2;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 2;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}", order: 0);
- }
+ endpoints[i] = CreateEndpoint($"/{i}", order: 0);
+ }
- var builder = CreateDfaMatcherBuilder();
- var candidates = builder.CreateCandidates(endpoints);
+ var builder = CreateDfaMatcherBuilder();
+ var candidates = builder.CreateCandidates(endpoints);
- var candidateSet = new CandidateSet(candidates);
+ var candidateSet = new CandidateSet(candidates);
- var services = new Mock<IServiceProvider>();
- services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
- var comparer = new EndpointMetadataComparer(services.Object);
+ var services = new Mock<IServiceProvider>();
+ services.Setup(s => s.GetService(typeof(IEnumerable<MatcherPolicy>))).Returns(new[] { new TestMetadataMatcherPolicy(), });
+ var comparer = new EndpointMetadataComparer(services.Object);
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => candidateSet.ExpandEndpoint(0, Array.Empty<Endpoint>(), comparer));
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => candidateSet.ExpandEndpoint(0, Array.Empty<Endpoint>(), comparer));
- // Assert
- Assert.Equal(@"Using ExpandEndpoint requires that the replaced endpoint have a unique priority. The following endpoints were found with the same priority:" +
- Environment.NewLine +
- "test: /0" +
- Environment.NewLine +
- "test: /1"
- .TrimStart(), ex.Message);
+ // Assert
+ Assert.Equal(@"Using ExpandEndpoint requires that the replaced endpoint have a unique priority. The following endpoints were found with the same priority:" +
+ Environment.NewLine +
+ "test: /0" +
+ Environment.NewLine +
+ "test: /1"
+ .TrimStart(), ex.Message);
+ }
+
+ [Fact]
+ public void Create_CreatesCandidateSet_TestConstructor()
+ {
+ // Arrange
+ var count = 10;
+ var endpoints = new RouteEndpoint[count];
+ for (var i = 0; i < endpoints.Length; i++)
+ {
+ endpoints[i] = CreateEndpoint($"/{i}");
}
- [Fact]
- public void Create_CreatesCandidateSet_TestConstructor()
+ var values = new RouteValueDictionary[count];
+ for (var i = 0; i < endpoints.Length; i++)
{
- // Arrange
- var count = 10;
- var endpoints = new RouteEndpoint[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- endpoints[i] = CreateEndpoint($"/{i}");
- }
-
- var values = new RouteValueDictionary[count];
- for (var i = 0; i < endpoints.Length; i++)
- {
- values[i] = new RouteValueDictionary()
+ values[i] = new RouteValueDictionary()
{
{ "i", i }
};
- }
-
- // Act
- var candidateSet = new CandidateSet(endpoints, values, Enumerable.Range(0, count).ToArray());
-
- // Assert
- for (var i = 0; i < candidateSet.Count; i++)
- {
- ref var state = ref candidateSet[i];
- Assert.True(candidateSet.IsValidCandidate(i));
- Assert.Same(endpoints[i], state.Endpoint);
- Assert.Equal(i, state.Score);
- Assert.NotNull(state.Values);
- Assert.Equal(i, state.Values["i"]);
-
- candidateSet.SetValidity(i, false);
- Assert.False(candidateSet.IsValidCandidate(i));
- }
}
- private RouteEndpoint CreateEndpoint(string template, int order = 0, params object[] metadata)
- {
- var builder = new RouteEndpointBuilder(TestConstants.EmptyRequestDelegate, RoutePatternFactory.Parse(template), order);
- for (var i = 0; i < metadata.Length; i++)
- {
- builder.Metadata.Add(metadata[i]);
- }
-
- builder.DisplayName = "test: " + template;
- return (RouteEndpoint)builder.Build();
- }
+ // Act
+ var candidateSet = new CandidateSet(endpoints, values, Enumerable.Range(0, count).ToArray());
- private static DfaMatcherBuilder CreateDfaMatcherBuilder(params MatcherPolicy[] policies)
+ // Assert
+ for (var i = 0; i < candidateSet.Count; i++)
{
- var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
- return new DfaMatcherBuilder(
- NullLoggerFactory.Instance,
- Mock.Of<ParameterPolicyFactory>(),
- Mock.Of<EndpointSelector>(),
- policies);
+ ref var state = ref candidateSet[i];
+ Assert.True(candidateSet.IsValidCandidate(i));
+ Assert.Same(endpoints[i], state.Endpoint);
+ Assert.Equal(i, state.Score);
+ Assert.NotNull(state.Values);
+ Assert.Equal(i, state.Values["i"]);
+
+ candidateSet.SetValidity(i, false);
+ Assert.False(candidateSet.IsValidCandidate(i));
}
+ }
- private class TestMetadata
+ private RouteEndpoint CreateEndpoint(string template, int order = 0, params object[] metadata)
+ {
+ var builder = new RouteEndpointBuilder(TestConstants.EmptyRequestDelegate, RoutePatternFactory.Parse(template), order);
+ for (var i = 0; i < metadata.Length; i++)
{
+ builder.Metadata.Add(metadata[i]);
}
- private class TestMetadataMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy
- {
- public override int Order { get; }
- public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata>.Default;
- }
+ builder.DisplayName = "test: " + template;
+ return (RouteEndpoint)builder.Build();
+ }
+
+ private static DfaMatcherBuilder CreateDfaMatcherBuilder(params MatcherPolicy[] policies)
+ {
+ var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
+ return new DfaMatcherBuilder(
+ NullLoggerFactory.Instance,
+ Mock.Of<ParameterPolicyFactory>(),
+ Mock.Of<EndpointSelector>(),
+ policies);
+ }
+
+ private class TestMetadata
+ {
+ }
+
+ private class TestMetadataMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy
+ {
+ public override int Order { get; }
+ public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata>.Default;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/DataSourceDependentMatcherTest.cs b/src/Http/Routing/test/UnitTests/Matching/DataSourceDependentMatcherTest.cs
index 88563ba750..f03bd7272f 100644
--- a/src/Http/Routing/test/UnitTests/Matching/DataSourceDependentMatcherTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/DataSourceDependentMatcherTest.cs
@@ -9,264 +9,263 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class DataSourceDependentMatcherTest
{
- public class DataSourceDependentMatcherTest
+ [Fact]
+ public void Matcher_Initializes_InConstructor()
{
- [Fact]
- public void Matcher_Initializes_InConstructor()
- {
- // Arrange
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
-
- // Act
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+ // Arrange
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
- // Assert
- var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
- Assert.Empty(inner.Endpoints);
+ // Act
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
- Assert.NotNull(lifetime.Cache);
- }
-
- [Fact]
- public void Matcher_Reinitializes_WhenDataSourceChanges()
- {
- // Arrange
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+ // Assert
+ var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
+ Assert.Empty(inner.Endpoints);
- var endpoint = new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("a/b/c"),
- 0,
- EndpointMetadataCollection.Empty,
- "test");
-
- // Act
- dataSource.AddEndpoint(endpoint);
-
- // Assert
- var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
- Assert.Collection(
- inner.Endpoints,
- e => Assert.Same(endpoint, e));
- }
-
- [Fact]
- public void Matcher_IgnoresUpdate_WhenDisposed()
- {
- // Arrange
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
-
- var endpoint = new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("a/b/c"),
- 0,
- EndpointMetadataCollection.Empty,
- "test");
-
- lifetime.Dispose();
-
- // Act
- dataSource.AddEndpoint(endpoint);
-
- // Assert
- var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
- Assert.Empty(inner.Endpoints);
- }
+ Assert.NotNull(lifetime.Cache);
+ }
- [Fact]
- public void Matcher_Ignores_NonRouteEndpoint()
- {
- // Arrange
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- var endpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "test");
- dataSource.AddEndpoint(endpoint);
-
- // Act
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
-
- // Assert
- var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
- Assert.Empty(inner.Endpoints);
- }
+ [Fact]
+ public void Matcher_Reinitializes_WhenDataSourceChanges()
+ {
+ // Arrange
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+
+ var endpoint = new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("a/b/c"),
+ 0,
+ EndpointMetadataCollection.Empty,
+ "test");
+
+ // Act
+ dataSource.AddEndpoint(endpoint);
+
+ // Assert
+ var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
+ Assert.Collection(
+ inner.Endpoints,
+ e => Assert.Same(endpoint, e));
+ }
- [Fact]
- public void Matcher_Ignores_SuppressedEndpoint()
- {
- // Arrange
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- var endpoint = new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/"),
- 0,
- new EndpointMetadataCollection(new SuppressMatchingMetadata()),
- "test");
- dataSource.AddEndpoint(endpoint);
+ [Fact]
+ public void Matcher_IgnoresUpdate_WhenDisposed()
+ {
+ // Arrange
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+
+ var endpoint = new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("a/b/c"),
+ 0,
+ EndpointMetadataCollection.Empty,
+ "test");
+
+ lifetime.Dispose();
+
+ // Act
+ dataSource.AddEndpoint(endpoint);
+
+ // Assert
+ var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
+ Assert.Empty(inner.Endpoints);
+ }
- // Act
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+ [Fact]
+ public void Matcher_Ignores_NonRouteEndpoint()
+ {
+ // Arrange
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ var endpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "test");
+ dataSource.AddEndpoint(endpoint);
+
+ // Act
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+
+ // Assert
+ var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
+ Assert.Empty(inner.Endpoints);
+ }
- // Assert
- var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
- Assert.Empty(inner.Endpoints);
- }
+ [Fact]
+ public void Matcher_Ignores_SuppressedEndpoint()
+ {
+ // Arrange
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ var endpoint = new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/"),
+ 0,
+ new EndpointMetadataCollection(new SuppressMatchingMetadata()),
+ "test");
+ dataSource.AddEndpoint(endpoint);
+
+ // Act
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+
+ // Assert
+ var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
+ Assert.Empty(inner.Endpoints);
+ }
- [Fact]
- public void Matcher_UnsuppressedEndpoint_IsUsed()
- {
- // Arrange
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- var endpoint = new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/"),
- 0,
- new EndpointMetadataCollection(new SuppressMatchingMetadata(), new EncourageMatchingMetadata()),
- "test");
- dataSource.AddEndpoint(endpoint);
+ [Fact]
+ public void Matcher_UnsuppressedEndpoint_IsUsed()
+ {
+ // Arrange
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ var endpoint = new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/"),
+ 0,
+ new EndpointMetadataCollection(new SuppressMatchingMetadata(), new EncourageMatchingMetadata()),
+ "test");
+ dataSource.AddEndpoint(endpoint);
+
+ // Act
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+
+ // Assert
+ var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
+ Assert.Same(endpoint, Assert.Single(inner.Endpoints));
+ }
- // Act
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+ [Fact]
+ public void Matcher_ThrowsOnDuplicateEndpoints()
+ {
+ // Arrange
+ var expectedError = "Duplicate endpoint name 'Foo' found on '/bar' and '/foo'. Endpoint names must be globally unique.";
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ dataSource.AddEndpoint(new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/foo"),
+ 0,
+ new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
+ "/foo"
+ ));
+ dataSource.AddEndpoint(new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/bar"),
+ 0,
+ new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
+ "/bar"
+ ));
+
+ // Assert
+ var exception = Assert.Throws<InvalidOperationException>(
+ () => new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create));
+ Assert.Equal(expectedError, exception.Message);
+ }
- // Assert
- var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
- Assert.Same(endpoint, Assert.Single(inner.Endpoints));
- }
+ [Fact]
+ public void Matcher_ThrowsOnDuplicateEndpointsFromMultipleSources()
+ {
+ // Arrange
+ var expectedError = "Duplicate endpoint name 'Foo' found on '/foo2' and '/foo'. Endpoint names must be globally unique.";
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ dataSource.AddEndpoint(new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/foo"),
+ 0,
+ new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
+ "/foo"
+ ));
+ dataSource.AddEndpoint(new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/bar"),
+ 0,
+ new EndpointMetadataCollection(new EndpointNameMetadata("Bar")),
+ "/bar"
+ ));
+ var anotherDataSource = new DynamicEndpointDataSource();
+ anotherDataSource.AddEndpoint(new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/foo2"),
+ 0,
+ new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
+ "/foo2"
+ ));
+
+ var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource, anotherDataSource });
+
+ // Assert
+ var exception = Assert.Throws<InvalidOperationException>(
+ () => new DataSourceDependentMatcher(compositeDataSource, lifetime, TestMatcherBuilder.Create));
+ Assert.Equal(expectedError, exception.Message);
+ }
- [Fact]
- public void Matcher_ThrowsOnDuplicateEndpoints()
- {
- // Arrange
- var expectedError = "Duplicate endpoint name 'Foo' found on '/bar' and '/foo'. Endpoint names must be globally unique.";
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- dataSource.AddEndpoint(new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/foo"),
- 0,
- new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
- "/foo"
- ));
- dataSource.AddEndpoint(new RouteEndpoint(
+ [Fact]
+ public void Matcher_ThrowsOnDuplicateEndpointAddedLater()
+ {
+ // Arrange
+ var expectedError = "Duplicate endpoint name 'Foo' found on '/bar' and '/foo'. Endpoint names must be globally unique.";
+ var dataSource = new DynamicEndpointDataSource();
+ var lifetime = new DataSourceDependentMatcher.Lifetime();
+ dataSource.AddEndpoint(new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse("/foo"),
+ 0,
+ new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
+ "/foo"
+ ));
+
+ // Act (should be all good since no duplicate has been added yet)
+ var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
+
+ // Assert that rerunning initializer throws AggregateException
+ var exception = Assert.Throws<AggregateException>(
+ () => dataSource.AddEndpoint(new RouteEndpoint(
TestConstants.EmptyRequestDelegate,
RoutePatternFactory.Parse("/bar"),
0,
new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
"/bar"
- ));
-
- // Assert
- var exception = Assert.Throws<InvalidOperationException>(
- () => new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create));
- Assert.Equal(expectedError, exception.Message);
- }
-
- [Fact]
- public void Matcher_ThrowsOnDuplicateEndpointsFromMultipleSources()
- {
- // Arrange
- var expectedError = "Duplicate endpoint name 'Foo' found on '/foo2' and '/foo'. Endpoint names must be globally unique.";
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- dataSource.AddEndpoint(new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/foo"),
- 0,
- new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
- "/foo"
- ));
- dataSource.AddEndpoint(new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/bar"),
- 0,
- new EndpointMetadataCollection(new EndpointNameMetadata("Bar")),
- "/bar"
- ));
- var anotherDataSource = new DynamicEndpointDataSource();
- anotherDataSource.AddEndpoint(new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/foo2"),
- 0,
- new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
- "/foo2"
- ));
+ )));
+ Assert.Equal(expectedError, exception.InnerException.Message);
+ }
- var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource, anotherDataSource });
+ private class TestMatcherBuilder : MatcherBuilder
+ {
+ public static Func<MatcherBuilder> Create = () => new TestMatcherBuilder();
- // Assert
- var exception = Assert.Throws<InvalidOperationException>(
- () => new DataSourceDependentMatcher(compositeDataSource, lifetime, TestMatcherBuilder.Create));
- Assert.Equal(expectedError, exception.Message);
- }
+ private List<RouteEndpoint> Endpoints { get; } = new List<RouteEndpoint>();
- [Fact]
- public void Matcher_ThrowsOnDuplicateEndpointAddedLater()
+ public override void AddEndpoint(RouteEndpoint endpoint)
{
- // Arrange
- var expectedError = "Duplicate endpoint name 'Foo' found on '/bar' and '/foo'. Endpoint names must be globally unique.";
- var dataSource = new DynamicEndpointDataSource();
- var lifetime = new DataSourceDependentMatcher.Lifetime();
- dataSource.AddEndpoint(new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/foo"),
- 0,
- new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
- "/foo"
- ));
-
- // Act (should be all good since no duplicate has been added yet)
- var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
-
- // Assert that rerunning initializer throws AggregateException
- var exception = Assert.Throws<AggregateException>(
- () => dataSource.AddEndpoint(new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse("/bar"),
- 0,
- new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
- "/bar"
- )));
- Assert.Equal(expectedError, exception.InnerException.Message);
+ Endpoints.Add(endpoint);
}
- private class TestMatcherBuilder : MatcherBuilder
+ public override Matcher Build()
{
- public static Func<MatcherBuilder> Create = () => new TestMatcherBuilder();
-
- private List<RouteEndpoint> Endpoints { get; } = new List<RouteEndpoint>();
-
- public override void AddEndpoint(RouteEndpoint endpoint)
- {
- Endpoints.Add(endpoint);
- }
-
- public override Matcher Build()
- {
- return new TestMatcher() { Endpoints = Endpoints, };
- }
+ return new TestMatcher() { Endpoints = Endpoints, };
}
+ }
- private class TestMatcher : Matcher
- {
- public IReadOnlyList<RouteEndpoint> Endpoints { get; set; }
-
- public override Task MatchAsync(HttpContext httpContext)
- {
- throw new NotImplementedException();
- }
- }
+ private class TestMatcher : Matcher
+ {
+ public IReadOnlyList<RouteEndpoint> Endpoints { get; set; }
- private class EncourageMatchingMetadata : ISuppressMatchingMetadata
+ public override Task MatchAsync(HttpContext httpContext)
{
- public bool SuppressMatching => false;
+ throw new NotImplementedException();
}
}
+
+ private class EncourageMatchingMetadata : ISuppressMatchingMetadata
+ {
+ public bool SuppressMatching => false;
+ }
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs b/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs
index a90e481382..d421bb4c94 100644
--- a/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs
@@ -9,192 +9,191 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class DefaultEndpointSelectorTest
{
- public class DefaultEndpointSelectorTest
+ [Fact]
+ public async Task SelectAsync_NoCandidates_DoesNothing()
{
- [Fact]
- public async Task SelectAsync_NoCandidates_DoesNothing()
- {
- // Arrange
- var endpoints = new RouteEndpoint[] { };
- var scores = new int[] { };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ // Arrange
+ var endpoints = new RouteEndpoint[] { };
+ var scores = new int[] { };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- await selector.SelectAsync(httpContext, candidateSet);
+ // Act
+ await selector.SelectAsync(httpContext, candidateSet);
- // Assert
- Assert.Null(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task SelectAsync_NoValidCandidates_DoesNothing()
- {
- // Arrange
- var endpoints = new RouteEndpoint[] { CreateEndpoint("/test"), };
- var scores = new int[] { 0, };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ [Fact]
+ public async Task SelectAsync_NoValidCandidates_DoesNothing()
+ {
+ // Arrange
+ var endpoints = new RouteEndpoint[] { CreateEndpoint("/test"), };
+ var scores = new int[] { 0, };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- candidateSet[0].Values = new RouteValueDictionary();
- candidateSet.SetValidity(0, false);
+ candidateSet[0].Values = new RouteValueDictionary();
+ candidateSet.SetValidity(0, false);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- await selector.SelectAsync(httpContext, candidateSet);
+ // Act
+ await selector.SelectAsync(httpContext, candidateSet);
- // Assert
- Assert.Null(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task SelectAsync_SingleCandidate_ChoosesCandidate()
- {
- // Arrange
- var endpoints = new RouteEndpoint[] { CreateEndpoint("/test"), };
- var scores = new int[] { 0, };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ [Fact]
+ public async Task SelectAsync_SingleCandidate_ChoosesCandidate()
+ {
+ // Arrange
+ var endpoints = new RouteEndpoint[] { CreateEndpoint("/test"), };
+ var scores = new int[] { 0, };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- candidateSet[0].Values = new RouteValueDictionary();
- candidateSet.SetValidity(0, true);
+ candidateSet[0].Values = new RouteValueDictionary();
+ candidateSet.SetValidity(0, true);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- await selector.SelectAsync(httpContext, candidateSet);
+ // Act
+ await selector.SelectAsync(httpContext, candidateSet);
- // Assert
- Assert.Same(endpoints[0], httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Same(endpoints[0], httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task SelectAsync_SingleValidCandidate_ChoosesCandidate()
- {
- // Arrange
- var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), };
- var scores = new int[] { 0, 0 };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ [Fact]
+ public async Task SelectAsync_SingleValidCandidate_ChoosesCandidate()
+ {
+ // Arrange
+ var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), };
+ var scores = new int[] { 0, 0 };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- candidateSet.SetValidity(0, false);
- candidateSet.SetValidity(1, true);
+ candidateSet.SetValidity(0, false);
+ candidateSet.SetValidity(1, true);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- await selector.SelectAsync(httpContext, candidateSet);
+ // Act
+ await selector.SelectAsync(httpContext, candidateSet);
- // Assert
- Assert.Same(endpoints[1], httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Same(endpoints[1], httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task SelectAsync_SingleValidCandidateInGroup_ChoosesCandidate()
- {
- // Arrange
- var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), CreateEndpoint("/test3"), };
- var scores = new int[] { 0, 0, 1 };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ [Fact]
+ public async Task SelectAsync_SingleValidCandidateInGroup_ChoosesCandidate()
+ {
+ // Arrange
+ var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), CreateEndpoint("/test3"), };
+ var scores = new int[] { 0, 0, 1 };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- candidateSet.SetValidity(0, false);
- candidateSet.SetValidity(1, true);
- candidateSet.SetValidity(2, true);
+ candidateSet.SetValidity(0, false);
+ candidateSet.SetValidity(1, true);
+ candidateSet.SetValidity(2, true);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- await selector.SelectAsync(httpContext, candidateSet);
+ // Act
+ await selector.SelectAsync(httpContext, candidateSet);
- // Assert
- Assert.Same(endpoints[1], httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Same(endpoints[1], httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task SelectAsync_ManyGroupsLastCandidate_ChoosesCandidate()
+ [Fact]
+ public async Task SelectAsync_ManyGroupsLastCandidate_ChoosesCandidate()
+ {
+ // Arrange
+ var endpoints = new RouteEndpoint[]
{
- // Arrange
- var endpoints = new RouteEndpoint[]
- {
CreateEndpoint("/test1"),
CreateEndpoint("/test2"),
CreateEndpoint("/test3"),
CreateEndpoint("/test4"),
CreateEndpoint("/test5"),
- };
- var scores = new int[] { 0, 1, 2, 3, 4 };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ };
+ var scores = new int[] { 0, 1, 2, 3, 4 };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- candidateSet.SetValidity(0, false);
- candidateSet.SetValidity(1, false);
- candidateSet.SetValidity(2, false);
- candidateSet.SetValidity(3, false);
- candidateSet.SetValidity(4, true);
+ candidateSet.SetValidity(0, false);
+ candidateSet.SetValidity(1, false);
+ candidateSet.SetValidity(2, false);
+ candidateSet.SetValidity(3, false);
+ candidateSet.SetValidity(4, true);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- await selector.SelectAsync(httpContext, candidateSet);
+ // Act
+ await selector.SelectAsync(httpContext, candidateSet);
- // Assert
- Assert.Same(endpoints[4], httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Same(endpoints[4], httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task SelectAsync_MultipleValidCandidatesInGroup_ReportsAmbiguity()
- {
- // Arrange
- var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), CreateEndpoint("/test3"), };
- var scores = new int[] { 0, 1, 1 };
- var candidateSet = CreateCandidateSet(endpoints, scores);
+ [Fact]
+ public async Task SelectAsync_MultipleValidCandidatesInGroup_ReportsAmbiguity()
+ {
+ // Arrange
+ var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), CreateEndpoint("/test3"), };
+ var scores = new int[] { 0, 1, 1 };
+ var candidateSet = CreateCandidateSet(endpoints, scores);
- candidateSet.SetValidity(0, false);
- candidateSet.SetValidity(1, true);
- candidateSet.SetValidity(2, true);
+ candidateSet.SetValidity(0, false);
+ candidateSet.SetValidity(1, true);
+ candidateSet.SetValidity(2, true);
- var httpContext = CreateContext();
- var selector = CreateSelector();
+ var httpContext = CreateContext();
+ var selector = CreateSelector();
- // Act
- var ex = await Assert.ThrowsAsync<AmbiguousMatchException>(() => selector.SelectAsync(httpContext, candidateSet));
+ // Act
+ var ex = await Assert.ThrowsAsync<AmbiguousMatchException>(() => selector.SelectAsync(httpContext, candidateSet));
- // Assert
- Assert.Equal(
+ // Assert
+ Assert.Equal(
@"The request matched multiple endpoints. Matches: " + Environment.NewLine + Environment.NewLine +
"test: /test2" + Environment.NewLine + "test: /test3", ex.Message);
- Assert.Null(httpContext.GetEndpoint());
- }
+ Assert.Null(httpContext.GetEndpoint());
+ }
- private static HttpContext CreateContext()
- {
- return new DefaultHttpContext();
- }
+ private static HttpContext CreateContext()
+ {
+ return new DefaultHttpContext();
+ }
- private static RouteEndpoint CreateEndpoint(string template)
- {
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template),
- 0,
- EndpointMetadataCollection.Empty,
- $"test: {template}");
- }
-
- private static CandidateSet CreateCandidateSet(RouteEndpoint[] endpoints, int[] scores)
- {
- return new CandidateSet(endpoints, new RouteValueDictionary[endpoints.Length], scores);
- }
+ private static RouteEndpoint CreateEndpoint(string template)
+ {
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template),
+ 0,
+ EndpointMetadataCollection.Empty,
+ $"test: {template}");
+ }
- private static DefaultEndpointSelector CreateSelector()
- {
- return new DefaultEndpointSelector();
- }
+ private static CandidateSet CreateCandidateSet(RouteEndpoint[] endpoints, int[] scores)
+ {
+ return new CandidateSet(endpoints, new RouteValueDictionary[endpoints.Length], scores);
+ }
+
+ private static DefaultEndpointSelector CreateSelector()
+ {
+ return new DefaultEndpointSelector();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs
index 64487a90c6..334fbd268f 100644
--- a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherBuilderTest.cs
@@ -14,1059 +14,1059 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class DfaMatcherBuilderTest
{
- public class DfaMatcherBuilderTest
+ [Fact]
+ public void BuildDfaTree_SingleEndpoint_Empty()
{
- [Fact]
- public void BuildDfaTree_SingleEndpoint_Empty()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint("/");
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint("/");
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Same(endpoint, Assert.Single(root.Matches));
- Assert.Null(root.Parameters);
- Assert.Null(root.Literals);
- }
+ // Assert
+ Assert.Same(endpoint, Assert.Single(root.Matches));
+ Assert.Null(root.Parameters);
+ Assert.Null(root.Literals);
+ }
- [Fact]
- public void BuildDfaTree_SingleEndpoint_Literals()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_SingleEndpoint_Literals()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint("a/b/c");
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint("a/b/c");
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Null(a.Matches);
- Assert.Null(a.Parameters);
+ var a = next.Value;
+ Assert.Null(a.Matches);
+ Assert.Null(a.Parameters);
- next = Assert.Single(a.Literals);
- Assert.Equal("b", next.Key);
+ next = Assert.Single(a.Literals);
+ Assert.Equal("b", next.Key);
- var b = next.Value;
- Assert.Null(b.Matches);
- Assert.Null(b.Parameters);
+ var b = next.Value;
+ Assert.Null(b.Matches);
+ Assert.Null(b.Parameters);
- next = Assert.Single(b.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b.Literals);
+ Assert.Equal("c", next.Key);
- var c = next.Value;
- Assert.Same(endpoint, Assert.Single(c.Matches));
- Assert.Null(c.Parameters);
- Assert.Null(c.Literals);
- }
+ var c = next.Value;
+ Assert.Same(endpoint, Assert.Single(c.Matches));
+ Assert.Null(c.Parameters);
+ Assert.Null(c.Literals);
+ }
- [Fact]
- public void BuildDfaTree_SingleEndpoint_Parameters()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_SingleEndpoint_Parameters()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint("{a}/{b}/{c}");
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint("{a}/{b}/{c}");
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Literals);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Literals);
- var a = root.Parameters;
- Assert.Null(a.Matches);
- Assert.Null(a.Literals);
+ var a = root.Parameters;
+ Assert.Null(a.Matches);
+ Assert.Null(a.Literals);
- var b = a.Parameters;
- Assert.Null(b.Matches);
- Assert.Null(b.Literals);
+ var b = a.Parameters;
+ Assert.Null(b.Matches);
+ Assert.Null(b.Literals);
- var c = b.Parameters;
- Assert.Same(endpoint, Assert.Single(c.Matches));
- Assert.Null(c.Parameters);
- Assert.Null(c.Literals);
- }
+ var c = b.Parameters;
+ Assert.Same(endpoint, Assert.Single(c.Matches));
+ Assert.Null(c.Parameters);
+ Assert.Null(c.Literals);
+ }
- [Fact]
- public void BuildDfaTree_SingleEndpoint_CatchAll()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_SingleEndpoint_CatchAll()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint("{a}/{*b}");
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint("{a}/{*b}");
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Literals);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Literals);
- var a = root.Parameters;
+ var a = root.Parameters;
- // The catch all can match a path like '/a'
- Assert.Same(endpoint, Assert.Single(a.Matches));
- Assert.Null(a.Literals);
- Assert.Null(a.Parameters);
+ // The catch all can match a path like '/a'
+ Assert.Same(endpoint, Assert.Single(a.Matches));
+ Assert.Null(a.Literals);
+ Assert.Null(a.Parameters);
- // Catch-all nodes include an extra transition that loops to process
- // extra segments.
- var catchAll = a.CatchAll;
- Assert.Same(endpoint, Assert.Single(catchAll.Matches));
- Assert.Null(catchAll.Literals);
- Assert.Same(catchAll, catchAll.Parameters);
- Assert.Same(catchAll, catchAll.CatchAll);
- }
+ // Catch-all nodes include an extra transition that loops to process
+ // extra segments.
+ var catchAll = a.CatchAll;
+ Assert.Same(endpoint, Assert.Single(catchAll.Matches));
+ Assert.Null(catchAll.Literals);
+ Assert.Same(catchAll, catchAll.Parameters);
+ Assert.Same(catchAll, catchAll.CatchAll);
+ }
- [Fact]
- public void BuildDfaTree_SingleEndpoint_CatchAllAtRoot()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_SingleEndpoint_CatchAllAtRoot()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint("{*a}");
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint("{*a}");
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Same(endpoint, Assert.Single(root.Matches));
- Assert.Null(root.Literals);
+ // Assert
+ Assert.Same(endpoint, Assert.Single(root.Matches));
+ Assert.Null(root.Literals);
- // Catch-all nodes include an extra transition that loops to process
- // extra segments.
- var catchAll = root.CatchAll;
- Assert.Same(endpoint, Assert.Single(catchAll.Matches));
- Assert.Null(catchAll.Literals);
- Assert.Same(catchAll, catchAll.Parameters);
- }
+ // Catch-all nodes include an extra transition that loops to process
+ // extra segments.
+ var catchAll = root.CatchAll;
+ Assert.Same(endpoint, Assert.Single(catchAll.Matches));
+ Assert.Null(catchAll.Literals);
+ Assert.Same(catchAll, catchAll.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_LiteralAndLiteral()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_LiteralAndLiteral()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a/b1/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a/b1/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a/b2/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a/b2/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Null(a.Matches);
+ var a = next.Value;
+ Assert.Null(a.Matches);
- Assert.Equal(2, a.Literals.Count);
+ Assert.Equal(2, a.Literals.Count);
- var b1 = a.Literals["b1"];
- Assert.Null(b1.Matches);
- Assert.Null(b1.Parameters);
+ var b1 = a.Literals["b1"];
+ Assert.Null(b1.Matches);
+ Assert.Null(b1.Parameters);
- next = Assert.Single(b1.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b1.Literals);
+ Assert.Equal("c", next.Key);
- var c1 = next.Value;
- Assert.Same(endpoint1, Assert.Single(c1.Matches));
- Assert.Null(c1.Parameters);
- Assert.Null(c1.Literals);
+ var c1 = next.Value;
+ Assert.Same(endpoint1, Assert.Single(c1.Matches));
+ Assert.Null(c1.Parameters);
+ Assert.Null(c1.Literals);
- var b2 = a.Literals["b2"];
- Assert.Null(b2.Matches);
- Assert.Null(b2.Parameters);
+ var b2 = a.Literals["b2"];
+ Assert.Null(b2.Matches);
+ Assert.Null(b2.Parameters);
- next = Assert.Single(b2.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b2.Literals);
+ Assert.Equal("c", next.Key);
- var c2 = next.Value;
- Assert.Same(endpoint2, Assert.Single(c2.Matches));
- Assert.Null(c2.Parameters);
- Assert.Null(c2.Literals);
- }
+ var c2 = next.Value;
+ Assert.Same(endpoint2, Assert.Single(c2.Matches));
+ Assert.Null(c2.Parameters);
+ Assert.Null(c2.Literals);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_LiteralDifferentCase()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_LiteralDifferentCase()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a/b1/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a/b1/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("A/b2/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("A/b2/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Null(a.Matches);
+ var a = next.Value;
+ Assert.Null(a.Matches);
- Assert.Equal(2, a.Literals.Count);
+ Assert.Equal(2, a.Literals.Count);
- var b1 = a.Literals["b1"];
- Assert.Null(b1.Matches);
- Assert.Null(b1.Parameters);
+ var b1 = a.Literals["b1"];
+ Assert.Null(b1.Matches);
+ Assert.Null(b1.Parameters);
- next = Assert.Single(b1.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b1.Literals);
+ Assert.Equal("c", next.Key);
- var c1 = next.Value;
- Assert.Same(endpoint1, Assert.Single(c1.Matches));
- Assert.Null(c1.Parameters);
- Assert.Null(c1.Literals);
+ var c1 = next.Value;
+ Assert.Same(endpoint1, Assert.Single(c1.Matches));
+ Assert.Null(c1.Parameters);
+ Assert.Null(c1.Literals);
- var b2 = a.Literals["b2"];
- Assert.Null(b2.Matches);
- Assert.Null(b2.Parameters);
+ var b2 = a.Literals["b2"];
+ Assert.Null(b2.Matches);
+ Assert.Null(b2.Parameters);
- next = Assert.Single(b2.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b2.Literals);
+ Assert.Equal("c", next.Key);
- var c2 = next.Value;
- Assert.Same(endpoint2, Assert.Single(c2.Matches));
- Assert.Null(c2.Parameters);
- Assert.Null(c2.Literals);
- }
+ var c2 = next.Value;
+ Assert.Same(endpoint2, Assert.Single(c2.Matches));
+ Assert.Null(c2.Parameters);
+ Assert.Null(c2.Literals);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_LiteralAndParameter()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_LiteralAndParameter()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a/b/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a/b/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a/{b}/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a/{b}/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Null(a.Matches);
+ var a = next.Value;
+ Assert.Null(a.Matches);
- next = Assert.Single(a.Literals);
- Assert.Equal("b", next.Key);
+ next = Assert.Single(a.Literals);
+ Assert.Equal("b", next.Key);
- var b = next.Value;
- Assert.Null(b.Matches);
- Assert.Null(b.Parameters);
+ var b = next.Value;
+ Assert.Null(b.Matches);
+ Assert.Null(b.Parameters);
- next = Assert.Single(b.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b.Literals);
+ Assert.Equal("c", next.Key);
- var c1 = next.Value;
- Assert.Collection(
- c1.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(c1.Parameters);
- Assert.Null(c1.Literals);
+ var c1 = next.Value;
+ Assert.Collection(
+ c1.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(c1.Parameters);
+ Assert.Null(c1.Literals);
- var b2 = a.Parameters;
- Assert.Null(b2.Matches);
- Assert.Null(b2.Parameters);
+ var b2 = a.Parameters;
+ Assert.Null(b2.Matches);
+ Assert.Null(b2.Parameters);
- next = Assert.Single(b2.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b2.Literals);
+ Assert.Equal("c", next.Key);
- var c2 = next.Value;
- Assert.Same(endpoint2, Assert.Single(c2.Matches));
- Assert.Null(c2.Parameters);
- Assert.Null(c2.Literals);
- }
+ var c2 = next.Value;
+ Assert.Same(endpoint2, Assert.Single(c2.Matches));
+ Assert.Null(c2.Parameters);
+ Assert.Null(c2.Literals);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ParameterAndParameter()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ParameterAndParameter()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a/{b1}/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a/{b1}/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a/{b2}/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a/{b2}/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Null(a.Matches);
- Assert.Null(a.Literals);
+ var a = next.Value;
+ Assert.Null(a.Matches);
+ Assert.Null(a.Literals);
- var b = a.Parameters;
- Assert.Null(b.Matches);
- Assert.Null(b.Parameters);
+ var b = a.Parameters;
+ Assert.Null(b.Matches);
+ Assert.Null(b.Parameters);
- next = Assert.Single(b.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b.Literals);
+ Assert.Equal("c", next.Key);
- var c = next.Value;
- Assert.Collection(
- c.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(c.Parameters);
- Assert.Null(c.Literals);
- }
+ var c = next.Value;
+ Assert.Collection(
+ c.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(c.Parameters);
+ Assert.Null(c.Literals);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_LiteralAndCatchAll()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_LiteralAndCatchAll()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a/b/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a/b/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a/{*b}");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a/{*b}");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Same(endpoint2, Assert.Single(a.Matches));
+ var a = next.Value;
+ Assert.Same(endpoint2, Assert.Single(a.Matches));
- next = Assert.Single(a.Literals);
- Assert.Equal("b", next.Key);
+ next = Assert.Single(a.Literals);
+ Assert.Equal("b", next.Key);
- var b1 = next.Value;
- Assert.Same(endpoint2, Assert.Single(a.Matches));
- Assert.Null(b1.Parameters);
+ var b1 = next.Value;
+ Assert.Same(endpoint2, Assert.Single(a.Matches));
+ Assert.Null(b1.Parameters);
- next = Assert.Single(b1.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b1.Literals);
+ Assert.Equal("c", next.Key);
- var c1 = next.Value;
- Assert.Collection(
- c1.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(c1.Parameters);
- Assert.Null(c1.Literals);
+ var c1 = next.Value;
+ Assert.Collection(
+ c1.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(c1.Parameters);
+ Assert.Null(c1.Literals);
- var catchAll = a.CatchAll;
- Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
- Assert.Same(catchAll, catchAll.Parameters);
- Assert.Same(catchAll, catchAll.CatchAll);
- }
+ var catchAll = a.CatchAll;
+ Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
+ Assert.Same(catchAll, catchAll.Parameters);
+ Assert.Same(catchAll, catchAll.CatchAll);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a/{b}/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a/{b}/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a/{*b}");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a/{*b}");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Same(endpoint2, Assert.Single(a.Matches));
- Assert.Null(a.Literals);
+ var a = next.Value;
+ Assert.Same(endpoint2, Assert.Single(a.Matches));
+ Assert.Null(a.Literals);
- var b1 = a.Parameters;
- Assert.Same(endpoint2, Assert.Single(a.Matches));
- Assert.Null(b1.Parameters);
+ var b1 = a.Parameters;
+ Assert.Same(endpoint2, Assert.Single(a.Matches));
+ Assert.Null(b1.Parameters);
- next = Assert.Single(b1.Literals);
- Assert.Equal("c", next.Key);
+ next = Assert.Single(b1.Literals);
+ Assert.Equal("c", next.Key);
- var c1 = next.Value;
- Assert.Collection(
- c1.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(c1.Parameters);
- Assert.Null(c1.Literals);
+ var c1 = next.Value;
+ Assert.Collection(
+ c1.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(c1.Parameters);
+ Assert.Null(c1.Literals);
- var catchAll = a.CatchAll;
- Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
- Assert.Same(catchAll, catchAll.Parameters);
- Assert.Same(catchAll, catchAll.CatchAll);
- }
+ var catchAll = a.CatchAll;
+ Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
+ Assert.Same(catchAll, catchAll.Parameters);
+ Assert.Same(catchAll, catchAll.CatchAll);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_DoesNotMeetConstraint()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("a/c");
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("{a:length(2)}/b/c");
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
-
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("a", aNodeKvp.Key);
-
- var aNodeValue = aNodeKvp.Value;
- var cNodeKvp = Assert.Single(aNodeValue.Literals);
- Assert.Equal("c", cNodeKvp.Key);
- var cNode = cNodeKvp.Value;
-
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
-
- var bNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bNodeKvp.Key);
- var bNode = bNodeKvp.Value;
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramCNodeKvp = Assert.Single(bNode.Literals);
-
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_DoesNotMeetConstraint()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("a/c");
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("{a:length(2)}/b/c");
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
+
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("a", aNodeKvp.Key);
+
+ var aNodeValue = aNodeKvp.Value;
+ var cNodeKvp = Assert.Single(aNodeValue.Literals);
+ Assert.Equal("c", cNodeKvp.Key);
+ var cNode = cNodeKvp.Value;
+
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
+
+ var bNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bNodeKvp.Key);
+ var bNode = bNodeKvp.Value;
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramCNodeKvp = Assert.Single(bNode.Literals);
+
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Theory]
- [InlineData("aa/c", "aa", "c")]
- [InlineData("1/c", "1", "c")]
- public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_EvaluatesAllConstraints(string candidate, string firstSegment, string secondSegment)
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint(candidate);
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("{a:int:length(2)}/b/c");
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
-
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal(firstSegment, aNodeKvp.Key);
-
- var aNodeValue = aNodeKvp.Value;
- var cNodeKvp = Assert.Single(aNodeValue.Literals);
- Assert.Equal(secondSegment, cNodeKvp.Key);
- var cNode = cNodeKvp.Value;
-
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
-
- var bNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bNodeKvp.Key);
- var bNode = bNodeKvp.Value;
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramCNodeKvp = Assert.Single(bNode.Literals);
-
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ [Theory]
+ [InlineData("aa/c", "aa", "c")]
+ [InlineData("1/c", "1", "c")]
+ public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_EvaluatesAllConstraints(string candidate, string firstSegment, string secondSegment)
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint(candidate);
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("{a:int:length(2)}/b/c");
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
+
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal(firstSegment, aNodeKvp.Key);
+
+ var aNodeValue = aNodeKvp.Value;
+ var cNodeKvp = Assert.Single(aNodeValue.Literals);
+ Assert.Equal(secondSegment, cNodeKvp.Key);
+ var cNode = cNodeKvp.Value;
+
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
+
+ var bNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bNodeKvp.Key);
+ var bNode = bNodeKvp.Value;
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramCNodeKvp = Assert.Single(bNode.Literals);
+
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_MeetsConstraint()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_MeetsConstraint()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("aa/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("aa/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("{a:length(2)}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("{a:length(2)}/b/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch aa -> c = (aa/c)
+ // Branch aa -> c = (aa/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("aa", aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("aa", aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
+ var aNodeValue = aNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch (aa) -> b -> c = ({a:length(2)}/b/c)
+ // Branch (aa) -> b -> c = ({a:length(2)}/b/c)
- Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramBCNodeKvp = Assert.Single(bNode.Literals);
- Assert.Equal("c", paramBCNodeKvp.Key);
- var paramBCNode = paramBCNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramBCNodeKvp = Assert.Single(bNode.Literals);
+ Assert.Equal("c", paramBCNodeKvp.Key);
+ var paramBCNode = paramBCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = ({a:length(2)}/b/c)
+ // Branch {param} -> b -> c = ({a:length(2)}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_BothCandidates_WhenLitteralPatternMeetsConstraintAndRoutePattern()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ConstrainedParameterTrimming_BothCandidates_WhenLitteralPatternMeetsConstraintAndRoutePattern()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("aa/b/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("aa/b/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("{a:length(2)}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("{a:length(2)}/b/c");
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("aa/c");
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("aa/c");
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch aa -> c = (aa/c)
+ // Branch aa -> c = (aa/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("aa", aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("aa", aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
+ var aNodeValue = aNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
- Assert.Same(endpoint3, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint3, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch (aa) -> b -> c = (aa/b/c, {a:length(2)}/b/c)
+ // Branch (aa) -> b -> c = (aa/b/c, {a:length(2)}/b/c)
- Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramBCNodeKvp = Assert.Single(bNode.Literals);
- Assert.Equal("c", paramBCNodeKvp.Key);
- var paramBCNode = paramBCNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramBCNodeKvp = Assert.Single(bNode.Literals);
+ Assert.Equal("c", paramBCNodeKvp.Key);
+ var paramBCNode = paramBCNodeKvp.Value;
- Assert.Equal(new[] { endpoint1, endpoint2 }, paramBCNode.Matches.ToArray());
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Equal(new[] { endpoint1, endpoint2 }, paramBCNode.Matches.ToArray());
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = ({a:length(2)}/b/c)
+ // Branch {param} -> b -> c = ({a:length(2)}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ComplexParameter_LiteralDoesNotMatchComplexParameter()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("a/c");
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("a{value}/b/c");
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
-
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("a", aNodeKvp.Key);
-
- var aNodeValue = aNodeKvp.Value;
- var cNodeKvp = Assert.Single(aNodeValue.Literals);
- Assert.Equal("c", cNodeKvp.Key);
- var cNode = cNodeKvp.Value;
-
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
-
- var bNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bNodeKvp.Key);
- var bNode = bNodeKvp.Value;
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramCNodeKvp = Assert.Single(bNode.Literals);
-
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ComplexParameter_LiteralDoesNotMatchComplexParameter()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("a/c");
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("a{value}/b/c");
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
+
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("a", aNodeKvp.Key);
+
+ var aNodeValue = aNodeKvp.Value;
+ var cNodeKvp = Assert.Single(aNodeValue.Literals);
+ Assert.Equal("c", cNodeKvp.Key);
+ var cNode = cNodeKvp.Value;
+
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
+
+ var bNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bNodeKvp.Key);
+ var bNode = bNodeKvp.Value;
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramCNodeKvp = Assert.Single(bNode.Literals);
+
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ComplexParameter_LiteralMatchesComplexParameter()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ComplexParameter_LiteralMatchesComplexParameter()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("aa/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("aa/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a{value}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a{value}/b/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch aa -> c = (aa/c)
+ // Branch aa -> c = (aa/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("aa", aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("aa", aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
+ var aNodeValue = aNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch (aa) -> b -> c = (a{value}/b/c)
+ // Branch (aa) -> b -> c = (a{value}/b/c)
- Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramBCNodeKvp = Assert.Single(bNode.Literals);
- Assert.Equal("c", paramBCNodeKvp.Key);
- var paramBCNode = paramBCNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramBCNodeKvp = Assert.Single(bNode.Literals);
+ Assert.Equal("c", paramBCNodeKvp.Key);
+ var paramBCNode = paramBCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = (a{value}/b/c)
+ // Branch {param} -> b -> c = (a{value}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ConstrainedComplexParameter_LiteralMatchesComplexParameterButNotConstraint()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("aa/c");
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("a{value:int}/b/c");
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
-
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("aa", aNodeKvp.Key);
-
- var aNodeValue = aNodeKvp.Value;
- var cNodeKvp = Assert.Single(aNodeValue.Literals);
- Assert.Equal("c", cNodeKvp.Key);
- var cNode = cNodeKvp.Value;
-
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
-
- var bNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bNodeKvp.Key);
- var bNode = bNodeKvp.Value;
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramCNodeKvp = Assert.Single(bNode.Literals);
-
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ConstrainedComplexParameter_LiteralMatchesComplexParameterButNotConstraint()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("aa/c");
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("a{value:int}/b/c");
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
+
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("aa", aNodeKvp.Key);
+
+ var aNodeValue = aNodeKvp.Value;
+ var cNodeKvp = Assert.Single(aNodeValue.Literals);
+ Assert.Equal("c", cNodeKvp.Key);
+ var cNode = cNodeKvp.Value;
+
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
+
+ var bNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bNodeKvp.Key);
+ var bNode = bNodeKvp.Value;
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramCNodeKvp = Assert.Single(bNode.Literals);
+
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ComplexParameter_LiteralMatchesComplexParameterAndPartConstraint()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ComplexParameter_LiteralMatchesComplexParameterAndPartConstraint()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a1/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a1/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a{value:int}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a{value:int}/b/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch aa -> c = (a1/c)
+ // Branch aa -> c = (a1/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("a1", aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("a1", aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
+ var aNodeValue = aNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch (a1) -> b -> c = (a{value:int}/b/c)
+ // Branch (a1) -> b -> c = (a{value:int}/b/c)
- Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramBCNodeKvp = Assert.Single(bNode.Literals);
- Assert.Equal("c", paramBCNodeKvp.Key);
- var paramBCNode = paramBCNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramBCNodeKvp = Assert.Single(bNode.Literals);
+ Assert.Equal("c", paramBCNodeKvp.Key);
+ var paramBCNode = paramBCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = (a{value:int}/b/c)
+ // Branch {param} -> b -> c = (a{value:int}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ComplexParameter_EvaluatesAllPartsAndConstraints()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ComplexParameter_EvaluatesAllPartsAndConstraints()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("a-11-b-true/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("a-11-b-true/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a-{value:int:length(2)}-b-{other:bool}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a-{value:int:length(2)}-b-{other:bool}/b/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch a11-b-true -> c = (a11-b-true/c)
+ // Branch a11-b-true -> c = (a11-b-true/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("a-11-b-true", aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("a-11-b-true", aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
+ var aNodeValue = aNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch (a-11-b-true) -> b -> c = (a-{value:int:length(2)}-b-{other:bool}/b/c)
+ // Branch (a-11-b-true) -> b -> c = (a-{value:int:length(2)}-b-{other:bool}/b/c)
- Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramBCNodeKvp = Assert.Single(bNode.Literals);
- Assert.Equal("c", paramBCNodeKvp.Key);
- var paramBCNode = paramBCNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramBCNodeKvp = Assert.Single(bNode.Literals);
+ Assert.Equal("c", paramBCNodeKvp.Key);
+ var paramBCNode = paramBCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint2, Assert.Single(paramBCNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = (a-{value:int:length(2)}-b-{other:bool}/b/c)
+ // Branch {param} -> b -> c = (a-{value:int:length(2)}-b-{other:bool}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Theory]
- [InlineData("a-11-b-true/c", "a-11-b-true", "c")]
- [InlineData("a-ddd-b-true/c", "a-ddd-b-true", "c")]
- [InlineData("a-111-b-0/c", "a-111-b-0", "c")]
- public void BuildDfaTree_MultipleEndpoint_ComplexParameter_Trims_When_OneConstraintFails(string candidate, string firstSegment, string secondSegment)
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Theory]
+ [InlineData("a-11-b-true/c", "a-11-b-true", "c")]
+ [InlineData("a-ddd-b-true/c", "a-ddd-b-true", "c")]
+ [InlineData("a-111-b-0/c", "a-111-b-0", "c")]
+ public void BuildDfaTree_MultipleEndpoint_ComplexParameter_Trims_When_OneConstraintFails(string candidate, string firstSegment, string secondSegment)
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint(candidate);
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint(candidate);
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a-{value:int:length(3)}-b-{other:bool}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a-{value:int:length(3)}-b-{other:bool}/b/c");
+ builder.AddEndpoint(endpoint2);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch a-11-b-true -> c = (a11-b-true/c)
+ // Branch a-11-b-true -> c = (a11-b-true/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal(firstSegment, aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal(firstSegment, aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- var cNodeKvp = aNodeValue.Literals.Single();
- Assert.Equal(secondSegment, cNodeKvp.Key);
- var cNode = cNodeKvp.Value;
+ var aNodeValue = aNodeKvp.Value;
+ var cNodeKvp = aNodeValue.Literals.Single();
+ Assert.Equal(secondSegment, cNodeKvp.Key);
+ var cNode = cNodeKvp.Value;
- Assert.Same(endpoint1, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint1, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = (a-{value:int:length(2)}-b-{other:bool}/b/c)
+ // Branch {param} -> b -> c = (a-{value:int:length(2)}-b-{other:bool}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ComplexParameter_BothCandidates_WhenLitteralPatternMatchesComplexParameterAndRoutePattern()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ComplexParameter_BothCandidates_WhenLitteralPatternMatchesComplexParameterAndRoutePattern()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateEndpoint("aa/b/c");
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("aa/b/c");
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("a{value}/b/c");
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("a{value}/b/c");
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("aa/c");
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("aa/c");
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.NotNull(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.NotNull(root.Parameters);
- // Branch aa -> c = (aa/c)
+ // Branch aa -> c = (aa/c)
- var aNodeKvp = Assert.Single(root.Literals);
- Assert.Equal("aa", aNodeKvp.Key);
+ var aNodeKvp = Assert.Single(root.Literals);
+ Assert.Equal("aa", aNodeKvp.Key);
- var aNodeValue = aNodeKvp.Value;
- Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
+ var aNodeValue = aNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("c", out var cNode));
- Assert.Same(endpoint3, Assert.Single(cNode.Matches));
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Same(endpoint3, Assert.Single(cNode.Matches));
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch (aa) -> b -> c = (aa/b/c, a{value}/b/c)
+ // Branch (aa) -> b -> c = (aa/b/c, a{value}/b/c)
- Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
- Assert.Null(bNode.Parameters);
- Assert.Null(bNode.Matches);
- var paramBCNodeKvp = Assert.Single(bNode.Literals);
- Assert.Equal("c", paramBCNodeKvp.Key);
- var paramBCNode = paramBCNodeKvp.Value;
+ Assert.True(aNodeValue.Literals.TryGetValue("b", out var bNode));
+ Assert.Null(bNode.Parameters);
+ Assert.Null(bNode.Matches);
+ var paramBCNodeKvp = Assert.Single(bNode.Literals);
+ Assert.Equal("c", paramBCNodeKvp.Key);
+ var paramBCNode = paramBCNodeKvp.Value;
- Assert.Equal(new[] { endpoint1, endpoint2 }, paramBCNode.Matches.ToArray());
- Assert.Null(cNode.Literals);
- Assert.Null(cNode.Parameters);
+ Assert.Equal(new[] { endpoint1, endpoint2 }, paramBCNode.Matches.ToArray());
+ Assert.Null(cNode.Literals);
+ Assert.Null(cNode.Parameters);
- // Branch {param} -> b -> c = (a{value}/b/c)
+ // Branch {param} -> b -> c = (a{value}/b/c)
- var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
- Assert.Equal("b", bParamNodeKvp.Key);
- var bParamNode = bParamNodeKvp.Value;
- Assert.Null(bParamNode.Parameters);
- Assert.Null(bParamNode.Matches);
- var paramCNodeKvp = Assert.Single(bParamNode.Literals);
+ var bParamNodeKvp = Assert.Single(root.Parameters.Literals);
+ Assert.Equal("b", bParamNodeKvp.Key);
+ var bParamNode = bParamNodeKvp.Value;
+ Assert.Null(bParamNode.Parameters);
+ Assert.Null(bParamNode.Matches);
+ var paramCNodeKvp = Assert.Single(bParamNode.Literals);
- Assert.Equal("c", paramCNodeKvp.Key);
- var paramCNode = paramCNodeKvp.Value;
- Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
- Assert.Null(paramCNode.Literals);
- Assert.Null(paramCNode.Parameters);
- }
+ Assert.Equal("c", paramCNodeKvp.Key);
+ var paramCNode = paramCNodeKvp.Value;
+ Assert.Same(endpoint2, Assert.Single(paramCNode.Matches));
+ Assert.Null(paramCNode.Literals);
+ Assert.Null(paramCNode.Parameters);
+ }
- // Regression test for excessive memory usage https://github.com/dotnet/aspnetcore/issues/23850
- [Fact]
- public void BuildDfaTree_CanHandle_LargeAmountOfRoutes_WithConstraints()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ // Regression test for excessive memory usage https://github.com/dotnet/aspnetcore/issues/23850
+ [Fact]
+ public void BuildDfaTree_CanHandle_LargeAmountOfRoutes_WithConstraints()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoints = new[]{
+ var endpoints = new[]{
CreateEndpoint("test1/method-1", new HttpMethodMetadata(new[] { "GET" })),
CreateEndpoint("{language:length(2)}/test1/method-1", new HttpMethodMetadata(new[] { "GET" })),
CreateEndpoint("{version:int}/{language:length(2)}/test1/method-1", new HttpMethodMetadata(new[] { "GET" })),
@@ -1636,32 +1636,32 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("{version:int}/{language:length(2)}/test63/method-3", new HttpMethodMetadata(new[] { "POST" }))
};
- foreach (var endpoint in endpoints)
- {
- builder.AddEndpoint(endpoint);
- }
+ foreach (var endpoint in endpoints)
+ {
+ builder.AddEndpoint(endpoint);
+ }
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.NotNull(root);
- var count = 0;
- root.Visit(node => count++);
+ // Assert
+ Assert.NotNull(root);
+ var count = 0;
+ root.Visit(node => count++);
- // Without filtering it would have resulted in millions of nodes, several GB of memory and minutes
- Assert.Equal(759, count);
- }
+ // Without filtering it would have resulted in millions of nodes, several GB of memory and minutes
+ Assert.Equal(759, count);
+ }
- // Regression test for excessive memory usage https://github.com/dotnet/aspnetcore/issues/33735
- [Fact]
- public void BuildDfaTree_Regression_33735()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ // Regression test for excessive memory usage https://github.com/dotnet/aspnetcore/issues/33735
+ [Fact]
+ public void BuildDfaTree_Regression_33735()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoints = new[]
- {
+ var endpoints = new[]
+ {
CreateEndpoint("api/{baseSpaceId:regex(Spaces-\\d\u002B)}/workers/{id}", new HttpMethodMetadata(new[] { "DELETE" })),
CreateEndpoint("api/workers/{id}", new HttpMethodMetadata(new[] { "DELETE" })),
CreateEndpoint("api/{baseSpaceId:regex(Spaces-\\d\u002B)}/workers/all", new HttpMethodMetadata(new[] { "GET" })),
@@ -2156,31 +2156,31 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("api/diagnostics/throw", new HttpMethodMetadata(new[] { "GET" })),
};
- foreach (var endpoint in endpoints)
- {
- builder.AddEndpoint(endpoint);
- }
+ foreach (var endpoint in endpoints)
+ {
+ builder.AddEndpoint(endpoint);
+ }
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.NotNull(root);
- var count = 0;
- root.Visit(node => count++);
+ // Assert
+ Assert.NotNull(root);
+ var count = 0;
+ root.Visit(node => count++);
- // Without filtering it would have resulted in several order of magnitudes more nodes and much more memory
- Assert.Equal(2964, count);
- }
+ // Without filtering it would have resulted in several order of magnitudes more nodes and much more memory
+ Assert.Equal(2964, count);
+ }
- // Another regression test based on OData models
- [Fact]
- public void BuildDfaTree_CanHandle_LargeAmountOfRoutes_WithComplexParameters()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ // Another regression test based on OData models
+ [Fact]
+ public void BuildDfaTree_CanHandle_LargeAmountOfRoutes_WithComplexParameters()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoints = new[]{
+ var endpoints = new[]{
CreateEndpoint("Student", new HttpMethodMetadata(new[] { "GET" })),
CreateEndpoint("{contextToken}/Student", new HttpMethodMetadata(new[] { "GET" })),
CreateEndpoint("{contextToken}/Student/$count", new HttpMethodMetadata(new[] { "GET" })),
@@ -2374,1039 +2374,1039 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("{contextToken}", new HttpMethodMetadata(new[] { "GET" })),
};
- foreach (var endpoint in endpoints)
- {
- builder.AddEndpoint(endpoint);
- }
+ foreach (var endpoint in endpoints)
+ {
+ builder.AddEndpoint(endpoint);
+ }
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.NotNull(root);
- var count = 0;
- root.Visit(node => count++);
+ // Assert
+ Assert.NotNull(root);
+ var count = 0;
+ root.Visit(node => count++);
- // Without filtering it would have resulted in several order of magnitudes more nodes and much more memory
- Assert.Equal(1453, count);
- }
+ // Without filtering it would have resulted in several order of magnitudes more nodes and much more memory
+ Assert.Equal(1453, count);
+ }
- // Regression test for https://github.com/dotnet/aspnetcore/issues/16579
- //
- // This case behaves the same for all combinations.
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll_OnSameNode_Order1()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("a/{b}", order: 0);
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("a/{*b}", order: 1);
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a = next.Value;
- Assert.Same(endpoint2, Assert.Single(a.Matches));
- Assert.Null(a.Literals);
-
- var b = a.Parameters;
- Assert.Collection(
- b.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(b.Literals);
- Assert.Null(b.Parameters);
- Assert.NotNull(b.CatchAll);
-
- var catchAll = b.CatchAll;
- Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
- Assert.Null(catchAll.Literals);
- Assert.Same(catchAll, catchAll.Parameters);
- Assert.Same(catchAll, catchAll.CatchAll);
- }
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/16579
+ //
+ // This case behaves the same for all combinations.
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll_OnSameNode_Order1()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("a/{b}", order: 0);
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("a/{*b}", order: 1);
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a = next.Value;
+ Assert.Same(endpoint2, Assert.Single(a.Matches));
+ Assert.Null(a.Literals);
+
+ var b = a.Parameters;
+ Assert.Collection(
+ b.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(b.Literals);
+ Assert.Null(b.Parameters);
+ Assert.NotNull(b.CatchAll);
+
+ var catchAll = b.CatchAll;
+ Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
+ Assert.Null(catchAll.Literals);
+ Assert.Same(catchAll, catchAll.Parameters);
+ Assert.Same(catchAll, catchAll.CatchAll);
+ }
- // Regression test for https://github.com/dotnet/aspnetcore/issues/16579
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll_OnSameNode_Order2()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("a/{*b}", order: 0);
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("a/{b}", order: 1);
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a = next.Value;
- Assert.Same(endpoint1, Assert.Single(a.Matches));
- Assert.Null(a.Literals);
-
- var b = a.Parameters;
- Assert.Collection(
- b.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(b.Literals);
- Assert.Null(b.Parameters);
- Assert.NotNull(b.CatchAll);
-
- var catchAll = b.CatchAll;
- Assert.Same(endpoint1, Assert.Single(catchAll.Matches));
- Assert.Null(catchAll.Literals);
- Assert.Same(catchAll, catchAll.Parameters);
- Assert.Same(catchAll, catchAll.CatchAll);
- }
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/16579
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll_OnSameNode_Order2()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("a/{*b}", order: 0);
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("a/{b}", order: 1);
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a = next.Value;
+ Assert.Same(endpoint1, Assert.Single(a.Matches));
+ Assert.Null(a.Literals);
+
+ var b = a.Parameters;
+ Assert.Collection(
+ b.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(b.Literals);
+ Assert.Null(b.Parameters);
+ Assert.NotNull(b.CatchAll);
+
+ var catchAll = b.CatchAll;
+ Assert.Same(endpoint1, Assert.Single(catchAll.Matches));
+ Assert.Null(catchAll.Literals);
+ Assert.Same(catchAll, catchAll.Parameters);
+ Assert.Same(catchAll, catchAll.CatchAll);
+ }
- // Regression test for https://github.com/dotnet/aspnetcore/issues/18677
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_CatchAllWithHigherPrecedenceThanParameter_Order1()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("{a}/{b}", order: 0);
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("a/{*b}", order: 1);
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a1 = next.Value;
- Assert.Same(endpoint2, Assert.Single(a1.Matches));
- Assert.Null(a1.Literals);
-
- var b1 = a1.Parameters;
- Assert.Collection(
- b1.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(b1.Literals);
- Assert.Null(b1.Parameters);
- Assert.NotNull(b1.CatchAll);
-
- var catchAll1 = b1.CatchAll;
- Assert.Same(endpoint2, Assert.Single(catchAll1.Matches));
- Assert.Null(catchAll1.Literals);
- Assert.Same(catchAll1, catchAll1.Parameters);
- Assert.Same(catchAll1, catchAll1.CatchAll);
-
- var a2 = root.Parameters;
- Assert.Null(a2.Matches);
- Assert.Null(a2.Literals);
-
- var b2 = a2.Parameters;
- Assert.Collection(
- b2.Matches,
- e => Assert.Same(endpoint1, e));
- Assert.Null(b2.Literals);
- Assert.Null(b2.Parameters);
- Assert.Null(b2.CatchAll);
- }
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/18677
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_CatchAllWithHigherPrecedenceThanParameter_Order1()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("{a}/{b}", order: 0);
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("a/{*b}", order: 1);
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a1 = next.Value;
+ Assert.Same(endpoint2, Assert.Single(a1.Matches));
+ Assert.Null(a1.Literals);
+
+ var b1 = a1.Parameters;
+ Assert.Collection(
+ b1.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(b1.Literals);
+ Assert.Null(b1.Parameters);
+ Assert.NotNull(b1.CatchAll);
+
+ var catchAll1 = b1.CatchAll;
+ Assert.Same(endpoint2, Assert.Single(catchAll1.Matches));
+ Assert.Null(catchAll1.Literals);
+ Assert.Same(catchAll1, catchAll1.Parameters);
+ Assert.Same(catchAll1, catchAll1.CatchAll);
+
+ var a2 = root.Parameters;
+ Assert.Null(a2.Matches);
+ Assert.Null(a2.Literals);
+
+ var b2 = a2.Parameters;
+ Assert.Collection(
+ b2.Matches,
+ e => Assert.Same(endpoint1, e));
+ Assert.Null(b2.Literals);
+ Assert.Null(b2.Parameters);
+ Assert.Null(b2.CatchAll);
+ }
- // Regression test for https://github.com/dotnet/aspnetcore/issues/18677
- [Fact]
- public void BuildDfaTree_MultipleEndpoint_CatchAllWithHigherPrecedenceThanParameter_Order2()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
-
- var endpoint1 = CreateEndpoint("a/{*b}", order: 0);
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("{a}/{b}", order: 1);
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a1 = next.Value;
- Assert.Same(endpoint1, Assert.Single(a1.Matches));
- Assert.Null(a1.Literals);
-
- var b1 = a1.Parameters;
- Assert.Collection(
- b1.Matches,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- Assert.Null(b1.Literals);
- Assert.Null(b1.Parameters);
- Assert.NotNull(b1.CatchAll);
-
- var catchAll1 = b1.CatchAll;
- Assert.Same(endpoint1, Assert.Single(catchAll1.Matches));
- Assert.Null(catchAll1.Literals);
- Assert.Same(catchAll1, catchAll1.Parameters);
- Assert.Same(catchAll1, catchAll1.CatchAll);
-
- var a2 = root.Parameters;
- Assert.Null(a2.Matches);
- Assert.Null(a2.Literals);
-
- var b2 = a2.Parameters;
- Assert.Collection(
- b2.Matches,
- e => Assert.Same(endpoint2, e));
- Assert.Null(b2.Literals);
- Assert.Null(b2.Parameters);
- Assert.Null(b2.CatchAll);
- }
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/18677
+ [Fact]
+ public void BuildDfaTree_MultipleEndpoint_CatchAllWithHigherPrecedenceThanParameter_Order2()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
+
+ var endpoint1 = CreateEndpoint("a/{*b}", order: 0);
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("{a}/{b}", order: 1);
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a1 = next.Value;
+ Assert.Same(endpoint1, Assert.Single(a1.Matches));
+ Assert.Null(a1.Literals);
+
+ var b1 = a1.Parameters;
+ Assert.Collection(
+ b1.Matches,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(b1.Literals);
+ Assert.Null(b1.Parameters);
+ Assert.NotNull(b1.CatchAll);
+
+ var catchAll1 = b1.CatchAll;
+ Assert.Same(endpoint1, Assert.Single(catchAll1.Matches));
+ Assert.Null(catchAll1.Literals);
+ Assert.Same(catchAll1, catchAll1.Parameters);
+ Assert.Same(catchAll1, catchAll1.CatchAll);
+
+ var a2 = root.Parameters;
+ Assert.Null(a2.Matches);
+ Assert.Null(a2.Literals);
+
+ var b2 = a2.Parameters;
+ Assert.Collection(
+ b2.Matches,
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(b2.Literals);
+ Assert.Null(b2.Parameters);
+ Assert.Null(b2.CatchAll);
+ }
- private void BuildDfaTree_MultipleEndpoint_CatchAllWithHigherPrecedenceThanParameter_Order2_Legacy30Behavior_Core(DfaMatcherBuilder builder)
- {
- // Arrange
- var endpoint1 = CreateEndpoint("a/{*b}", order: 0);
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("{a}/{b}", order: 1);
- builder.AddEndpoint(endpoint2);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a1 = next.Value;
- Assert.Same(endpoint1, Assert.Single(a1.Matches));
- Assert.Null(a1.Literals);
-
- var b1 = a1.Parameters;
- Assert.Same(endpoint2, Assert.Single(b1.Matches));
- Assert.Null(b1.Literals);
- Assert.Null(b1.Parameters);
- Assert.Null(b1.CatchAll);
-
- var a2 = root.Parameters;
- Assert.Null(a2.Matches);
- Assert.Null(a2.Literals);
-
- var b2 = a2.Parameters;
- Assert.Collection(
- b2.Matches,
- e => Assert.Same(endpoint2, e));
- Assert.Null(b2.Literals);
- Assert.Null(b2.Parameters);
- Assert.Null(b2.CatchAll);
- }
+ private void BuildDfaTree_MultipleEndpoint_CatchAllWithHigherPrecedenceThanParameter_Order2_Legacy30Behavior_Core(DfaMatcherBuilder builder)
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("a/{*b}", order: 0);
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("{a}/{b}", order: 1);
+ builder.AddEndpoint(endpoint2);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a1 = next.Value;
+ Assert.Same(endpoint1, Assert.Single(a1.Matches));
+ Assert.Null(a1.Literals);
+
+ var b1 = a1.Parameters;
+ Assert.Same(endpoint2, Assert.Single(b1.Matches));
+ Assert.Null(b1.Literals);
+ Assert.Null(b1.Parameters);
+ Assert.Null(b1.CatchAll);
+
+ var a2 = root.Parameters;
+ Assert.Null(a2.Matches);
+ Assert.Null(a2.Literals);
+
+ var b2 = a2.Parameters;
+ Assert.Collection(
+ b2.Matches,
+ e => Assert.Same(endpoint2, e));
+ Assert.Null(b2.Literals);
+ Assert.Null(b2.Parameters);
+ Assert.Null(b2.CatchAll);
+ }
- [Fact]
- public void BuildDfaTree_WithPolicies()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
-
- var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
- builder.AddEndpoint(endpoint1);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a = next.Value;
- Assert.Empty(a.Matches);
- Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
- Assert.Collection(
- a.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(0, e.Key));
-
- var test1_0 = a.PolicyEdges[0];
- Assert.Empty(a.Matches);
- Assert.IsType<TestMetadata2MatcherPolicy>(test1_0.NodeBuilder);
- Assert.Collection(
- test1_0.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(true, e.Key));
-
- var test2_true = test1_0.PolicyEdges[true];
- Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
- Assert.Null(test2_true.NodeBuilder);
- Assert.Null(test2_true.PolicyEdges);
- }
+ [Fact]
+ public void BuildDfaTree_WithPolicies()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
+
+ var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint1);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a = next.Value;
+ Assert.Empty(a.Matches);
+ Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
+ Assert.Collection(
+ a.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(0, e.Key));
+
+ var test1_0 = a.PolicyEdges[0];
+ Assert.Empty(a.Matches);
+ Assert.IsType<TestMetadata2MatcherPolicy>(test1_0.NodeBuilder);
+ Assert.Collection(
+ test1_0.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(true, e.Key));
+
+ var test2_true = test1_0.PolicyEdges[true];
+ Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
+ Assert.Null(test2_true.NodeBuilder);
+ Assert.Null(test2_true.PolicyEdges);
+ }
- [Fact]
- public void BuildDfaTree_WithPolicies_AndBranches()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
-
- var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
- builder.AddEndpoint(endpoint1);
-
- var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), new TestMetadata2(true), });
- builder.AddEndpoint(endpoint2);
-
- var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), new TestMetadata2(false), });
- builder.AddEndpoint(endpoint3);
-
- // Act
- var root = builder.BuildDfaTree();
-
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
-
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
-
- var a = next.Value;
- Assert.Empty(a.Matches);
- Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
- Assert.Collection(
- a.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(0, e.Key),
- e => Assert.Equal(1, e.Key));
-
- var test1_0 = a.PolicyEdges[0];
- Assert.Empty(test1_0.Matches);
- Assert.IsType<TestMetadata2MatcherPolicy>(test1_0.NodeBuilder);
- Assert.Collection(
- test1_0.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(true, e.Key));
-
- var test2_true = test1_0.PolicyEdges[true];
- Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
- Assert.Null(test2_true.NodeBuilder);
- Assert.Null(test2_true.PolicyEdges);
-
- var test1_1 = a.PolicyEdges[1];
- Assert.Empty(test1_1.Matches);
- Assert.IsType<TestMetadata2MatcherPolicy>(test1_1.NodeBuilder);
- Assert.Collection(
- test1_1.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(false, e.Key),
- e => Assert.Equal(true, e.Key));
-
- test2_true = test1_1.PolicyEdges[true];
- Assert.Same(endpoint2, Assert.Single(test2_true.Matches));
- Assert.Null(test2_true.NodeBuilder);
- Assert.Null(test2_true.PolicyEdges);
-
- var test2_false = test1_1.PolicyEdges[false];
- Assert.Same(endpoint3, Assert.Single(test2_false.Matches));
- Assert.Null(test2_false.NodeBuilder);
- Assert.Null(test2_false.PolicyEdges);
- }
+ [Fact]
+ public void BuildDfaTree_WithPolicies_AndBranches()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
+
+ var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint1);
+
+ var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint2);
+
+ var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), new TestMetadata2(false), });
+ builder.AddEndpoint(endpoint3);
+
+ // Act
+ var root = builder.BuildDfaTree();
+
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
+
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
+
+ var a = next.Value;
+ Assert.Empty(a.Matches);
+ Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
+ Assert.Collection(
+ a.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(0, e.Key),
+ e => Assert.Equal(1, e.Key));
+
+ var test1_0 = a.PolicyEdges[0];
+ Assert.Empty(test1_0.Matches);
+ Assert.IsType<TestMetadata2MatcherPolicy>(test1_0.NodeBuilder);
+ Assert.Collection(
+ test1_0.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(true, e.Key));
+
+ var test2_true = test1_0.PolicyEdges[true];
+ Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
+ Assert.Null(test2_true.NodeBuilder);
+ Assert.Null(test2_true.PolicyEdges);
+
+ var test1_1 = a.PolicyEdges[1];
+ Assert.Empty(test1_1.Matches);
+ Assert.IsType<TestMetadata2MatcherPolicy>(test1_1.NodeBuilder);
+ Assert.Collection(
+ test1_1.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(false, e.Key),
+ e => Assert.Equal(true, e.Key));
+
+ test2_true = test1_1.PolicyEdges[true];
+ Assert.Same(endpoint2, Assert.Single(test2_true.Matches));
+ Assert.Null(test2_true.NodeBuilder);
+ Assert.Null(test2_true.PolicyEdges);
+
+ var test2_false = test1_1.PolicyEdges[false];
+ Assert.Same(endpoint3, Assert.Single(test2_false.Matches));
+ Assert.Null(test2_false.NodeBuilder);
+ Assert.Null(test2_false.PolicyEdges);
+ }
- [Fact]
- public void BuildDfaTree_WithPolicies_AndBranches_FirstPolicySkipped()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
+ [Fact]
+ public void BuildDfaTree_WithPolicies_AndBranches_FirstPolicySkipped()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
- var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(true), });
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(true), });
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(false), });
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(false), });
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Empty(a.Matches);
- Assert.IsType<TestMetadata2MatcherPolicy>(a.NodeBuilder);
- Assert.Collection(
- a.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(false, e.Key),
- e => Assert.Equal(true, e.Key));
+ var a = next.Value;
+ Assert.Empty(a.Matches);
+ Assert.IsType<TestMetadata2MatcherPolicy>(a.NodeBuilder);
+ Assert.Collection(
+ a.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(false, e.Key),
+ e => Assert.Equal(true, e.Key));
- var test2_true = a.PolicyEdges[true];
- Assert.Equal(new[] { endpoint1, endpoint2, }, test2_true.Matches);
- Assert.Null(test2_true.NodeBuilder);
- Assert.Null(test2_true.PolicyEdges);
+ var test2_true = a.PolicyEdges[true];
+ Assert.Equal(new[] { endpoint1, endpoint2, }, test2_true.Matches);
+ Assert.Null(test2_true.NodeBuilder);
+ Assert.Null(test2_true.PolicyEdges);
- var test2_false = a.PolicyEdges[false];
- Assert.Equal(new[] { endpoint3, }, test2_false.Matches);
- Assert.Null(test2_false.NodeBuilder);
- Assert.Null(test2_false.PolicyEdges);
- }
+ var test2_false = a.PolicyEdges[false];
+ Assert.Equal(new[] { endpoint3, }, test2_false.Matches);
+ Assert.Null(test2_false.NodeBuilder);
+ Assert.Null(test2_false.PolicyEdges);
+ }
- [Fact]
- public void BuildDfaTree_WithPolicies_AndBranches_SecondSkipped()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
+ [Fact]
+ public void BuildDfaTree_WithPolicies_AndBranches_SecondSkipped()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
- var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Empty(a.Matches);
- Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
- Assert.Collection(
- a.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(0, e.Key),
- e => Assert.Equal(1, e.Key));
+ var a = next.Value;
+ Assert.Empty(a.Matches);
+ Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
+ Assert.Collection(
+ a.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(0, e.Key),
+ e => Assert.Equal(1, e.Key));
- var test1_0 = a.PolicyEdges[0];
- Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
- Assert.Null(test1_0.NodeBuilder);
- Assert.Null(test1_0.PolicyEdges);
+ var test1_0 = a.PolicyEdges[0];
+ Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
+ Assert.Null(test1_0.NodeBuilder);
+ Assert.Null(test1_0.PolicyEdges);
- var test1_1 = a.PolicyEdges[1];
- Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
- Assert.Null(test1_1.NodeBuilder);
- Assert.Null(test1_1.PolicyEdges);
- }
+ var test1_1 = a.PolicyEdges[1];
+ Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
+ Assert.Null(test1_1.NodeBuilder);
+ Assert.Null(test1_1.PolicyEdges);
+ }
- [Fact]
- public void BuildDfaTree_WithPolicies_AndBranches_NonRouteEndpoint()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder(new TestNonRoutePatternMatcherPolicy());
+ [Fact]
+ public void BuildDfaTree_WithPolicies_AndBranches_NonRouteEndpoint()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder(new TestNonRoutePatternMatcherPolicy());
- var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Empty(a.Matches);
- Assert.IsType<TestNonRoutePatternMatcherPolicy>(a.NodeBuilder);
- Assert.Collection(
- a.PolicyEdges.OrderBy(e => e.Key),
- e => Assert.Equal(0, e.Key),
- e => Assert.Equal(1, e.Key),
- e => Assert.Equal(int.MaxValue, e.Key));
+ var a = next.Value;
+ Assert.Empty(a.Matches);
+ Assert.IsType<TestNonRoutePatternMatcherPolicy>(a.NodeBuilder);
+ Assert.Collection(
+ a.PolicyEdges.OrderBy(e => e.Key),
+ e => Assert.Equal(0, e.Key),
+ e => Assert.Equal(1, e.Key),
+ e => Assert.Equal(int.MaxValue, e.Key));
- var test1_0 = a.PolicyEdges[0];
- Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
- Assert.Null(test1_0.NodeBuilder);
- Assert.Null(test1_0.PolicyEdges);
+ var test1_0 = a.PolicyEdges[0];
+ Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
+ Assert.Null(test1_0.NodeBuilder);
+ Assert.Null(test1_0.PolicyEdges);
- var test1_1 = a.PolicyEdges[1];
- Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
- Assert.Null(test1_1.NodeBuilder);
- Assert.Null(test1_1.PolicyEdges);
+ var test1_1 = a.PolicyEdges[1];
+ Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
+ Assert.Null(test1_1.NodeBuilder);
+ Assert.Null(test1_1.PolicyEdges);
- var nonRouteEndpoint = a.PolicyEdges[int.MaxValue];
- Assert.Equal("MaxValueEndpoint", Assert.Single(nonRouteEndpoint.Matches).DisplayName);
- }
+ var nonRouteEndpoint = a.PolicyEdges[int.MaxValue];
+ Assert.Equal("MaxValueEndpoint", Assert.Single(nonRouteEndpoint.Matches).DisplayName);
+ }
- [Fact]
- public void BuildDfaTree_WithPolicies_AndBranches_BothPoliciesSkipped()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
+ [Fact]
+ public void BuildDfaTree_WithPolicies_AndBranches_BothPoliciesSkipped()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
- var endpoint1 = CreateEndpoint("/a", metadata: new object[] { });
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("/a", metadata: new object[] { });
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("/a", metadata: new object[] { });
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("/a", metadata: new object[] { });
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("/a", metadata: new object[] { });
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("/a", metadata: new object[] { });
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("a", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("a", next.Key);
- var a = next.Value;
- Assert.Equal(new[] { endpoint1, endpoint2, endpoint3, }, a.Matches);
- Assert.Null(a.NodeBuilder);
- Assert.Null(a.PolicyEdges);
- }
+ var a = next.Value;
+ Assert.Equal(new[] { endpoint1, endpoint2, endpoint3, }, a.Matches);
+ Assert.Null(a.NodeBuilder);
+ Assert.Null(a.PolicyEdges);
+ }
- // Verifies that we sort the endpoints before calling into policies.
+ // Verifies that we sort the endpoints before calling into policies.
+ //
+ // The builder uses a different sort order when building the tree, vs when building the policy nodes. Policy
+ // nodes should see an "absolute" order.
+ [Fact]
+ public void BuildDfaTree_WithPolicies_SortedAccordingToScore()
+ {
+ // Arrange
//
- // The builder uses a different sort order when building the tree, vs when building the policy nodes. Policy
- // nodes should see an "absolute" order.
- [Fact]
- public void BuildDfaTree_WithPolicies_SortedAccordingToScore()
+ // These cases where chosen where the absolute order incontrolled explicitly by setting .Order, but
+ // the precedence of segments is different, so these will be sorted into different orders when building
+ // the tree.
+ var policies = new MatcherPolicy[]
{
- // Arrange
- //
- // These cases where chosen where the absolute order incontrolled explicitly by setting .Order, but
- // the precedence of segments is different, so these will be sorted into different orders when building
- // the tree.
- var policies = new MatcherPolicy[]
- {
new TestMetadata1MatcherPolicy(),
new TestMetadata2MatcherPolicy(),
- };
+ };
- var comparer = new EndpointComparer(policies.OrderBy(p => p.Order).OfType<IEndpointComparerPolicy>().ToArray());
+ var comparer = new EndpointComparer(policies.OrderBy(p => p.Order).OfType<IEndpointComparerPolicy>().ToArray());
- var builder = CreateDfaMatcherBuilder(policies);
+ var builder = CreateDfaMatcherBuilder(policies);
- ((TestMetadata1MatcherPolicy)policies[0]).OnGetEdges = VerifyOrder;
- ((TestMetadata2MatcherPolicy)policies[1]).OnGetEdges = VerifyOrder;
+ ((TestMetadata1MatcherPolicy)policies[0]).OnGetEdges = VerifyOrder;
+ ((TestMetadata2MatcherPolicy)policies[1]).OnGetEdges = VerifyOrder;
- var endpoint1 = CreateEndpoint("/a/{**b}", order: -1, metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateEndpoint("/a/{**b}", order: -1, metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateEndpoint("/a/{b}/{**c}", order: 0, metadata: new object[] { new TestMetadata1(1), new TestMetadata2(true), });
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateEndpoint("/a/{b}/{**c}", order: 0, metadata: new object[] { new TestMetadata1(1), new TestMetadata2(true), });
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateEndpoint("/a/b/{c}", order: 1, metadata: new object[] { new TestMetadata1(1), new TestMetadata2(false), });
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateEndpoint("/a/b/{c}", order: 1, metadata: new object[] { new TestMetadata1(1), new TestMetadata2(false), });
+ builder.AddEndpoint(endpoint3);
- // Act & Assert
- _ = builder.BuildDfaTree();
+ // Act & Assert
+ _ = builder.BuildDfaTree();
- void VerifyOrder(IReadOnlyList<Endpoint> endpoints)
- {
- // The list should already be in sorted order, every time build is called.
- Assert.Equal(endpoints, endpoints.OrderBy(e => e, comparer));
- }
+ void VerifyOrder(IReadOnlyList<Endpoint> endpoints)
+ {
+ // The list should already be in sorted order, every time build is called.
+ Assert.Equal(endpoints, endpoints.OrderBy(e => e, comparer));
}
+ }
- [Fact]
- public void BuildDfaTree_RequiredValues()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_RequiredValues()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint("{controller}/{action}", requiredValues: new { controller = "Home", action = "Index" });
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint("{controller}/{action}", requiredValues: new { controller = "Home", action = "Index" });
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("Home", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("Home", next.Key);
- var home = next.Value;
- Assert.Null(home.Matches);
- Assert.Null(home.Parameters);
+ var home = next.Value;
+ Assert.Null(home.Matches);
+ Assert.Null(home.Parameters);
- next = Assert.Single(home.Literals);
- Assert.Equal("Index", next.Key);
+ next = Assert.Single(home.Literals);
+ Assert.Equal("Index", next.Key);
- var index = next.Value;
- Assert.Same(endpoint, Assert.Single(index.Matches));
- Assert.Null(index.Literals);
- }
+ var index = next.Value;
+ Assert.Same(endpoint, Assert.Single(index.Matches));
+ Assert.Null(index.Literals);
+ }
- [Fact]
- public void BuildDfaTree_RequiredValues_AndMatchingDefaults()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_RequiredValues_AndMatchingDefaults()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint(
- "{controller}/{action}",
- defaults: new { controller = "Home", action = "Index" },
- requiredValues: new { controller = "Home", action = "Index" });
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint(
+ "{controller}/{action}",
+ defaults: new { controller = "Home", action = "Index" },
+ requiredValues: new { controller = "Home", action = "Index" });
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Same(endpoint, Assert.Single(root.Matches));
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Same(endpoint, Assert.Single(root.Matches));
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("Home", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("Home", next.Key);
- var home = next.Value;
- Assert.Same(endpoint, Assert.Single(home.Matches));
- Assert.Null(home.Parameters);
+ var home = next.Value;
+ Assert.Same(endpoint, Assert.Single(home.Matches));
+ Assert.Null(home.Parameters);
- next = Assert.Single(home.Literals);
- Assert.Equal("Index", next.Key);
+ next = Assert.Single(home.Literals);
+ Assert.Equal("Index", next.Key);
- var index = next.Value;
- Assert.Same(endpoint, Assert.Single(index.Matches));
- Assert.Null(index.Literals);
- }
+ var index = next.Value;
+ Assert.Same(endpoint, Assert.Single(index.Matches));
+ Assert.Null(index.Literals);
+ }
- [Fact]
- public void BuildDfaTree_RequiredValues_AndDifferentDefaults()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_RequiredValues_AndDifferentDefaults()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateSubsitutedEndpoint(
- "{controller}/{action}",
- defaults: new { controller = "Home", action = "Index" },
- requiredValues: new { controller = "Login", action = "Index" });
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateSubsitutedEndpoint(
+ "{controller}/{action}",
+ defaults: new { controller = "Home", action = "Index" },
+ requiredValues: new { controller = "Login", action = "Index" });
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("Login", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("Login", next.Key);
- var login = next.Value;
- Assert.Same(endpoint, Assert.Single(login.Matches));
- Assert.Null(login.Parameters);
+ var login = next.Value;
+ Assert.Same(endpoint, Assert.Single(login.Matches));
+ Assert.Null(login.Parameters);
- next = Assert.Single(login.Literals);
- Assert.Equal("Index", next.Key);
+ next = Assert.Single(login.Literals);
+ Assert.Equal("Index", next.Key);
- var index = next.Value;
- Assert.Same(endpoint, Assert.Single(index.Matches));
- Assert.Null(index.Literals);
- }
+ var index = next.Value;
+ Assert.Same(endpoint, Assert.Single(index.Matches));
+ Assert.Null(index.Literals);
+ }
- [Fact]
- public void BuildDfaTree_RequiredValues_Multiple()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_RequiredValues_Multiple()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint1 = CreateSubsitutedEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index" },
- requiredValues: new { controller = "Home", action = "Index" });
- builder.AddEndpoint(endpoint1);
+ var endpoint1 = CreateSubsitutedEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index" },
+ requiredValues: new { controller = "Home", action = "Index" });
+ builder.AddEndpoint(endpoint1);
- var endpoint2 = CreateSubsitutedEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index" },
- requiredValues: new { controller = "Login", action = "Index" });
- builder.AddEndpoint(endpoint2);
+ var endpoint2 = CreateSubsitutedEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index" },
+ requiredValues: new { controller = "Login", action = "Index" });
+ builder.AddEndpoint(endpoint2);
- var endpoint3 = CreateSubsitutedEndpoint(
- "{controller}/{action}/{id?}",
- defaults: new { controller = "Home", action = "Index" },
- requiredValues: new { controller = "Login", action = "ChangePassword" });
- builder.AddEndpoint(endpoint3);
+ var endpoint3 = CreateSubsitutedEndpoint(
+ "{controller}/{action}/{id?}",
+ defaults: new { controller = "Home", action = "Index" },
+ requiredValues: new { controller = "Login", action = "ChangePassword" });
+ builder.AddEndpoint(endpoint3);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Same(endpoint1, Assert.Single(root.Matches));
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Same(endpoint1, Assert.Single(root.Matches));
+ Assert.Null(root.Parameters);
- Assert.Equal(2, root.Literals.Count);
+ Assert.Equal(2, root.Literals.Count);
- var home = root.Literals["Home"];
+ var home = root.Literals["Home"];
- Assert.Same(endpoint1, Assert.Single(home.Matches));
- Assert.Null(home.Parameters);
+ Assert.Same(endpoint1, Assert.Single(home.Matches));
+ Assert.Null(home.Parameters);
- var next = Assert.Single(home.Literals);
- Assert.Equal("Index", next.Key);
+ var next = Assert.Single(home.Literals);
+ Assert.Equal("Index", next.Key);
- var homeIndex = next.Value;
- Assert.Same(endpoint1, Assert.Single(homeIndex.Matches));
- Assert.Null(homeIndex.Literals);
- Assert.NotNull(homeIndex.Parameters);
+ var homeIndex = next.Value;
+ Assert.Same(endpoint1, Assert.Single(homeIndex.Matches));
+ Assert.Null(homeIndex.Literals);
+ Assert.NotNull(homeIndex.Parameters);
- Assert.Same(endpoint1, Assert.Single(homeIndex.Parameters.Matches));
+ Assert.Same(endpoint1, Assert.Single(homeIndex.Parameters.Matches));
- var login = root.Literals["Login"];
+ var login = root.Literals["Login"];
- Assert.Same(endpoint2, Assert.Single(login.Matches));
- Assert.Null(login.Parameters);
+ Assert.Same(endpoint2, Assert.Single(login.Matches));
+ Assert.Null(login.Parameters);
- Assert.Equal(2, login.Literals.Count);
+ Assert.Equal(2, login.Literals.Count);
- var loginIndex = login.Literals["Index"];
+ var loginIndex = login.Literals["Index"];
- Assert.Same(endpoint2, Assert.Single(loginIndex.Matches));
- Assert.Null(loginIndex.Literals);
- Assert.NotNull(loginIndex.Parameters);
+ Assert.Same(endpoint2, Assert.Single(loginIndex.Matches));
+ Assert.Null(loginIndex.Literals);
+ Assert.NotNull(loginIndex.Parameters);
- Assert.Same(endpoint2, Assert.Single(loginIndex.Parameters.Matches));
+ Assert.Same(endpoint2, Assert.Single(loginIndex.Parameters.Matches));
- var loginChangePassword = login.Literals["ChangePassword"];
+ var loginChangePassword = login.Literals["ChangePassword"];
- Assert.Same(endpoint3, Assert.Single(loginChangePassword.Matches));
- Assert.Null(loginChangePassword.Literals);
- Assert.NotNull(loginChangePassword.Parameters);
+ Assert.Same(endpoint3, Assert.Single(loginChangePassword.Matches));
+ Assert.Null(loginChangePassword.Literals);
+ Assert.NotNull(loginChangePassword.Parameters);
- Assert.Same(endpoint3, Assert.Single(loginChangePassword.Parameters.Matches));
- }
+ Assert.Same(endpoint3, Assert.Single(loginChangePassword.Parameters.Matches));
+ }
- [Fact]
- public void BuildDfaTree_RequiredValues_AndParameterTransformer()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_RequiredValues_AndParameterTransformer()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint(
- "{controller:slugify}/{action:slugify}",
- defaults: new { controller = "RecentProducts", action = "ViewAll" },
- requiredValues: new { controller = "RecentProducts", action = "ViewAll" });
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint(
+ "{controller:slugify}/{action:slugify}",
+ defaults: new { controller = "RecentProducts", action = "ViewAll" },
+ requiredValues: new { controller = "RecentProducts", action = "ViewAll" });
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Same(endpoint, Assert.Single(root.Matches));
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Same(endpoint, Assert.Single(root.Matches));
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("recent-products", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("recent-products", next.Key);
- var home = next.Value;
- Assert.Same(endpoint, Assert.Single(home.Matches));
- Assert.Null(home.Parameters);
+ var home = next.Value;
+ Assert.Same(endpoint, Assert.Single(home.Matches));
+ Assert.Null(home.Parameters);
- next = Assert.Single(home.Literals);
- Assert.Equal("view-all", next.Key);
+ next = Assert.Single(home.Literals);
+ Assert.Equal("view-all", next.Key);
- var index = next.Value;
- Assert.Same(endpoint, Assert.Single(index.Matches));
- Assert.Null(index.Literals);
- }
+ var index = next.Value;
+ Assert.Same(endpoint, Assert.Single(index.Matches));
+ Assert.Null(index.Literals);
+ }
- [Fact]
- public void BuildDfaTree_RequiredValues_AndDefaults_AndParameterTransformer()
- {
- // Arrange
- var builder = CreateDfaMatcherBuilder();
+ [Fact]
+ public void BuildDfaTree_RequiredValues_AndDefaults_AndParameterTransformer()
+ {
+ // Arrange
+ var builder = CreateDfaMatcherBuilder();
- var endpoint = CreateEndpoint(
- "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
- requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null });
- builder.AddEndpoint(endpoint);
+ var endpoint = CreateEndpoint(
+ "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
+ requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null });
+ builder.AddEndpoint(endpoint);
- // Act
- var root = builder.BuildDfaTree();
+ // Act
+ var root = builder.BuildDfaTree();
- // Assert
- Assert.Null(root.Matches);
- Assert.Null(root.Parameters);
+ // Assert
+ Assert.Null(root.Matches);
+ Assert.Null(root.Parameters);
- var next = Assert.Single(root.Literals);
- Assert.Equal("ConventionalTransformerRoute", next.Key);
+ var next = Assert.Single(root.Literals);
+ Assert.Equal("ConventionalTransformerRoute", next.Key);
- var conventionalTransformerRoute = next.Value;
- Assert.Null(conventionalTransformerRoute.Matches);
- Assert.Null(conventionalTransformerRoute.Parameters);
+ var conventionalTransformerRoute = next.Value;
+ Assert.Null(conventionalTransformerRoute.Matches);
+ Assert.Null(conventionalTransformerRoute.Parameters);
- next = Assert.Single(conventionalTransformerRoute.Literals);
- Assert.Equal("conventional-transformer", next.Key);
+ next = Assert.Single(conventionalTransformerRoute.Literals);
+ Assert.Equal("conventional-transformer", next.Key);
- var conventionalTransformer = next.Value;
- Assert.Same(endpoint, Assert.Single(conventionalTransformer.Matches));
+ var conventionalTransformer = next.Value;
+ Assert.Same(endpoint, Assert.Single(conventionalTransformer.Matches));
- next = Assert.Single(conventionalTransformer.Literals);
- Assert.Equal("Index", next.Key);
+ next = Assert.Single(conventionalTransformer.Literals);
+ Assert.Equal("Index", next.Key);
- var index = next.Value;
- Assert.Same(endpoint, Assert.Single(index.Matches));
+ var index = next.Value;
+ Assert.Same(endpoint, Assert.Single(index.Matches));
- Assert.NotNull(index.Parameters);
+ Assert.NotNull(index.Parameters);
- Assert.Same(endpoint, Assert.Single(index.Parameters.Matches));
- }
+ Assert.Same(endpoint, Assert.Single(index.Parameters.Matches));
+ }
- [Fact]
- public void CreateCandidate_JustLiterals()
- {
- // Arrange
- var endpoint = CreateEndpoint("/a/b/c");
+ [Fact]
+ public void CreateCandidate_JustLiterals()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/a/b/c");
- var builder = CreateDfaMatcherBuilder();
+ var builder = CreateDfaMatcherBuilder();
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
- // Assert
- Assert.Equal(Candidate.CandidateFlags.None, candidate.Flags);
- Assert.Empty(candidate.Slots);
- Assert.Empty(candidate.Captures);
- Assert.Equal(default, candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Empty(candidate.Constraints);
- }
+ // Assert
+ Assert.Equal(Candidate.CandidateFlags.None, candidate.Flags);
+ Assert.Empty(candidate.Slots);
+ Assert.Empty(candidate.Captures);
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Empty(candidate.Constraints);
+ }
- [Fact]
- public void CreateCandidate_Parameters()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{a}/{b}/{c}");
-
- var builder = CreateDfaMatcherBuilder();
-
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
-
- // Assert
- Assert.Equal(Candidate.CandidateFlags.HasCaptures, candidate.Flags);
- Assert.Equal(3, candidate.Slots.Length);
- Assert.Collection(
- candidate.Captures,
- c => Assert.Equal(("a", 0, 0), c),
- c => Assert.Equal(("b", 1, 1), c),
- c => Assert.Equal(("c", 2, 2), c));
- Assert.Equal(default, candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Empty(candidate.Constraints);
- }
+ [Fact]
+ public void CreateCandidate_Parameters()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{a}/{b}/{c}");
+
+ var builder = CreateDfaMatcherBuilder();
+
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
+
+ // Assert
+ Assert.Equal(Candidate.CandidateFlags.HasCaptures, candidate.Flags);
+ Assert.Equal(3, candidate.Slots.Length);
+ Assert.Collection(
+ candidate.Captures,
+ c => Assert.Equal(("a", 0, 0), c),
+ c => Assert.Equal(("b", 1, 1), c),
+ c => Assert.Equal(("c", 2, 2), c));
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Empty(candidate.Constraints);
+ }
- [Fact]
- public void CreateCandidate_Parameters_WithDefaults()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{a=aa}/{b=bb}/{c=cc}");
-
- var builder = CreateDfaMatcherBuilder();
-
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
-
- // Assert
- Assert.Equal(
- Candidate.CandidateFlags.HasDefaults | Candidate.CandidateFlags.HasCaptures,
- candidate.Flags);
- Assert.Collection(
- candidate.Slots,
- s => Assert.Equal(new KeyValuePair<string, object>("a", "aa"), s),
- s => Assert.Equal(new KeyValuePair<string, object>("b", "bb"), s),
- s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s));
- Assert.Collection(
- candidate.Captures,
- c => Assert.Equal(("a", 0, 0), c),
- c => Assert.Equal(("b", 1, 1), c),
- c => Assert.Equal(("c", 2, 2), c));
- Assert.Equal(default, candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Empty(candidate.Constraints);
- }
+ [Fact]
+ public void CreateCandidate_Parameters_WithDefaults()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{a=aa}/{b=bb}/{c=cc}");
+
+ var builder = CreateDfaMatcherBuilder();
+
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
+
+ // Assert
+ Assert.Equal(
+ Candidate.CandidateFlags.HasDefaults | Candidate.CandidateFlags.HasCaptures,
+ candidate.Flags);
+ Assert.Collection(
+ candidate.Slots,
+ s => Assert.Equal(new KeyValuePair<string, object>("a", "aa"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>("b", "bb"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s));
+ Assert.Collection(
+ candidate.Captures,
+ c => Assert.Equal(("a", 0, 0), c),
+ c => Assert.Equal(("b", 1, 1), c),
+ c => Assert.Equal(("c", 2, 2), c));
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Empty(candidate.Constraints);
+ }
- [Fact]
- public void CreateCandidate_Parameters_CatchAll()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{a}/{b}/{*c=cc}");
-
- var builder = CreateDfaMatcherBuilder();
-
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
-
- // Assert
- Assert.Equal(
- Candidate.CandidateFlags.HasDefaults |
- Candidate.CandidateFlags.HasCaptures |
- Candidate.CandidateFlags.HasCatchAll,
- candidate.Flags);
- Assert.Collection(
- candidate.Slots,
- s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s),
- s => Assert.Equal(new KeyValuePair<string, object>(null, null), s),
- s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
- Assert.Collection(
- candidate.Captures,
- c => Assert.Equal(("a", 0, 1), c),
- c => Assert.Equal(("b", 1, 2), c));
- Assert.Equal(("c", 2, 0), candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Empty(candidate.Constraints);
- }
+ [Fact]
+ public void CreateCandidate_Parameters_CatchAll()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{a}/{b}/{*c=cc}");
+
+ var builder = CreateDfaMatcherBuilder();
+
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
+
+ // Assert
+ Assert.Equal(
+ Candidate.CandidateFlags.HasDefaults |
+ Candidate.CandidateFlags.HasCaptures |
+ Candidate.CandidateFlags.HasCatchAll,
+ candidate.Flags);
+ Assert.Collection(
+ candidate.Slots,
+ s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>(null, null), s),
+ s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
+ Assert.Collection(
+ candidate.Captures,
+ c => Assert.Equal(("a", 0, 1), c),
+ c => Assert.Equal(("b", 1, 2), c));
+ Assert.Equal(("c", 2, 0), candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Empty(candidate.Constraints);
+ }
- // Defaults are processed first, which affects the slot ordering.
- [Fact]
- public void CreateCandidate_Parameters_OutOfLineDefaults()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{a}/{b}/{c=cc}", new { a = "aa", d = "dd", });
-
- var builder = CreateDfaMatcherBuilder();
-
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
-
- // Assert
- Assert.Equal(
- Candidate.CandidateFlags.HasDefaults | Candidate.CandidateFlags.HasCaptures,
- candidate.Flags);
- Assert.Collection(
- candidate.Slots,
- s => Assert.Equal(new KeyValuePair<string, object>("a", "aa"), s),
- s => Assert.Equal(new KeyValuePair<string, object>("d", "dd"), s),
- s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s),
- s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
- Assert.Collection(
- candidate.Captures,
- c => Assert.Equal(("a", 0, 0), c),
- c => Assert.Equal(("b", 1, 3), c),
- c => Assert.Equal(("c", 2, 2), c));
- Assert.Equal(default, candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Empty(candidate.Constraints);
- }
+ // Defaults are processed first, which affects the slot ordering.
+ [Fact]
+ public void CreateCandidate_Parameters_OutOfLineDefaults()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{a}/{b}/{c=cc}", new { a = "aa", d = "dd", });
+
+ var builder = CreateDfaMatcherBuilder();
+
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
+
+ // Assert
+ Assert.Equal(
+ Candidate.CandidateFlags.HasDefaults | Candidate.CandidateFlags.HasCaptures,
+ candidate.Flags);
+ Assert.Collection(
+ candidate.Slots,
+ s => Assert.Equal(new KeyValuePair<string, object>("a", "aa"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>("d", "dd"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
+ Assert.Collection(
+ candidate.Captures,
+ c => Assert.Equal(("a", 0, 0), c),
+ c => Assert.Equal(("b", 1, 3), c),
+ c => Assert.Equal(("c", 2, 2), c));
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Empty(candidate.Constraints);
+ }
- [Fact]
- public void CreateCandidate_Parameters_ComplexSegments()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{a}-{b=bb}/{c}");
-
- var builder = CreateDfaMatcherBuilder();
-
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
-
- // Assert
- Assert.Equal(
- Candidate.CandidateFlags.HasDefaults |
- Candidate.CandidateFlags.HasCaptures |
- Candidate.CandidateFlags.HasComplexSegments,
- candidate.Flags);
- Assert.Collection(
- candidate.Slots,
- s => Assert.Equal(new KeyValuePair<string, object>("b", "bb"), s),
- s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
- Assert.Collection(
- candidate.Captures,
- c => Assert.Equal(("c", 1, 1), c));
- Assert.Equal(default, candidate.CatchAll);
- Assert.Collection(
- candidate.ComplexSegments,
- s => Assert.Equal(0, s.segmentIndex));
- Assert.Empty(candidate.Constraints);
- }
+ [Fact]
+ public void CreateCandidate_Parameters_ComplexSegments()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{a}-{b=bb}/{c}");
+
+ var builder = CreateDfaMatcherBuilder();
+
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
+
+ // Assert
+ Assert.Equal(
+ Candidate.CandidateFlags.HasDefaults |
+ Candidate.CandidateFlags.HasCaptures |
+ Candidate.CandidateFlags.HasComplexSegments,
+ candidate.Flags);
+ Assert.Collection(
+ candidate.Slots,
+ s => Assert.Equal(new KeyValuePair<string, object>("b", "bb"), s),
+ s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
+ Assert.Collection(
+ candidate.Captures,
+ c => Assert.Equal(("c", 1, 1), c));
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Collection(
+ candidate.ComplexSegments,
+ s => Assert.Equal(0, s.segmentIndex));
+ Assert.Empty(candidate.Constraints);
+ }
- [Fact]
- public void CreateCandidate_RouteConstraints()
- {
- // Arrange
- var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), });
+ [Fact]
+ public void CreateCandidate_RouteConstraints()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), });
- var builder = CreateDfaMatcherBuilder();
+ var builder = CreateDfaMatcherBuilder();
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
- // Assert
- Assert.Equal(Candidate.CandidateFlags.HasConstraints, candidate.Flags);
- Assert.Empty(candidate.Slots);
- Assert.Empty(candidate.Captures);
- Assert.Equal(default, candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Single(candidate.Constraints);
- }
+ // Assert
+ Assert.Equal(Candidate.CandidateFlags.HasConstraints, candidate.Flags);
+ Assert.Empty(candidate.Slots);
+ Assert.Empty(candidate.Captures);
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Single(candidate.Constraints);
+ }
- [Fact]
- public void CreateCandidate_CustomParameterPolicy()
- {
- // Arrange
- var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new CustomParameterPolicy(), });
+ [Fact]
+ public void CreateCandidate_CustomParameterPolicy()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new CustomParameterPolicy(), });
- var builder = CreateDfaMatcherBuilder();
+ var builder = CreateDfaMatcherBuilder();
- // Act
- var candidate = builder.CreateCandidate(endpoint, score: 0);
+ // Act
+ var candidate = builder.CreateCandidate(endpoint, score: 0);
- // Assert
- Assert.Equal(Candidate.CandidateFlags.None, candidate.Flags);
- Assert.Empty(candidate.Slots);
- Assert.Empty(candidate.Captures);
- Assert.Equal(default, candidate.CatchAll);
- Assert.Empty(candidate.ComplexSegments);
- Assert.Empty(candidate.Constraints);
- }
+ // Assert
+ Assert.Equal(Candidate.CandidateFlags.None, candidate.Flags);
+ Assert.Empty(candidate.Slots);
+ Assert.Empty(candidate.Captures);
+ Assert.Equal(default, candidate.CatchAll);
+ Assert.Empty(candidate.ComplexSegments);
+ Assert.Empty(candidate.Constraints);
+ }
- private class CustomParameterPolicy : IParameterPolicy
- {
- }
+ private class CustomParameterPolicy : IParameterPolicy
+ {
+ }
- [Fact]
- public void CreateCandidates_CreatesScoresCorrectly()
+ [Fact]
+ public void CreateCandidates_CreatesScoresCorrectly()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), }, metadata: new object[] { new TestMetadata1(), new TestMetadata2(), }),
CreateEndpoint("/a/b/c", constraints: new { a = new AlphaRouteConstraint(), }, metadata: new object[] { new TestMetadata1(), new TestMetadata2(), }),
CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), }, metadata: new object[] { new TestMetadata1(), }),
@@ -3415,204 +3415,203 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/a/b/c", constraints: new { }, metadata: new object[] { }),
};
- var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
+ var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
- // Act
- var candidates = builder.CreateCandidates(endpoints);
+ // Act
+ var candidates = builder.CreateCandidates(endpoints);
- // Assert
- Assert.Collection(
- candidates,
- c => Assert.Equal(0, c.Score),
- c => Assert.Equal(0, c.Score),
- c => Assert.Equal(1, c.Score),
- c => Assert.Equal(2, c.Score),
- c => Assert.Equal(3, c.Score),
- c => Assert.Equal(3, c.Score));
- }
+ // Assert
+ Assert.Collection(
+ candidates,
+ c => Assert.Equal(0, c.Score),
+ c => Assert.Equal(0, c.Score),
+ c => Assert.Equal(1, c.Score),
+ c => Assert.Equal(2, c.Score),
+ c => Assert.Equal(3, c.Score),
+ c => Assert.Equal(3, c.Score));
+ }
- private static DfaMatcherBuilder CreateDfaMatcherBuilder(params MatcherPolicy[] policies)
- {
- var policyFactory = CreateParameterPolicyFactory();
- var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
- return new DfaMatcherBuilder(
- NullLoggerFactory.Instance,
- policyFactory,
- Mock.Of<EndpointSelector>(),
- policies);
- }
+ private static DfaMatcherBuilder CreateDfaMatcherBuilder(params MatcherPolicy[] policies)
+ {
+ var policyFactory = CreateParameterPolicyFactory();
+ var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
+ return new DfaMatcherBuilder(
+ NullLoggerFactory.Instance,
+ policyFactory,
+ Mock.Of<EndpointSelector>(),
+ policies);
+ }
- private static RouteEndpoint CreateSubsitutedEndpoint(
- string template,
- object defaults = null,
- object constraints = null,
- object requiredValues = null,
- params object[] metadata)
- {
- var routePattern = RoutePatternFactory.Parse(template, defaults, constraints);
+ private static RouteEndpoint CreateSubsitutedEndpoint(
+ string template,
+ object defaults = null,
+ object constraints = null,
+ object requiredValues = null,
+ params object[] metadata)
+ {
+ var routePattern = RoutePatternFactory.Parse(template, defaults, constraints);
- var policyFactory = CreateParameterPolicyFactory();
- var defaultRoutePatternTransformer = new DefaultRoutePatternTransformer(policyFactory);
+ var policyFactory = CreateParameterPolicyFactory();
+ var defaultRoutePatternTransformer = new DefaultRoutePatternTransformer(policyFactory);
- routePattern = defaultRoutePatternTransformer.SubstituteRequiredValues(routePattern, requiredValues);
+ routePattern = defaultRoutePatternTransformer.SubstituteRequiredValues(routePattern, requiredValues);
- return EndpointFactory.CreateRouteEndpoint(routePattern, metadata: metadata);
- }
+ return EndpointFactory.CreateRouteEndpoint(routePattern, metadata: metadata);
+ }
- public static RoutePattern CreateRoutePattern(RoutePattern routePattern, object requiredValues)
+ public static RoutePattern CreateRoutePattern(RoutePattern routePattern, object requiredValues)
+ {
+ if (requiredValues != null)
{
- if (requiredValues != null)
- {
- var policyFactory = CreateParameterPolicyFactory();
- var defaultRoutePatternTransformer = new DefaultRoutePatternTransformer(policyFactory);
-
- routePattern = defaultRoutePatternTransformer.SubstituteRequiredValues(routePattern, requiredValues);
- }
+ var policyFactory = CreateParameterPolicyFactory();
+ var defaultRoutePatternTransformer = new DefaultRoutePatternTransformer(policyFactory);
- return routePattern;
+ routePattern = defaultRoutePatternTransformer.SubstituteRequiredValues(routePattern, requiredValues);
}
- private static DefaultParameterPolicyFactory CreateParameterPolicyFactory()
- {
- var serviceCollection = new ServiceCollection();
- var policyFactory = new DefaultParameterPolicyFactory(
- Options.Create(new RouteOptions
+ return routePattern;
+ }
+
+ private static DefaultParameterPolicyFactory CreateParameterPolicyFactory()
+ {
+ var serviceCollection = new ServiceCollection();
+ var policyFactory = new DefaultParameterPolicyFactory(
+ Options.Create(new RouteOptions
+ {
+ ConstraintMap =
{
- ConstraintMap =
- {
["slugify"] = typeof(SlugifyParameterTransformer),
["upper-case"] = typeof(UpperCaseParameterTransform)
- }
- }),
- serviceCollection.BuildServiceProvider());
+ }
+ }),
+ serviceCollection.BuildServiceProvider());
- return policyFactory;
- }
+ return policyFactory;
+ }
- private static RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- object constraints = null,
- object requiredValues = null,
- int order = 0,
- params object[] metadata)
- {
- return EndpointFactory.CreateRouteEndpoint(template, defaults, constraints, requiredValues, order: order, metadata: metadata);
- }
+ private static RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ object constraints = null,
+ object requiredValues = null,
+ int order = 0,
+ params object[] metadata)
+ {
+ return EndpointFactory.CreateRouteEndpoint(template, defaults, constraints, requiredValues, order: order, metadata: metadata);
+ }
- private class TestMetadata1
+ private class TestMetadata1
+ {
+ public TestMetadata1()
{
- public TestMetadata1()
- {
- }
-
- public TestMetadata1(int state)
- {
- State = state;
- }
-
- public int State { get; set; }
}
- private class TestMetadata1MatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
+ public TestMetadata1(int state)
{
- public override int Order => 100;
+ State = state;
+ }
- public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
+ public int State { get; set; }
+ }
- public Action<IReadOnlyList<Endpoint>> OnGetEdges { get; set; }
+ private class TestMetadata1MatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
+ {
+ public override int Order => 100;
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
- }
+ public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
- {
- throw new NotImplementedException();
- }
+ public Action<IReadOnlyList<Endpoint>> OnGetEdges { get; set; }
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
- {
- OnGetEdges?.Invoke(endpoints);
- return endpoints
- .GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
- .Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
- .ToArray();
- }
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
}
- private class TestMetadata2
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{
- public TestMetadata2()
- {
- }
+ throw new NotImplementedException();
+ }
- public TestMetadata2(bool state)
- {
- State = state;
- }
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
+ {
+ OnGetEdges?.Invoke(endpoints);
+ return endpoints
+ .GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
+ .Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
+ .ToArray();
+ }
+ }
- public bool State { get; set; }
+ private class TestMetadata2
+ {
+ public TestMetadata2()
+ {
}
- private class TestMetadata2MatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
+ public TestMetadata2(bool state)
{
- public override int Order => 101;
+ State = state;
+ }
+
+ public bool State { get; set; }
+ }
- public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata2>.Default;
+ private class TestMetadata2MatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
+ {
+ public override int Order => 101;
- public Action<IReadOnlyList<Endpoint>> OnGetEdges { get; set; }
+ public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata2>.Default;
+ public Action<IReadOnlyList<Endpoint>> OnGetEdges { get; set; }
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata2>() != null);
- }
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
- {
- throw new NotImplementedException();
- }
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata2>() != null);
+ }
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
- {
- OnGetEdges?.Invoke(endpoints);
- return endpoints
- .GroupBy(e => e.Metadata.GetMetadata<TestMetadata2>().State)
- .Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
- .ToArray();
- }
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
+ {
+ throw new NotImplementedException();
}
- private class TestNonRoutePatternMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{
- public override int Order => 100;
+ OnGetEdges?.Invoke(endpoints);
+ return endpoints
+ .GroupBy(e => e.Metadata.GetMetadata<TestMetadata2>().State)
+ .Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
+ .ToArray();
+ }
+ }
- public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
+ private class TestNonRoutePatternMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
+ {
+ public override int Order => 100;
- public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
- {
- return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
- }
+ public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
- public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
- {
- throw new NotImplementedException();
- }
+ public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
+ {
+ return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
+ }
- public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
- {
- var edges = endpoints
- .GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
- .Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
- .ToList();
+ public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
+ {
+ var edges = endpoints
+ .GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
+ .Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
+ .ToList();
- var maxValueEndpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "MaxValueEndpoint");
- edges.Add(new PolicyNodeEdge(int.MaxValue, new[] { maxValueEndpoint }));
+ var maxValueEndpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "MaxValueEndpoint");
+ edges.Add(new PolicyNodeEdge(int.MaxValue, new[] { maxValueEndpoint }));
- return edges;
- }
+ return edges;
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherConformanceTest.cs
index 9a3202ec01..fc9cf62847 100644
--- a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherConformanceTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherConformanceTest.cs
@@ -5,35 +5,35 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class DfaMatcherConformanceTest : FullFeaturedMatcherConformanceTest
{
- public class DfaMatcherConformanceTest : FullFeaturedMatcherConformanceTest
+ // See the comments in the base class. DfaMatcher fixes a long-standing bug
+ // with catchall parameters and empty segments.
+ public override async Task Quirks_CatchAllParameter(string template, string path, string[] keys, string[] values)
{
- // See the comments in the base class. DfaMatcher fixes a long-standing bug
- // with catchall parameters and empty segments.
- public override async Task Quirks_CatchAllParameter(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
- // https://github.com/dotnet/aspnetcore/issues/18677
- [Theory]
- [InlineData("/middleware", 1)]
- [InlineData("/middleware/test", 1)]
- [InlineData("/middleware/test1/test2", 1)]
- [InlineData("/bill/boga", 0)]
- public async Task Match_Regression_1867_CorrectBehavior(string path, int endpointIndex)
+ // https://github.com/dotnet/aspnetcore/issues/18677
+ [Theory]
+ [InlineData("/middleware", 1)]
+ [InlineData("/middleware/test", 1)]
+ [InlineData("/middleware/test1/test2", 1)]
+ [InlineData("/bill/boga", 0)]
+ public async Task Match_Regression_1867_CorrectBehavior(string path, int endpointIndex)
+ {
+ var endpoints = new RouteEndpoint[]
{
- var endpoints = new RouteEndpoint[]
- {
EndpointFactory.CreateRouteEndpoint(
"{firstName}/{lastName}",
order: 0,
@@ -42,40 +42,39 @@ namespace Microsoft.AspNetCore.Routing.Matching
EndpointFactory.CreateRouteEndpoint(
"middleware/{**_}",
order: 0),
- };
+ };
- var expected = endpoints[endpointIndex];
+ var expected = endpoints[endpointIndex];
- var matcher = CreateMatcherCore(endpoints);
- var httpContext = CreateContext(path);
+ var matcher = CreateMatcherCore(endpoints);
+ var httpContext = CreateContext(path);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
- internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
- {
- return CreateMatcherCore(endpoints);
- }
+ internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ {
+ return CreateMatcherCore(endpoints);
+ }
- internal Matcher CreateMatcherCore(params RouteEndpoint[] endpoints)
- {
- var services = new ServiceCollection()
- .AddLogging()
- .AddOptions()
- .AddRouting()
- .BuildServiceProvider();
+ internal Matcher CreateMatcherCore(params RouteEndpoint[] endpoints)
+ {
+ var services = new ServiceCollection()
+ .AddLogging()
+ .AddOptions()
+ .AddRouting()
+ .BuildServiceProvider();
- var builder = services.GetRequiredService<DfaMatcherBuilder>();
+ var builder = services.GetRequiredService<DfaMatcherBuilder>();
- for (var i = 0; i < endpoints.Length; i++)
- {
- builder.AddEndpoint(endpoints[i]);
- }
- return builder.Build();
+ for (var i = 0; i < endpoints.Length; i++)
+ {
+ builder.AddEndpoint(endpoints[i]);
}
+ return builder.Build();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs
index bdd1850671..bf9af68b30 100644
--- a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs
@@ -14,1033 +14,1032 @@ using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// Many of these are integration tests that exercise the system end to end,
+// so we're reusing the services here.
+public class DfaMatcherTest
{
- // Many of these are integration tests that exercise the system end to end,
- // so we're reusing the services here.
- public class DfaMatcherTest
+ private RouteEndpoint CreateEndpoint(string template, int order, object defaults = null, object requiredValues = null, object policies = null)
{
- private RouteEndpoint CreateEndpoint(string template, int order, object defaults = null, object requiredValues = null, object policies = null)
- {
- return EndpointFactory.CreateRouteEndpoint(template, defaults, policies, requiredValues, order, displayName: template);
- }
-
- private DataSourceDependentMatcher CreateDfaMatcher(
- EndpointDataSource dataSource,
- MatcherPolicy[] policies = null,
- EndpointSelector endpointSelector = null,
- ILoggerFactory loggerFactory = null)
- {
- var serviceCollection = new ServiceCollection()
- .AddLogging()
- .AddOptions()
- .AddRouting(options =>
- {
- options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
- });
-
- if (policies != null)
- {
- for (var i = 0; i < policies.Length; i++)
- {
- serviceCollection.AddSingleton<MatcherPolicy>(policies[i]);
- }
- }
+ return EndpointFactory.CreateRouteEndpoint(template, defaults, policies, requiredValues, order, displayName: template);
+ }
- if (endpointSelector != null)
+ private DataSourceDependentMatcher CreateDfaMatcher(
+ EndpointDataSource dataSource,
+ MatcherPolicy[] policies = null,
+ EndpointSelector endpointSelector = null,
+ ILoggerFactory loggerFactory = null)
+ {
+ var serviceCollection = new ServiceCollection()
+ .AddLogging()
+ .AddOptions()
+ .AddRouting(options =>
{
- serviceCollection.AddSingleton<EndpointSelector>(endpointSelector);
- }
+ options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
- if (loggerFactory != null)
+ if (policies != null)
+ {
+ for (var i = 0; i < policies.Length; i++)
{
- serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory);
+ serviceCollection.AddSingleton<MatcherPolicy>(policies[i]);
}
+ }
- var services = serviceCollection.BuildServiceProvider();
-
- var factory = services.GetRequiredService<MatcherFactory>();
- return Assert.IsType<DataSourceDependentMatcher>(factory.CreateMatcher(dataSource));
+ if (endpointSelector != null)
+ {
+ serviceCollection.AddSingleton<EndpointSelector>(endpointSelector);
}
- [Fact]
- public async Task MatchAsync_ValidRouteConstraint_EndpointMatched()
+ if (loggerFactory != null)
{
- // Arrange
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory);
+ }
+
+ var services = serviceCollection.BuildServiceProvider();
+
+ var factory = services.GetRequiredService<MatcherFactory>();
+ return Assert.IsType<DataSourceDependentMatcher>(factory.CreateMatcher(dataSource));
+ }
+
+ [Fact]
+ public async Task MatchAsync_ValidRouteConstraint_EndpointMatched()
+ {
+ // Arrange
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/{p:int}", 0)
});
- var matcher = CreateDfaMatcher(endpointDataSource);
+ var matcher = CreateDfaMatcher(endpointDataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/1";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/1";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.NotNull(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.NotNull(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_InvalidRouteConstraint_NoEndpointMatched()
- {
- // Arrange
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_InvalidRouteConstraint_NoEndpointMatched()
+ {
+ // Arrange
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/{p:int}", 0)
});
- var matcher = CreateDfaMatcher(endpointDataSource);
+ var matcher = CreateDfaMatcher(endpointDataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/One";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/One";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Null(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_RequireValuesAndDefaultValues_EndpointMatched()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "{controller=Home}/{action=Index}/{id?}",
- 0,
- requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
+ [Fact]
+ public async Task MatchAsync_RequireValuesAndDefaultValues_EndpointMatched()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "{controller=Home}/{action=Index}/{id?}",
+ 0,
+ requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint
});
- var matcher = CreateDfaMatcher(dataSource);
+ var matcher = CreateDfaMatcher(dataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Same(endpoint, httpContext.GetEndpoint());
+ // Assert
+ Assert.Same(endpoint, httpContext.GetEndpoint());
- Assert.Collection(
- httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
- (kvp) =>
- {
- Assert.Equal("action", kvp.Key);
- Assert.Equal("Index", kvp.Value);
- },
- (kvp) =>
- {
- Assert.Equal("controller", kvp.Key);
- Assert.Equal("Home", kvp.Value);
- });
- }
+ Assert.Collection(
+ httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
+ (kvp) =>
+ {
+ Assert.Equal("action", kvp.Key);
+ Assert.Equal("Index", kvp.Value);
+ },
+ (kvp) =>
+ {
+ Assert.Equal("controller", kvp.Key);
+ Assert.Equal("Home", kvp.Value);
+ });
+ }
- [Fact]
- public async Task MatchAsync_RequireValuesAndDifferentPath_NoEndpointMatched()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "{controller}/{action}",
- 0,
- requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
+ [Fact]
+ public async Task MatchAsync_RequireValuesAndDifferentPath_NoEndpointMatched()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "{controller}/{action}",
+ 0,
+ requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint
});
- var matcher = CreateDfaMatcher(dataSource);
+ var matcher = CreateDfaMatcher(dataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Login/Index";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Login/Index";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Null(httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_RequireValuesAndOptionalParameter_EndpointMatched()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "{controller}/{action}/{id?}",
- 0,
- requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
+ [Fact]
+ public async Task MatchAsync_RequireValuesAndOptionalParameter_EndpointMatched()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "{controller}/{action}/{id?}",
+ 0,
+ requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint
});
- var matcher = CreateDfaMatcher(dataSource);
-
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Home/Index/123";
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- Assert.Same(endpoint, httpContext.GetEndpoint());
-
- Assert.Collection(
- httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
- (kvp) =>
- {
- Assert.Equal("action", kvp.Key);
- Assert.Equal("Index", kvp.Value);
- },
- (kvp) =>
- {
- Assert.Equal("controller", kvp.Key);
- Assert.Equal("Home", kvp.Value);
- },
- (kvp) =>
- {
- Assert.Equal("id", kvp.Key);
- Assert.Equal("123", kvp.Value);
- });
- }
+ var matcher = CreateDfaMatcher(dataSource);
- [Theory]
- [InlineData("/")]
- [InlineData("/TestController")]
- [InlineData("/TestController/TestAction")]
- [InlineData("/TestController/TestAction/17")]
- [InlineData("/TestController/TestAction/17/catchAll")]
- public async Task MatchAsync_ShortenedPattern_EndpointMatched(string path)
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}",
- 0,
- requiredValues: new { controller = "TestController", action = "TestAction", area = (string)null, page = (string)null });
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Home/Index/123";
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ Assert.Same(endpoint, httpContext.GetEndpoint());
+
+ Assert.Collection(
+ httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
+ (kvp) =>
+ {
+ Assert.Equal("action", kvp.Key);
+ Assert.Equal("Index", kvp.Value);
+ },
+ (kvp) =>
+ {
+ Assert.Equal("controller", kvp.Key);
+ Assert.Equal("Home", kvp.Value);
+ },
+ (kvp) =>
+ {
+ Assert.Equal("id", kvp.Key);
+ Assert.Equal("123", kvp.Value);
+ });
+ }
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Theory]
+ [InlineData("/")]
+ [InlineData("/TestController")]
+ [InlineData("/TestController/TestAction")]
+ [InlineData("/TestController/TestAction/17")]
+ [InlineData("/TestController/TestAction/17/catchAll")]
+ public async Task MatchAsync_ShortenedPattern_EndpointMatched(string path)
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}",
+ 0,
+ requiredValues: new { controller = "TestController", action = "TestAction", area = (string)null, page = (string)null });
+
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint
});
- var matcher = CreateDfaMatcher(dataSource);
+ var matcher = CreateDfaMatcher(dataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = path;
+ var httpContext = CreateContext();
+ httpContext.Request.Path = path;
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Same(endpoint, httpContext.GetEndpoint());
+ // Assert
+ Assert.Same(endpoint, httpContext.GetEndpoint());
- Assert.Equal("TestAction", httpContext.Request.RouteValues["action"]);
- Assert.Equal("TestController", httpContext.Request.RouteValues["controller"]);
- Assert.Equal("17", httpContext.Request.RouteValues["id"]);
- }
+ Assert.Equal("TestAction", httpContext.Request.RouteValues["action"]);
+ Assert.Equal("TestController", httpContext.Request.RouteValues["controller"]);
+ Assert.Equal("17", httpContext.Request.RouteValues["id"]);
+ }
- [Fact]
- public async Task MatchAsync_MultipleEndpointsWithDifferentRequiredValues_EndpointMatched()
- {
- // Arrange
- var endpoint1 = CreateEndpoint(
- "{controller}/{action}/{id?}",
- 0,
- requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
- var endpoint2 = CreateEndpoint(
- "{controller}/{action}/{id?}",
- 0,
- requiredValues: new { controller = "Login", action = "Index", area = (string)null, page = (string)null });
-
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_MultipleEndpointsWithDifferentRequiredValues_EndpointMatched()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint(
+ "{controller}/{action}/{id?}",
+ 0,
+ requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null });
+ var endpoint2 = CreateEndpoint(
+ "{controller}/{action}/{id?}",
+ 0,
+ requiredValues: new { controller = "Login", action = "Index", area = (string)null, page = (string)null });
+
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = CreateDfaMatcher(dataSource);
+ var matcher = CreateDfaMatcher(dataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Home/Index/123";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Home/Index/123";
- // Act 1
- await matcher.MatchAsync(httpContext);
+ // Act 1
+ await matcher.MatchAsync(httpContext);
- // Assert 1
- Assert.Same(endpoint1, httpContext.GetEndpoint());
+ // Assert 1
+ Assert.Same(endpoint1, httpContext.GetEndpoint());
- httpContext.Request.Path = "/Login/Index/123";
+ httpContext.Request.Path = "/Login/Index/123";
- // Act 2
- await matcher.MatchAsync(httpContext);
+ // Act 2
+ await matcher.MatchAsync(httpContext);
- // Assert 2
- Assert.Same(endpoint2, httpContext.GetEndpoint());
- }
+ // Assert 2
+ Assert.Same(endpoint2, httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_ParameterTransformer_EndpointMatched()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
- 0,
- requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null });
+ [Fact]
+ public async Task MatchAsync_ParameterTransformer_EndpointMatched()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}",
+ 0,
+ requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null });
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint
});
- var matcher = CreateDfaMatcher(dataSource);
+ var matcher = CreateDfaMatcher(dataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/ConventionalTransformerRoute/conventional-transformer/Index";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/ConventionalTransformerRoute/conventional-transformer/Index";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Same(endpoint, httpContext.GetEndpoint());
+ // Assert
+ Assert.Same(endpoint, httpContext.GetEndpoint());
- Assert.Collection(
- httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
- (kvp) =>
- {
- Assert.Equal("action", kvp.Key);
- Assert.Equal("Index", kvp.Value);
- },
- (kvp) =>
- {
- Assert.Equal("controller", kvp.Key);
- Assert.Equal("ConventionalTransformer", kvp.Value);
- });
- }
+ Assert.Collection(
+ httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
+ (kvp) =>
+ {
+ Assert.Equal("action", kvp.Key);
+ Assert.Equal("Index", kvp.Value);
+ },
+ (kvp) =>
+ {
+ Assert.Equal("controller", kvp.Key);
+ Assert.Equal("ConventionalTransformer", kvp.Value);
+ });
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_EndpointMatched()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("a/c", 0);
- var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_EndpointMatched()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("a/c", 0);
+ var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect endpoint2 to match here since we trimmed the branch for the parameter based on `a` not meeting
- // the constraints.
- var candidate = Assert.Single(set.candidates);
- Assert.Same(endpoint2, candidate.Endpoint);
- }
+ // Assert
+ // We expect endpoint2 to match here since we trimmed the branch for the parameter based on `a` not meeting
+ // the constraints.
+ var candidate = Assert.Single(set.candidates);
+ Assert.Same(endpoint2, candidate.Endpoint);
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_EndpointNotMatched()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("a/c", 0);
- var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_EndpointNotMatched()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("a/c", 0);
+ var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect no candidates here, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) for not meeting the length(2) constraint.
- Assert.Empty(set.candidates);
- }
+ // Assert
+ // We expect no candidates here, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) for not meeting the length(2) constraint.
+ Assert.Empty(set.candidates);
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_EndpointMatched_WhenExplicitRouteExists()
- {
- // Arrange
- // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will
- // be filtered out of the candidates list because it didn't meet the constraint.
- var endpoint1 = CreateEndpoint("a/b/c", 0);
- var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_EndpointMatched_WhenExplicitRouteExists()
+ {
+ // Arrange
+ // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will
+ // be filtered out of the candidates list because it didn't meet the constraint.
+ var endpoint1 = CreateEndpoint("a/b/c", 0);
+ var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect only one candidate, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) does not meet the length(2) constraint.
- var candidate = Assert.Single(set.candidates);
- Assert.Same(endpoint1, candidate.Endpoint);
- }
+ // Assert
+ // We expect only one candidate, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) does not meet the length(2) constraint.
+ var candidate = Assert.Single(set.candidates);
+ Assert.Same(endpoint1, candidate.Endpoint);
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint()
- {
- // Arrange
- // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates
- var endpoint1 = CreateEndpoint("aa/b/c", 0);
- var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
- var endpoints = new List<Endpoint>
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint()
+ {
+ // Arrange
+ // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates
+ var endpoint1 = CreateEndpoint("aa/b/c", 0);
+ var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0);
+ var endpoints = new List<Endpoint>
{
endpoint2,
endpoint1,
};
- var dataSource = new DefaultEndpointDataSource(endpoints);
+ var dataSource = new DefaultEndpointDataSource(endpoints);
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint.
- Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray());
- }
+ // Assert
+ // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint.
+ Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray());
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("a/b/c", 0);
- var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("a/b/c", 0);
+ var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect endpoint2 to match here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint.
- var candidate = Assert.Single(set.candidates);
- Assert.Same(endpoint2, candidate.Endpoint);
- }
+ // Assert
+ // We expect endpoint2 to match here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint.
+ var candidate = Assert.Single(set.candidates);
+ Assert.Same(endpoint2, candidate.Endpoint);
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointNotMatched()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("a/b/d", 0);
- var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointNotMatched()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("a/b/d", 0);
+ var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect no candidates here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint.
- Assert.Empty(set.candidates);
- }
+ // Assert
+ // We expect no candidates here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint.
+ Assert.Empty(set.candidates);
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched_WhenExplicitRouteExists()
- {
- // Arrange
- // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will
- // be filtered out of the candidates list because it didn't meet the constraint.
- var endpoint1 = CreateEndpoint("a/b/c", 0);
- var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched_WhenExplicitRouteExists()
+ {
+ // Arrange
+ // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will
+ // be filtered out of the candidates list because it didn't meet the constraint.
+ var endpoint1 = CreateEndpoint("a/b/c", 0);
+ var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/a/b/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect only one candidate, since the path on the tree (a -> b -> c = (a/{param:length(2)}/c)) does not meet the length(2) constraint.
- var candidate = Assert.Single(set.candidates);
- Assert.Same(endpoint1, candidate.Endpoint);
- }
+ // Assert
+ // We expect only one candidate, since the path on the tree (a -> b -> c = (a/{param:length(2)}/c)) does not meet the length(2) constraint.
+ var candidate = Assert.Single(set.candidates);
+ Assert.Same(endpoint1, candidate.Endpoint);
+ }
- [Fact]
- public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint()
- {
- // Arrange
- // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates
- var endpoint1 = CreateEndpoint("a/bb/c", 0);
- var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
- var endpoints = new List<Endpoint>
+ [Fact]
+ public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint()
+ {
+ // Arrange
+ // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates
+ var endpoint1 = CreateEndpoint("a/bb/c", 0);
+ var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0);
+ var endpoints = new List<Endpoint>
{
endpoint2,
endpoint1,
};
- var dataSource = new DefaultEndpointDataSource(endpoints);
+ var dataSource = new DefaultEndpointDataSource(endpoints);
- var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
- var buffer = new PathSegment[3];
- var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer);
+ var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher;
+ var buffer = new PathSegment[3];
+ var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer);
- // Act
- var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
+ // Act
+ var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count));
- // Assert
- // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint.
- Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray());
- }
+ // Assert
+ // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint.
+ Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray());
+ }
- private (HttpContext context, string path, int count) CreateMatchingContext(string requestPath, PathSegment[] buffer)
- {
- var context = CreateContext();
- context.Request.Path = requestPath;
+ private (HttpContext context, string path, int count) CreateMatchingContext(string requestPath, PathSegment[] buffer)
+ {
+ var context = CreateContext();
+ context.Request.Path = requestPath;
- // First tokenize the path into series of segments.
- var count = FastPathTokenizer.Tokenize(requestPath, buffer);
- return (context, requestPath, count);
- }
+ // First tokenize the path into series of segments.
+ var count = FastPathTokenizer.Tokenize(requestPath, buffer);
+ return (context, requestPath, count);
+ }
- [Fact]
- public async Task MatchAsync_DifferentDefaultCase_RouteValueUsesDefaultCase()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "{controller}/{action=TESTACTION}/{id?}",
- 0,
- requiredValues: new { controller = "TestController", action = "TestAction" });
+ [Fact]
+ public async Task MatchAsync_DifferentDefaultCase_RouteValueUsesDefaultCase()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "{controller}/{action=TESTACTION}/{id?}",
+ 0,
+ requiredValues: new { controller = "TestController", action = "TestAction" });
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint
});
- var matcher = CreateDfaMatcher(dataSource);
+ var matcher = CreateDfaMatcher(dataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/TestController";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/TestController";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Same(endpoint, httpContext.GetEndpoint());
+ // Assert
+ Assert.Same(endpoint, httpContext.GetEndpoint());
- Assert.Collection(
- httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
- (kvp) =>
- {
- Assert.Equal("action", kvp.Key);
- Assert.Equal("TESTACTION", kvp.Value);
- },
- (kvp) =>
- {
- Assert.Equal("controller", kvp.Key);
- Assert.Equal("TestController", kvp.Value);
- });
- }
+ Assert.Collection(
+ httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key),
+ (kvp) =>
+ {
+ Assert.Equal("action", kvp.Key);
+ Assert.Equal("TESTACTION", kvp.Value);
+ },
+ (kvp) =>
+ {
+ Assert.Equal("controller", kvp.Key);
+ Assert.Equal("TestController", kvp.Value);
+ });
+ }
- [Fact]
- public async Task MatchAsync_DuplicateTemplatesAndDifferentOrder_LowerOrderEndpointMatched()
- {
- // Arrange
- var higherOrderEndpoint = CreateEndpoint("/Teams", 1);
- var lowerOrderEndpoint = CreateEndpoint("/Teams", 0);
+ [Fact]
+ public async Task MatchAsync_DuplicateTemplatesAndDifferentOrder_LowerOrderEndpointMatched()
+ {
+ // Arrange
+ var higherOrderEndpoint = CreateEndpoint("/Teams", 1);
+ var lowerOrderEndpoint = CreateEndpoint("/Teams", 0);
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
higherOrderEndpoint,
lowerOrderEndpoint
});
- var matcher = CreateDfaMatcher(endpointDataSource);
+ var matcher = CreateDfaMatcher(endpointDataSource);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Teams";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Teams";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Equal(lowerOrderEndpoint, httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Equal(lowerOrderEndpoint, httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/Teams", 0);
- var endpoint2 = CreateEndpoint("/Teams", 1);
-
- var endpointSelector = new Mock<EndpointSelector>();
- endpointSelector
- .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Callback<HttpContext, CandidateSet>((c, cs) =>
- {
- Assert.Equal(2, cs.Count);
-
- Assert.Same(endpoint1, cs[0].Endpoint);
- Assert.True(cs.IsValidCandidate(0));
- Assert.Equal(0, cs[0].Score);
- Assert.Null(cs[0].Values);
-
- Assert.Same(endpoint2, cs[1].Endpoint);
- Assert.True(cs.IsValidCandidate(1));
- Assert.Equal(1, cs[1].Score);
- Assert.Null(cs[1].Values);
-
- c.SetEndpoint(endpoint2);
- })
- .Returns(Task.CompletedTask);
-
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/Teams", 0);
+ var endpoint2 = CreateEndpoint("/Teams", 1);
+
+ var endpointSelector = new Mock<EndpointSelector>();
+ endpointSelector
+ .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Callback<HttpContext, CandidateSet>((c, cs) =>
+ {
+ Assert.Equal(2, cs.Count);
+
+ Assert.Same(endpoint1, cs[0].Endpoint);
+ Assert.True(cs.IsValidCandidate(0));
+ Assert.Equal(0, cs[0].Score);
+ Assert.Null(cs[0].Values);
+
+ Assert.Same(endpoint2, cs[1].Endpoint);
+ Assert.True(cs.IsValidCandidate(1));
+ Assert.Equal(1, cs[1].Score);
+ Assert.Null(cs[1].Values);
+
+ c.SetEndpoint(endpoint2);
+ })
+ .Returns(Task.CompletedTask);
+
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object);
+ var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Teams";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Teams";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Equal(endpoint2, httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Equal(endpoint2, httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteParameter()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/Teams/{x?}", 0);
- var endpoint2 = CreateEndpoint("/Teams/{x?}", 1);
-
- var endpointSelector = new Mock<EndpointSelector>();
- endpointSelector
- .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Callback<HttpContext, CandidateSet>((c, cs) =>
- {
- Assert.Equal(2, cs.Count);
-
- Assert.Same(endpoint1, cs[0].Endpoint);
- Assert.True(cs.IsValidCandidate(0));
- Assert.Equal(0, cs[0].Score);
- Assert.Empty(cs[0].Values);
-
- Assert.Same(endpoint2, cs[1].Endpoint);
- Assert.True(cs.IsValidCandidate(1));
- Assert.Equal(1, cs[1].Score);
- Assert.Empty(cs[1].Values);
-
- c.SetEndpoint(endpoint2);
- })
- .Returns(Task.CompletedTask);
-
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteParameter()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/Teams/{x?}", 0);
+ var endpoint2 = CreateEndpoint("/Teams/{x?}", 1);
+
+ var endpointSelector = new Mock<EndpointSelector>();
+ endpointSelector
+ .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Callback<HttpContext, CandidateSet>((c, cs) =>
+ {
+ Assert.Equal(2, cs.Count);
+
+ Assert.Same(endpoint1, cs[0].Endpoint);
+ Assert.True(cs.IsValidCandidate(0));
+ Assert.Equal(0, cs[0].Score);
+ Assert.Empty(cs[0].Values);
+
+ Assert.Same(endpoint2, cs[1].Endpoint);
+ Assert.True(cs.IsValidCandidate(1));
+ Assert.Equal(1, cs[1].Score);
+ Assert.Empty(cs[1].Values);
+
+ c.SetEndpoint(endpoint2);
+ })
+ .Returns(Task.CompletedTask);
+
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object);
+ var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Teams";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Teams";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Equal(endpoint2, httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Equal(endpoint2, httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteConstraint()
- {
- // Arrange
- var constraint = new OptionalRouteConstraint(new IntRouteConstraint());
- var endpoint1 = CreateEndpoint("/Teams", 0, policies: new { x = constraint, });
- var endpoint2 = CreateEndpoint("/Teams", 1, policies: new { x = constraint, });
-
- var endpointSelector = new Mock<EndpointSelector>();
- endpointSelector
- .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Callback<HttpContext, CandidateSet>((c, cs) =>
- {
- Assert.Equal(2, cs.Count);
-
- Assert.Same(endpoint1, cs[0].Endpoint);
- Assert.True(cs.IsValidCandidate(0));
- Assert.Equal(0, cs[0].Score);
- Assert.Empty(cs[0].Values);
-
- Assert.Same(endpoint2, cs[1].Endpoint);
- Assert.True(cs.IsValidCandidate(1));
- Assert.Equal(1, cs[1].Score);
- Assert.Empty(cs[1].Values);
-
- c.SetEndpoint(endpoint2);
- })
- .Returns(Task.CompletedTask);
-
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteConstraint()
+ {
+ // Arrange
+ var constraint = new OptionalRouteConstraint(new IntRouteConstraint());
+ var endpoint1 = CreateEndpoint("/Teams", 0, policies: new { x = constraint, });
+ var endpoint2 = CreateEndpoint("/Teams", 1, policies: new { x = constraint, });
+
+ var endpointSelector = new Mock<EndpointSelector>();
+ endpointSelector
+ .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Callback<HttpContext, CandidateSet>((c, cs) =>
+ {
+ Assert.Equal(2, cs.Count);
+
+ Assert.Same(endpoint1, cs[0].Endpoint);
+ Assert.True(cs.IsValidCandidate(0));
+ Assert.Equal(0, cs[0].Score);
+ Assert.Empty(cs[0].Values);
+
+ Assert.Same(endpoint2, cs[1].Endpoint);
+ Assert.True(cs.IsValidCandidate(1));
+ Assert.Equal(1, cs[1].Score);
+ Assert.Empty(cs[1].Values);
+
+ c.SetEndpoint(endpoint2);
+ })
+ .Returns(Task.CompletedTask);
+
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
endpoint1,
endpoint2
});
- var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object);
+ var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object);
- var httpContext = CreateContext();
- httpContext.Request.Path = "/Teams";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/Teams";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Equal(endpoint2, httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Equal(endpoint2, httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_NoCandidates_Logging()
- {
- // Arrange
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_NoCandidates_Logging()
+ {
+ // Arrange
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/{p:int}", 0)
});
- var sink = new TestSink();
- var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
+ var sink = new TestSink();
+ var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
- var httpContext = CreateContext();
- httpContext.Request.Path = "/";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Null(httpContext.GetEndpoint());
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
- Assert.Collection(
- sink.Writes,
- (log) =>
- {
- Assert.Equal(1000, log.EventId);
- Assert.Equal("No candidates found for the request path '/'", log.Message);
- });
- }
+ Assert.Collection(
+ sink.Writes,
+ (log) =>
+ {
+ Assert.Equal(1000, log.EventId);
+ Assert.Equal("No candidates found for the request path '/'", log.Message);
+ });
+ }
- [Fact]
- public async Task MatchAsync_ConstraintRejectsEndpoint_Logging()
- {
- // Arrange
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_ConstraintRejectsEndpoint_Logging()
+ {
+ // Arrange
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/{p:int}", 0)
});
- var sink = new TestSink();
- var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
-
- var httpContext = CreateContext();
- httpContext.Request.Path = "/One";
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- Assert.Null(httpContext.GetEndpoint());
-
- Assert.Collection(
- sink.Writes,
- (log) =>
- {
- Assert.Equal(1001, log.EventId);
- Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1003, log.EventId);
- Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1004, log.EventId);
- Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
- });
- }
+ var sink = new TestSink();
+ var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
- [Fact]
- public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging()
- {
- // Arrange
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/One";
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
+
+ Assert.Collection(
+ sink.Writes,
+ (log) =>
+ {
+ Assert.Equal(1001, log.EventId);
+ Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1003, log.EventId);
+ Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1004, log.EventId);
+ Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
+ });
+ }
+
+ [Fact]
+ public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging()
+ {
+ // Arrange
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/x-{id}-y", 0)
});
- var sink = new TestSink();
- var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
-
- var httpContext = CreateContext();
- httpContext.Request.Path = "/One";
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- Assert.Null(httpContext.GetEndpoint());
-
- Assert.Collection(
- sink.Writes,
- (log) =>
- {
- Assert.Equal(1001, log.EventId);
- Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1002, log.EventId);
- Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1004, log.EventId);
- Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
- });
- }
+ var sink = new TestSink();
+ var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
- [Fact]
- public async Task MatchAsync_MultipleCandidates_Logging()
- {
- // Arrange
- var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/One";
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ Assert.Null(httpContext.GetEndpoint());
+
+ Assert.Collection(
+ sink.Writes,
+ (log) =>
+ {
+ Assert.Equal(1001, log.EventId);
+ Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1002, log.EventId);
+ Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1004, log.EventId);
+ Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
+ });
+ }
+
+ [Fact]
+ public async Task MatchAsync_MultipleCandidates_Logging()
+ {
+ // Arrange
+ var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/{one}", 0),
CreateEndpoint("/{p:int}", 1),
CreateEndpoint("/x-{id}-y", 2),
});
- var sink = new TestSink();
- var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
-
- var httpContext = CreateContext();
- httpContext.Request.Path = "/One";
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- Assert.Same(endpointDataSource.Endpoints[0], httpContext.GetEndpoint());
-
- Assert.Collection(
- sink.Writes,
- (log) =>
- {
- Assert.Equal(1001, log.EventId);
- Assert.Equal("3 candidate(s) found for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1005, log.EventId);
- Assert.Equal("Endpoint '/{one}' with route pattern '/{one}' is valid for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1003, log.EventId);
- Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1004, log.EventId);
- Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1002, log.EventId);
- Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
- },
- (log) =>
- {
- Assert.Equal(1004, log.EventId);
- Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
- });
- }
+ var sink = new TestSink();
+ var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true));
- [Fact]
- public async Task MatchAsync_RunsApplicableEndpointSelectorPolicies()
- {
- // Arrange
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/One";
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ Assert.Same(endpointDataSource.Endpoints[0], httpContext.GetEndpoint());
+
+ Assert.Collection(
+ sink.Writes,
+ (log) =>
+ {
+ Assert.Equal(1001, log.EventId);
+ Assert.Equal("3 candidate(s) found for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1005, log.EventId);
+ Assert.Equal("Endpoint '/{one}' with route pattern '/{one}' is valid for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1003, log.EventId);
+ Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1004, log.EventId);
+ Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1002, log.EventId);
+ Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message);
+ },
+ (log) =>
+ {
+ Assert.Equal(1004, log.EventId);
+ Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message);
+ });
+ }
+
+ [Fact]
+ public async Task MatchAsync_RunsApplicableEndpointSelectorPolicies()
+ {
+ // Arrange
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/test/{id:alpha}", 0),
CreateEndpoint("/test/{id:int}", 0),
CreateEndpoint("/test/{id}", 0),
});
- var policy = new Mock<MatcherPolicy>();
- policy
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true);
- policy
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Returns<HttpContext, CandidateSet>((c, cs) =>
- {
- cs.SetValidity(1, false);
- return Task.CompletedTask;
- });
+ var policy = new Mock<MatcherPolicy>();
+ policy
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true);
+ policy
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Returns<HttpContext, CandidateSet>((c, cs) =>
+ {
+ cs.SetValidity(1, false);
+ return Task.CompletedTask;
+ });
- var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, });
+ var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, });
- var httpContext = CreateContext();
- httpContext.Request.Path = "/test/17";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/test/17";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Same(dataSource.Endpoints[2], httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Same(dataSource.Endpoints[2], httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_SkipsNonApplicableEndpointSelectorPolicies()
- {
- // Arrange
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_SkipsNonApplicableEndpointSelectorPolicies()
+ {
+ // Arrange
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/test/{id:alpha}", 0),
CreateEndpoint("/test/{id:int}", 0),
CreateEndpoint("/test/{id}", 0),
});
- var policy = new Mock<MatcherPolicy>();
- policy
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(false);
- policy
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Returns<HttpContext, CandidateSet>((c, cs) =>
- {
- throw null; // Won't be called.
+ var policy = new Mock<MatcherPolicy>();
+ policy
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(false);
+ policy
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Returns<HttpContext, CandidateSet>((c, cs) =>
+ {
+ throw null; // Won't be called.
});
- var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, });
+ var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, });
- var httpContext = CreateContext();
- httpContext.Request.Path = "/test/17";
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/test/17";
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.Same(dataSource.Endpoints[1], httpContext.GetEndpoint());
- }
+ // Assert
+ Assert.Same(dataSource.Endpoints[1], httpContext.GetEndpoint());
+ }
- [Fact]
- public async Task MatchAsync_RunsEndpointSelectorPolicies_CanShortCircuit()
- {
- // Arrange
- var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
+ [Fact]
+ public async Task MatchAsync_RunsEndpointSelectorPolicies_CanShortCircuit()
+ {
+ // Arrange
+ var dataSource = new DefaultEndpointDataSource(new List<Endpoint>
{
CreateEndpoint("/test/{id:alpha}", 0),
CreateEndpoint("/test/{id:int}", 0),
CreateEndpoint("/test/{id}", 0),
});
- var policy1 = new Mock<MatcherPolicy>();
- policy1
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true);
- policy1
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Returns<HttpContext, CandidateSet>((c, cs) =>
- {
- c.SetEndpoint(cs[0].Endpoint);
- return Task.CompletedTask;
- });
+ var policy1 = new Mock<MatcherPolicy>();
+ policy1
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true);
+ policy1
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Returns<HttpContext, CandidateSet>((c, cs) =>
+ {
+ c.SetEndpoint(cs[0].Endpoint);
+ return Task.CompletedTask;
+ });
- // This should never run, it's after policy1 which short circuits
- var policy2 = new Mock<MatcherPolicy>();
- policy2
- .SetupGet(p => p.Order)
- .Returns(1000);
- policy2
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true);
- policy2
- .As<IEndpointSelectorPolicy>()
- .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
- .Throws(new InvalidOperationException());
-
- var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy1.Object, policy2.Object, });
-
- var httpContext = CreateContext();
- httpContext.Request.Path = "/test/17";
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- Assert.Same(dataSource.Endpoints[0], httpContext.GetEndpoint());
- }
+ // This should never run, it's after policy1 which short circuits
+ var policy2 = new Mock<MatcherPolicy>();
+ policy2
+ .SetupGet(p => p.Order)
+ .Returns(1000);
+ policy2
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true);
+ policy2
+ .As<IEndpointSelectorPolicy>()
+ .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
+ .Throws(new InvalidOperationException());
+
+ var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy1.Object, policy2.Object, });
+
+ var httpContext = CreateContext();
+ httpContext.Request.Path = "/test/17";
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ Assert.Same(dataSource.Endpoints[0], httpContext.GetEndpoint());
+ }
- private HttpContext CreateContext()
- {
- return new DefaultHttpContext();
- }
+ private HttpContext CreateContext()
+ {
+ return new DefaultHttpContext();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/DictionaryJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/DictionaryJumpTableTest.cs
index e057dedb32..416333ef5a 100644
--- a/src/Http/Routing/test/UnitTests/Matching/DictionaryJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/DictionaryJumpTableTest.cs
@@ -1,16 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class DictionaryJumpTableTest : MultipleEntryJumpTableTest
{
- public class DictionaryJumpTableTest : MultipleEntryJumpTableTest
+ internal override JumpTable CreateTable(
+ int defaultDestination,
+ int exitDestination,
+ params (string text, int destination)[] entries)
{
- internal override JumpTable CreateTable(
- int defaultDestination,
- int exitDestination,
- params (string text, int destination)[] entries)
- {
- return new DictionaryJumpTable(defaultDestination, exitDestination, entries);
- }
+ return new DictionaryJumpTable(defaultDestination, exitDestination, entries);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/EndpointComparerTest.cs b/src/Http/Routing/test/UnitTests/Matching/EndpointComparerTest.cs
index a1364bebc5..24d504b2b9 100644
--- a/src/Http/Routing/test/UnitTests/Matching/EndpointComparerTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/EndpointComparerTest.cs
@@ -6,272 +6,271 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class RouteEndpointComparerTest
{
- public class RouteEndpointComparerTest
+ [Fact]
+ public void Compare_PrefersOrder_IfDifferent()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/", order: 1);
+ var endpoint2 = CreateEndpoint("/api/foo", order: -1);
+
+ var comparer = CreateComparer();
+
+ // Act
+ var result = comparer.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void Compare_PrefersPrecedence_IfOrderIsSame()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/api/foo", order: 1);
+ var endpoint2 = CreateEndpoint("/", order: 1);
+
+ var comparer = CreateComparer();
+
+ // Act
+ var result = comparer.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void Compare_PrefersPolicy_IfPrecedenceIsSame()
{
- [Fact]
- public void Compare_PrefersOrder_IfDifferent()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/", order: 1);
- var endpoint2 = CreateEndpoint("/api/foo", order: -1);
+ // Arrange
+ var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/", order: 1);
- var comparer = CreateComparer();
+ var comparer = CreateComparer(new TestMetadata1Policy());
- // Act
- var result = comparer.Compare(endpoint1, endpoint2);
+ // Act
+ var result = comparer.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(-1, result);
+ }
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public void Compare_PrefersPrecedence_IfOrderIsSame()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/api/foo", order: 1);
- var endpoint2 = CreateEndpoint("/", order: 1);
+ [Fact]
+ public void Compare_PrefersSecondPolicy_IfFirstPolicyIsSame()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/", order: 1, new TestMetadata1(), new TestMetadata2());
- var comparer = CreateComparer();
+ var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
- // Act
- var result = comparer.Compare(endpoint1, endpoint2);
+ // Act
+ var result = comparer.Compare(endpoint1, endpoint2);
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public void Compare_PrefersPolicy_IfPrecedenceIsSame()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/", order: 1);
+ // Assert
+ Assert.Equal(1, result);
+ }
- var comparer = CreateComparer(new TestMetadata1Policy());
+ [Fact]
+ public void Compare_PrefersTemplate_IfOtherCriteriaIsSame()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/bar", order: 1, new TestMetadata1());
- // Act
- var result = comparer.Compare(endpoint1, endpoint2);
+ var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
- // Assert
- Assert.Equal(-1, result);
- }
-
- [Fact]
- public void Compare_PrefersSecondPolicy_IfFirstPolicyIsSame()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/", order: 1, new TestMetadata1(), new TestMetadata2());
+ // Act
+ var result = comparer.Compare(endpoint1, endpoint2);
- var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
+ // Assert
+ Assert.True(result > 0);
+ }
- // Act
- var result = comparer.Compare(endpoint1, endpoint2);
+ [Fact]
+ public void Compare_ReturnsZero_WhenIdentical()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public void Compare_PrefersTemplate_IfOtherCriteriaIsSame()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/bar", order: 1, new TestMetadata1());
+ var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
- var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
+ // Act
+ var result = comparer.Compare(endpoint1, endpoint2);
- // Act
- var result = comparer.Compare(endpoint1, endpoint2);
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void Equals_NotEqual_IfOrderDifferent()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/", order: 1);
+ var endpoint2 = CreateEndpoint("/api/foo", order: -1);
+
+ var comparer = CreateComparer();
+
+ // Act
+ var result = comparer.Equals(endpoint1, endpoint2);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void Equals_NotEqual_IfPrecedenceDifferent()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/api/foo", order: 1);
+ var endpoint2 = CreateEndpoint("/", order: 1);
+
+ var comparer = CreateComparer();
+
+ // Act
+ var result = comparer.Equals(endpoint1, endpoint2);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void Equals_NotEqual_IfFirstPolicyDifferent()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/", order: 1);
+
+ var comparer = CreateComparer(new TestMetadata1Policy());
+
+ // Act
+ var result = comparer.Equals(endpoint1, endpoint2);
+
+ // Assert
+ Assert.False(result);
+ }
- // Assert
- Assert.True(result > 0);
- }
-
- [Fact]
- public void Compare_ReturnsZero_WhenIdentical()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
-
- var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
-
- // Act
- var result = comparer.Compare(endpoint1, endpoint2);
-
- // Assert
- Assert.Equal(0, result);
- }
-
- [Fact]
- public void Equals_NotEqual_IfOrderDifferent()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/", order: 1);
- var endpoint2 = CreateEndpoint("/api/foo", order: -1);
-
- var comparer = CreateComparer();
-
- // Act
- var result = comparer.Equals(endpoint1, endpoint2);
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- public void Equals_NotEqual_IfPrecedenceDifferent()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/api/foo", order: 1);
- var endpoint2 = CreateEndpoint("/", order: 1);
-
- var comparer = CreateComparer();
-
- // Act
- var result = comparer.Equals(endpoint1, endpoint2);
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- public void Equals_NotEqual_IfFirstPolicyDifferent()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/", order: 1);
-
- var comparer = CreateComparer(new TestMetadata1Policy());
-
- // Act
- var result = comparer.Equals(endpoint1, endpoint2);
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- public void Equals_NotEqual_IfSecondPolicyDifferent()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/", order: 1, new TestMetadata1(), new TestMetadata2());
-
- var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
-
- // Act
- var result = comparer.Equals(endpoint1, endpoint2);
-
- // Assert
- Assert.False(result);
- }
-
- [Fact]
- public void Equals_Equals_WhenTemplateIsDifferent()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
- var endpoint2 = CreateEndpoint("/bar", order: 1, new TestMetadata1());
-
- var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
-
- // Act
- var result = comparer.Equals(endpoint1, endpoint2);
-
- // Assert
- Assert.True(result);
- }
-
- [Fact]
- public void Sort_MoreSpecific_FirstInList()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/foo", order: -1);
- var endpoint2 = CreateEndpoint("/bar/{baz}", order: -1);
- var endpoint3 = CreateEndpoint("/bar", order: 0, new TestMetadata1());
- var endpoint4 = CreateEndpoint("/foo", order: 0, new TestMetadata2());
- var endpoint5 = CreateEndpoint("/foo", order: 0);
- var endpoint6 = CreateEndpoint("/a{baz}", order: 0, new TestMetadata1(), new TestMetadata2());
- var endpoint7 = CreateEndpoint("/bar{baz}", order: 0, new TestMetadata1(), new TestMetadata2());
-
- // Endpoints listed in reverse of the desired order.
- var list = new List<RouteEndpoint>() { endpoint7, endpoint6, endpoint5, endpoint4, endpoint3, endpoint2, endpoint1, };
-
- var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
-
- // Act
- list.Sort(comparer);
-
- // Assert
- Assert.Collection(
- list,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e),
- e => Assert.Same(endpoint3, e),
- e => Assert.Same(endpoint4, e),
- e => Assert.Same(endpoint5, e),
- e => Assert.Same(endpoint6, e),
- e => Assert.Same(endpoint7, e));
- }
-
- [Fact]
- public void Compare_PatternOrder_OrdinalIgnoreCaseSort()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/I", order: 0);
- var endpoint2 = CreateEndpoint("/i", order: 0);
- var endpoint3 = CreateEndpoint("/\u0131", order: 0); // Turkish lowercase i
-
- var list = new List<RouteEndpoint>() { endpoint1, endpoint2, endpoint3 };
-
- var comparer = CreateComparer();
-
- // Act
- list.Sort(comparer);
-
- // Assert
- Assert.Collection(
- list,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e),
- e => Assert.Same(endpoint3, e));
- }
-
- private static RouteEndpoint CreateEndpoint(string template, int order, params object[] metadata)
- {
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template),
- order,
- new EndpointMetadataCollection(metadata),
- "test: " + template);
- }
-
- private static EndpointComparer CreateComparer(params IEndpointComparerPolicy[] policies)
- {
- return new EndpointComparer(policies);
- }
-
- private class TestMetadata1
- {
- }
-
- private class TestMetadata1Policy : IEndpointComparerPolicy
- {
- public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
- }
-
- private class TestMetadata2
- {
- }
-
- private class TestMetadata2Policy : IEndpointComparerPolicy
- {
- public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata2>.Default;
- }
+ [Fact]
+ public void Equals_NotEqual_IfSecondPolicyDifferent()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/", order: 1, new TestMetadata1(), new TestMetadata2());
+
+ var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
+
+ // Act
+ var result = comparer.Equals(endpoint1, endpoint2);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void Equals_Equals_WhenTemplateIsDifferent()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/foo", order: 1, new TestMetadata1());
+ var endpoint2 = CreateEndpoint("/bar", order: 1, new TestMetadata1());
+
+ var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
+
+ // Act
+ var result = comparer.Equals(endpoint1, endpoint2);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void Sort_MoreSpecific_FirstInList()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/foo", order: -1);
+ var endpoint2 = CreateEndpoint("/bar/{baz}", order: -1);
+ var endpoint3 = CreateEndpoint("/bar", order: 0, new TestMetadata1());
+ var endpoint4 = CreateEndpoint("/foo", order: 0, new TestMetadata2());
+ var endpoint5 = CreateEndpoint("/foo", order: 0);
+ var endpoint6 = CreateEndpoint("/a{baz}", order: 0, new TestMetadata1(), new TestMetadata2());
+ var endpoint7 = CreateEndpoint("/bar{baz}", order: 0, new TestMetadata1(), new TestMetadata2());
+
+ // Endpoints listed in reverse of the desired order.
+ var list = new List<RouteEndpoint>() { endpoint7, endpoint6, endpoint5, endpoint4, endpoint3, endpoint2, endpoint1, };
+
+ var comparer = CreateComparer(new TestMetadata1Policy(), new TestMetadata2Policy());
+
+ // Act
+ list.Sort(comparer);
+
+ // Assert
+ Assert.Collection(
+ list,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e),
+ e => Assert.Same(endpoint3, e),
+ e => Assert.Same(endpoint4, e),
+ e => Assert.Same(endpoint5, e),
+ e => Assert.Same(endpoint6, e),
+ e => Assert.Same(endpoint7, e));
+ }
+
+ [Fact]
+ public void Compare_PatternOrder_OrdinalIgnoreCaseSort()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/I", order: 0);
+ var endpoint2 = CreateEndpoint("/i", order: 0);
+ var endpoint3 = CreateEndpoint("/\u0131", order: 0); // Turkish lowercase i
+
+ var list = new List<RouteEndpoint>() { endpoint1, endpoint2, endpoint3 };
+
+ var comparer = CreateComparer();
+
+ // Act
+ list.Sort(comparer);
+
+ // Assert
+ Assert.Collection(
+ list,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e),
+ e => Assert.Same(endpoint3, e));
+ }
+
+ private static RouteEndpoint CreateEndpoint(string template, int order, params object[] metadata)
+ {
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template),
+ order,
+ new EndpointMetadataCollection(metadata),
+ "test: " + template);
+ }
+
+ private static EndpointComparer CreateComparer(params IEndpointComparerPolicy[] policies)
+ {
+ return new EndpointComparer(policies);
+ }
+
+ private class TestMetadata1
+ {
+ }
+
+ private class TestMetadata1Policy : IEndpointComparerPolicy
+ {
+ public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
+ }
+
+ private class TestMetadata2
+ {
+ }
+
+ private class TestMetadata2Policy : IEndpointComparerPolicy
+ {
+ public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata2>.Default;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/EndpointMetadataComparerTest.cs b/src/Http/Routing/test/UnitTests/Matching/EndpointMetadataComparerTest.cs
index 25ba6e35ef..b4ed3ec2e8 100644
--- a/src/Http/Routing/test/UnitTests/Matching/EndpointMetadataComparerTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/EndpointMetadataComparerTest.cs
@@ -5,87 +5,86 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class EndpointMetadataComparerTest
{
- public class EndpointMetadataComparerTest
+ [Fact]
+ public void Compare_EndpointWithMetadata_MoreSpecific()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test2");
+
+ // Act
+ var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(-1, result);
+ }
+
+ [Fact]
+ public void Compare_EndpointWithMetadata_ReverseOrder_MoreSpecific()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test2");
+
+ // Act
+ var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void Compare_BothEndpointsWithMetadata_Equal()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test2");
+
+ // Act
+ var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void Compare_BothEndpointsWithoutMetadata_Equal()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test2");
+
+ // Act
+ var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
+
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void Sort_EndpointWithMetadata_FirstInList()
+ {
+ // Arrange
+ var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test1");
+ var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test2");
+
+ var list = new List<Endpoint>() { endpoint2, endpoint1, };
+
+ // Act
+ list.Sort(EndpointMetadataComparer<TestMetadata>.Default);
+
+ // Assert
+ Assert.Collection(
+ list,
+ e => Assert.Same(endpoint1, e),
+ e => Assert.Same(endpoint2, e));
+ }
+
+ private class TestMetadata
{
- [Fact]
- public void Compare_EndpointWithMetadata_MoreSpecific()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test2");
-
- // Act
- var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
-
- // Assert
- Assert.Equal(-1, result);
- }
-
- [Fact]
- public void Compare_EndpointWithMetadata_ReverseOrder_MoreSpecific()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test2");
-
- // Act
- var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
-
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public void Compare_BothEndpointsWithMetadata_Equal()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test2");
-
- // Act
- var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
-
- // Assert
- Assert.Equal(0, result);
- }
-
- [Fact]
- public void Compare_BothEndpointsWithoutMetadata_Equal()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test2");
-
- // Act
- var result = EndpointMetadataComparer<TestMetadata>.Default.Compare(endpoint1, endpoint2);
-
- // Assert
- Assert.Equal(0, result);
- }
-
- [Fact]
- public void Sort_EndpointWithMetadata_FirstInList()
- {
- // Arrange
- var endpoint1 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { new TestMetadata(), }), "test1");
- var endpoint2 = new Endpoint(TestConstants.EmptyRequestDelegate, new EndpointMetadataCollection(new object[] { }), "test2");
-
- var list = new List<Endpoint>() { endpoint2, endpoint1, };
-
- // Act
- list.Sort(EndpointMetadataComparer<TestMetadata>.Default);
-
- // Assert
- Assert.Collection(
- list,
- e => Assert.Same(endpoint1, e),
- e => Assert.Same(endpoint2, e));
- }
-
- private class TestMetadata
- {
- }
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/FastPathTokenizerTest.cs b/src/Http/Routing/test/UnitTests/Matching/FastPathTokenizerTest.cs
index 7276f744b7..1086ee5018 100644
--- a/src/Http/Routing/test/UnitTests/Matching/FastPathTokenizerTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/FastPathTokenizerTest.cs
@@ -4,130 +4,129 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class FastPathTokenizerTest
{
- public class FastPathTokenizerTest
+ // Generally this will only happen in tests when the HttpContext hasn't been
+ // initialized. We still don't want to crash in this case.
+ [Fact]
+ public void Tokenize_EmptyString()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[1];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("", segments);
+
+ // Assert
+ Assert.Equal(0, count);
+ }
+
+ [Fact]
+ public void Tokenize_RootPath()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[1];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("/", segments);
+
+ // Assert
+ Assert.Equal(0, count);
+ }
+
+ [Fact]
+ public void Tokenize_SingleSegment()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[1];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("/abc", segments);
+
+ // Assert
+ Assert.Equal(1, count);
+ Assert.Equal(new PathSegment(1, 3), segments[0]);
+ }
+
+ [Fact]
+ public void Tokenize_WithSomeSegments()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[3];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("/a/b/c", segments);
+
+ // Assert
+ Assert.Equal(3, count);
+ Assert.Equal(new PathSegment(1, 1), segments[0]);
+ Assert.Equal(new PathSegment(3, 1), segments[1]);
+ Assert.Equal(new PathSegment(5, 1), segments[2]);
+ }
+
+ [Fact] // Empty trailing / is ignored
+ public void Tokenize_WithSomeSegments_TrailingSlash()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[3];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("/a/b/c/", segments);
+
+ // Assert
+ Assert.Equal(3, count);
+ Assert.Equal(new PathSegment(1, 1), segments[0]);
+ Assert.Equal(new PathSegment(3, 1), segments[1]);
+ Assert.Equal(new PathSegment(5, 1), segments[2]);
+ }
+
+ [Fact]
+ public void Tokenize_LongerSegments()
{
- // Generally this will only happen in tests when the HttpContext hasn't been
- // initialized. We still don't want to crash in this case.
- [Fact]
- public void Tokenize_EmptyString()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[1];
-
- // Act
- var count = FastPathTokenizer.Tokenize("", segments);
-
- // Assert
- Assert.Equal(0, count);
- }
-
- [Fact]
- public void Tokenize_RootPath()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[1];
-
- // Act
- var count = FastPathTokenizer.Tokenize("/", segments);
-
- // Assert
- Assert.Equal(0, count);
- }
-
- [Fact]
- public void Tokenize_SingleSegment()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[1];
-
- // Act
- var count = FastPathTokenizer.Tokenize("/abc", segments);
-
- // Assert
- Assert.Equal(1, count);
- Assert.Equal(new PathSegment(1, 3), segments[0]);
- }
-
- [Fact]
- public void Tokenize_WithSomeSegments()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[3];
-
- // Act
- var count = FastPathTokenizer.Tokenize("/a/b/c", segments);
-
- // Assert
- Assert.Equal(3, count);
- Assert.Equal(new PathSegment(1, 1), segments[0]);
- Assert.Equal(new PathSegment(3, 1), segments[1]);
- Assert.Equal(new PathSegment(5, 1), segments[2]);
- }
-
- [Fact] // Empty trailing / is ignored
- public void Tokenize_WithSomeSegments_TrailingSlash()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[3];
-
- // Act
- var count = FastPathTokenizer.Tokenize("/a/b/c/", segments);
-
- // Assert
- Assert.Equal(3, count);
- Assert.Equal(new PathSegment(1, 1), segments[0]);
- Assert.Equal(new PathSegment(3, 1), segments[1]);
- Assert.Equal(new PathSegment(5, 1), segments[2]);
- }
-
- [Fact]
- public void Tokenize_LongerSegments()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[3];
-
- // Act
- var count = FastPathTokenizer.Tokenize("/aaa/bb/ccccc", segments);
-
- // Assert
- Assert.Equal(3, count);
- Assert.Equal(new PathSegment(1, 3), segments[0]);
- Assert.Equal(new PathSegment(5, 2), segments[1]);
- Assert.Equal(new PathSegment(8, 5), segments[2]);
- }
-
- [Fact]
- public void Tokenize_EmptySegments()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[3];
-
- // Act
- var count = FastPathTokenizer.Tokenize("///c", segments);
-
- // Assert
- Assert.Equal(3, count);
- Assert.Equal(new PathSegment(1, 0), segments[0]);
- Assert.Equal(new PathSegment(2, 0), segments[1]);
- Assert.Equal(new PathSegment(3, 1), segments[2]);
- }
-
- [Fact]
- public void Tokenize_TooManySegments()
- {
- // Arrange
- Span<PathSegment> segments = stackalloc PathSegment[3];
-
- // Act
- var count = FastPathTokenizer.Tokenize("/a/b/c/d", segments);
-
- // Assert
- Assert.Equal(3, count);
- Assert.Equal(new PathSegment(1, 1), segments[0]);
- Assert.Equal(new PathSegment(3, 1), segments[1]);
- Assert.Equal(new PathSegment(5, 1), segments[2]);
- }
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[3];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("/aaa/bb/ccccc", segments);
+
+ // Assert
+ Assert.Equal(3, count);
+ Assert.Equal(new PathSegment(1, 3), segments[0]);
+ Assert.Equal(new PathSegment(5, 2), segments[1]);
+ Assert.Equal(new PathSegment(8, 5), segments[2]);
+ }
+
+ [Fact]
+ public void Tokenize_EmptySegments()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[3];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("///c", segments);
+
+ // Assert
+ Assert.Equal(3, count);
+ Assert.Equal(new PathSegment(1, 0), segments[0]);
+ Assert.Equal(new PathSegment(2, 0), segments[1]);
+ Assert.Equal(new PathSegment(3, 1), segments[2]);
+ }
+
+ [Fact]
+ public void Tokenize_TooManySegments()
+ {
+ // Arrange
+ Span<PathSegment> segments = stackalloc PathSegment[3];
+
+ // Act
+ var count = FastPathTokenizer.Tokenize("/a/b/c/d", segments);
+
+ // Assert
+ Assert.Equal(3, count);
+ Assert.Equal(new PathSegment(1, 1), segments[0]);
+ Assert.Equal(new PathSegment(3, 1), segments[1]);
+ Assert.Equal(new PathSegment(5, 1), segments[2]);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/FullFeaturedMatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/FullFeaturedMatcherConformanceTest.cs
index eb5e343532..9123c397ca 100644
--- a/src/Http/Routing/test/UnitTests/Matching/FullFeaturedMatcherConformanceTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/FullFeaturedMatcherConformanceTest.cs
@@ -7,412 +7,412 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// This class includes features that we have not yet implemented in the DFA
+// and instruction matchers.
+//
+// As those matchers add features we can move tests from this class into
+// MatcherConformanceTest and delete this.
+public abstract class FullFeaturedMatcherConformanceTest : MatcherConformanceTest
{
- // This class includes features that we have not yet implemented in the DFA
- // and instruction matchers.
- //
- // As those matchers add features we can move tests from this class into
- // MatcherConformanceTest and delete this.
- public abstract class FullFeaturedMatcherConformanceTest : MatcherConformanceTest
+ [Theory]
+ [InlineData("/a/{b=15}", "/a/b", new string[] { "b", }, new string[] { "b", })]
+ [InlineData("/a/{b=15}", "/a/", new string[] { "b", }, new string[] { "15", })]
+ [InlineData("/a/{b=15}", "/a", new string[] { "b", }, new string[] { "15", })]
+ [InlineData("/{a}/{b=15}", "/54/b", new string[] { "a", "b", }, new string[] { "54", "b", })]
+ [InlineData("/{a=19}/{b=15}", "/54/b", new string[] { "a", "b", }, new string[] { "54", "b", })]
+ [InlineData("/{a=19}/{b=15}", "/54/", new string[] { "a", "b", }, new string[] { "54", "15", })]
+ [InlineData("/{a=19}/{b=15}", "/54", new string[] { "a", "b", }, new string[] { "54", "15", })]
+ [InlineData("/{a=19}/{b=15}", "/", new string[] { "a", "b", }, new string[] { "19", "15", })]
+ public virtual async Task Match_DefaultValues(string template, string path, string[] keys, string[] values)
{
- [Theory]
- [InlineData("/a/{b=15}", "/a/b", new string[] { "b", }, new string[] { "b", })]
- [InlineData("/a/{b=15}", "/a/", new string[] { "b", }, new string[] { "15", })]
- [InlineData("/a/{b=15}", "/a", new string[] { "b", }, new string[] { "15", })]
- [InlineData("/{a}/{b=15}", "/54/b", new string[] { "a", "b", }, new string[] { "54", "b", })]
- [InlineData("/{a=19}/{b=15}", "/54/b", new string[] { "a", "b", }, new string[] { "54", "b", })]
- [InlineData("/{a=19}/{b=15}", "/54/", new string[] { "a", "b", }, new string[] { "54", "15", })]
- [InlineData("/{a=19}/{b=15}", "/54", new string[] { "a", "b", }, new string[] { "54", "15", })]
- [InlineData("/{a=19}/{b=15}", "/", new string[] { "a", "b", }, new string[] { "19", "15", })]
- public virtual async Task Match_DefaultValues(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
- [Fact]
- public virtual async Task Match_NonInlineDefaultValues()
- {
- // Arrange
- var endpoint = CreateEndpoint("/a/{b}/{c}", new { b = "17", c = "18", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/a");
+ [Fact]
+ public virtual async Task Match_NonInlineDefaultValues()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/a/{b}/{c}", new { b = "17", c = "18", });
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/a");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, new { b = "17", c = "18", });
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, new { b = "17", c = "18", });
+ }
- [Fact]
- public virtual async Task Match_ExtraDefaultValues()
- {
- // Arrange
- var endpoint = CreateEndpoint("/a/{b}/{c}", new { b = "17", c = "18", d = "19" });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/a");
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, new { b = "17", c = "18", d = "19" });
- }
-
- [Theory]
- [InlineData("/a/{b=15}", "/54/b")]
- [InlineData("/a/{b=15}", "/54/")]
- [InlineData("/a/{b=15}", "/54")]
- [InlineData("/a/{b=15}", "/a//")]
- [InlineData("/a/{b=15}", "/54/43/23")]
- [InlineData("/{a=19}/{b=15}", "/54/b/c")]
- [InlineData("/a/{b=15}/c", "/a/b")] // Intermediate default values don't act like optional segments
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d")]
- public virtual async Task NotMatch_DefaultValues(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- [Theory]
- [InlineData("/{a?}/{b?}/{c?}", "/", null, null)]
- [InlineData("/{a?}/{b?}/{c?}", "/a", new[] { "a", }, new[] { "a", })]
- [InlineData("/{a?}/{b?}/{c?}", "/a/", new[] { "a", }, new[] { "a", })]
- [InlineData("/{a?}/{b?}/{c?}", "/a/b", new[] { "a", "b", }, new[] { "a", "b", })]
- [InlineData("/{a?}/{b?}/{c?}", "/a/b/", new[] { "a", "b", }, new[] { "a", "b", })]
- [InlineData("/{a?}/{b?}/{c?}", "/a/b/c", new[] { "a", "b", "c", }, new[] { "a", "b", "c", })]
- [InlineData("/{a?}/{b?}/{c?}", "/a/b/c/", new[] { "a", "b", "c", }, new[] { "a", "b", "c", })]
- [InlineData("/{c}/{a?}", "/h/i", new[] { "c", "a", }, new[] { "h", "i", })]
- [InlineData("/{c}/{a?}", "/h/", new[] { "c", }, new[] { "h", })]
- [InlineData("/{c}/{a?}", "/h", new[] { "c", }, new[] { "h", })]
- [InlineData("/{c?}/{a?}", "/", null, null)]
- [InlineData("/{c}/{a?}/{id?}", "/h/i/18", new[] { "c", "a", "id", }, new[] { "h", "i", "18", })]
- [InlineData("/{c}/{a?}/{id?}", "/h/i", new[] { "c", "a", }, new[] { "h", "i", })]
- [InlineData("/{c}/{a?}/{id?}", "/h", new[] { "c", }, new[] { "h", })]
- [InlineData("template/{p:int?}", "/template/5", new[] { "p", }, new[] { "5", })]
- [InlineData("template/{p:int?}", "/template", null, null)]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e", new[] { "b", "d", "f" }, new[] { "b", "d", null, })]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e/f", new[] { "b", "d", "f", }, new[] { "b", "d", "f", })]
- public virtual async Task Match_OptionalParameter(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
-
- [Theory]
- [InlineData("/{a?}/{b?}/{c?}", "///")]
- [InlineData("/{a?}/{b?}/{c?}", "/a//")]
- [InlineData("/{a?}/{b?}/{c?}", "/a/b//")]
- [InlineData("/{a?}/{b?}/{c?}", "//b//")]
- [InlineData("/{a?}/{b?}/{c?}", "///c")]
- [InlineData("/{a?}/{b?}/{c?}", "///c/")]
- [InlineData("/{a?}/{b?}/{c?}", "/a/b/c/d")]
- [InlineData("/a/{b?}/{c?}", "/")]
- [InlineData("template/{parameter:int?}", "/template/qwer")]
- public virtual async Task NotMatch_OptionalParameter(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- [Theory]
- [InlineData("/{a}/{*b}", "/a", new[] { "a", "b", }, new[] { "a", null, })]
- [InlineData("/{a}/{*b}", "/a/", new[] { "a", "b", }, new[] { "a", null, })]
- [InlineData("/{a}/{*b=b}", "/a", new[] { "a", "b", }, new[] { "a", "b", })]
- [InlineData("/{a}/{*b=b}", "/a/", new[] { "a", "b", }, new[] { "a", "b", })]
- [InlineData("/{a}/{*b=b}", "/a/hello", new[] { "a", "b", }, new[] { "a", "hello", })]
- [InlineData("/{a}/{*b=b}", "/a/hello/goodbye", new[] { "a", "b", }, new[] { "a", "hello/goodbye", })]
- [InlineData("/{a}/{*b=b}", "/a/b//", new[] { "a", "b", }, new[] { "a", "b//", })]
- [InlineData("/{a}/{*b=b}", "/a/b/c/", new[] { "a", "b", }, new[] { "a", "b/c/", })]
- [InlineData("/{a=1}/{b=2}/{c=3}/{d=4}", "/a/b/c", new[] { "a", "b", "c", "d", }, new[] { "a", "b", "c", "4", })]
- [InlineData("a/{*path:regex(10/20/30)}", "/a/10/20/30", new[] { "path", }, new[] { "10/20/30" })]
- public virtual async Task Match_CatchAllParameter(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
-
- // Historically catchall segments don't match an empty segment, but only if it's
- // the first one. So `/a/b//` would match, but `/a//` would not. This is pretty
- // weird and inconsistent with the intent of using a catch all. The DfaMatcher
- // fixes this issue.
- [Theory]
- [InlineData("/{a}/{*b=b}", "/a///", new[] { "a", "b", }, new[] { "a", "//" })]
- [InlineData("/{a}/{*b=b}", "/a//c/", new[] { "a", "b", }, new[] { "a", "/c/" })]
- public virtual async Task Quirks_CatchAllParameter(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
-
- // Need to access these to prevent a warning from the xUnit analyzer.
- // Some of these tests will match (and process the values) and some will not.
- GC.KeepAlive(keys);
- GC.KeepAlive(values);
- }
-
- [Theory]
- [InlineData("{p}x{s}", "/xxxxxxxxxx", new[] { "p", "s" }, new[] { "xxxxxxxx", "x", })]
- [InlineData("{p}xyz{s}", "/xxxxyzxyzxxxxxxyz", new[] { "p", "s" }, new[] { "xxxxyz", "xxxxxxyz", })]
- [InlineData("{p}xyz{s}", "/abcxxxxyzxyzxxxxxxyzxx", new[] { "p", "s" }, new[] { "abcxxxxyzxyzxxxxx", "xx", })]
- [InlineData("{p}xyz{s}", "/xyzxyzxyzxyzxyz", new[] { "p", "s" }, new[] { "xyzxyzxyz", "xyz", })]
- [InlineData("{p}xyz{s}", "/xyzxyzxyzxyzxyz1", new[] { "p", "s" }, new[] { "xyzxyzxyzxyz", "1", })]
- [InlineData("{p}xyz{s}", "/xyzxyzxyz", new[] { "p", "s" }, new[] { "xyz", "xyz", })]
- [InlineData("{p}aa{s}", "/aaaaa", new[] { "p", "s" }, new[] { "aa", "a", })]
- [InlineData("{p}aaa{s}", "/aaaaa", new[] { "p", "s" }, new[] { "a", "a", })]
- [InlineData("language/{lang=en}-{region=US}", "/language/xx-yy", new[] { "lang", "region" }, new[] { "xx", "yy", })]
- [InlineData("language/{lang}-{region}", "/language/en-US", new[] { "lang", "region" }, new[] { "en", "US", })]
- [InlineData("language/{lang}-{region}a", "/language/en-USa", new[] { "lang", "region" }, new[] { "en", "US", })]
- [InlineData("language/a{lang}-{region}", "/language/aen-US", new[] { "lang", "region" }, new[] { "en", "US", })]
- [InlineData("language/a{lang}-{region}a", "/language/aen-USa", new[] { "lang", "region" }, new[] { "en", "US", })]
- [InlineData("language/{lang}-", "/language/en-", new[] { "lang", }, new[] { "en", })]
- [InlineData("language/a{lang}", "/language/aen", new[] { "lang", }, new[] { "en", })]
- [InlineData("language/a{lang}a", "/language/aena", new[] { "lang", }, new[] { "en", })]
- public virtual async Task Match_ComplexSegment(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
-
- [Theory]
- [InlineData("language/a{lang}-{region}a", "/language/a-USa")]
- [InlineData("language/a{lang}-{region}a", "/language/aen-a")]
- [InlineData("language/{lang=en}-{region=US}", "/language")]
- [InlineData("language/{lang=en}-{region=US}", "/language/-")]
- [InlineData("language/{lang=en}-{region=US}", "/language/xx-")]
- [InlineData("language/{lang=en}-{region=US}", "/language/-xx")]
- public virtual async Task NotMatch_ComplexSegment(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", new[] { "p1", "p2", }, new[] { "foo", "bar" })]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo", new[] { "p1", }, new[] { "foo", })]
- [InlineData("moo/{p1}.{p2?}", "/moo/.foo", new[] { "p1", }, new[] { ".foo", })]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", new[] { "p1", "p2", }, new[] { "foo.", "bar" })]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", new[] { "p1", "p2", }, new[] { "foo.moo", "bar" })]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", new[] { "p1", }, new[] { "moo", })]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.foo.bar", new[] { "p1", "p2", }, new[] { "foo", "bar" })]
- [InlineData("moo/.{p2?}", "/moo/.foo", new[] { "p2", }, new[] { "foo", })]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", new[] { "p1", "p2", }, new[] { "foo", "moo" })]
- [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
- [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
- [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", new[] { "p1", "p3" }, new[] { "foo", "bar" })]
- [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", new[] { "p1", "p3" }, new[] { ".foo", "bar" })]
- [InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", new[] { "p1", "p2", "p3" }, new[] { "foo", "bar", "baz" })]
- public virtual async Task Match_OptionalSeparator(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
-
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
- [InlineData("moo/{p1}.{p2?}", "/moo/.")]
- [InlineData("moo/{p1}.{p2}", "/foo.")]
- [InlineData("moo/{p1}.{p2}", "/foo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
- [InlineData("moo/.{p2?}", "/moo/.")]
- [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
- public virtual async Task NotMatch_OptionalSeparator(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- // Most of are copied from old routing tests that date back to the VS 2010 era. Enjoy!
- [Theory]
- [InlineData("{Controller}.mvc/../{action}", "/Home.mvc/../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
- [InlineData("{Controller}.mvc/.../{action}", "/Home.mvc/.../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
- [InlineData("{Controller}.mvc/../../../{action}", "/Home.mvc/../../../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
- [InlineData("{Controller}.mvc!/{action}", "/Home.mvc!/index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
- [InlineData("../{Controller}.mvc", "/../Home.mvc", new string[] { "Controller", }, new string[] { "Home", })]
- [InlineData(@"\{Controller}.mvc", @"/\Home.mvc", new string[] { "Controller", }, new string[] { "Home", })]
- [InlineData(@"{Controller}.mvc\{id}\{Param1}", @"/Home.mvc\123\p1", new string[] { "Controller", "id", "Param1" }, new string[] { "Home", "123", "p1" })]
- [InlineData("(Controller).mvc", "/(Controller).mvc", new string[] { }, new string[] { })]
- [InlineData("Controller.mvc/ ", "/Controller.mvc/ ", new string[] { }, new string[] { })]
- [InlineData("Controller.mvc ", "/Controller.mvc ", new string[] { }, new string[] { })]
- public virtual async Task Match_WeirdCharacterCases(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
-
- [Theory]
- [InlineData("template/5", "template/{parameter:int}")]
- [InlineData("template/5", "template/{parameter}")]
- [InlineData("template/5", "template/{*parameter:int}")]
- [InlineData("template/5", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{parameter:alpha}")] // constraint doesn't match
- [InlineData("template/{parameter:int}", "template/{parameter}")]
- [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{parameter:int}", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{*parameter:int}")]
- [InlineData("template/{parameter}", "template/{*parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter}")]
- public virtual async Task Match_SelectEndpoint_BasedOnPrecedence(string template1, string template2)
- {
- // Arrange
- var expected = CreateEndpoint(template1);
- var other = CreateEndpoint(template2);
- var path = "/template/5";
-
- // Arrange
- var matcher = CreateMatcher(other, expected);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
-
- [Theory]
- [InlineData("template/5", "template/{parameter:int}")]
- [InlineData("template/5", "template/{parameter}")]
- [InlineData("template/5", "template/{*parameter:int}")]
- [InlineData("template/5", "template/{*parameter}")]
- [InlineData("template/{parameter:int}", "template/{parameter}")]
- [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{parameter:int}", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{*parameter:int}")]
- [InlineData("template/{parameter}", "template/{*parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter}")]
- [InlineData("template/5", "template/5")]
- [InlineData("template/{parameter:int}", "template/{parameter:int}")]
- [InlineData("template/{parameter}", "template/{parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{*parameter}", "template/{*parameter}")]
- public virtual async Task Match_SelectEndpoint_BasedOnOrder(string template1, string template2)
- {
- // Arrange
- var expected = CreateEndpoint(template1, order: 0);
- var other = CreateEndpoint(template2, order: 1);
- var path = "/template/5";
-
- // Arrange
- var matcher = CreateMatcher(other, expected);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
-
- [Theory]
- [InlineData("/", "")]
- [InlineData("/Literal1", "Literal1")]
- [InlineData("/Literal1/Literal2", "Literal1/Literal2")]
- [InlineData("/Literal1/Literal2/Literal3", "Literal1/Literal2/Literal3")]
- [InlineData("/Literal1/Literal2/Literal3/4", "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}")]
- [InlineData("/Literal1/Literal2/Literal3/Literal4", "Literal1/Literal2/Literal3/{*catchAll}")]
- [InlineData("/1", "{constrained1:int}")]
- [InlineData("/1/2", "{constrained1:int}/{constrained2:int}")]
- [InlineData("/1/2/3", "{constrained1:int}/{constrained2:int}/{constrained3:int}")]
- [InlineData("/1/2/3/4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}")]
- [InlineData("/1/2/3/CatchAll4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}")]
- [InlineData("/parameter1", "{parameter1}")]
- [InlineData("/parameter1/parameter2", "{parameter1}/{parameter2}")]
- [InlineData("/parameter1/parameter2/parameter3", "{parameter1}/{parameter2}/{parameter3}")]
- [InlineData("/parameter1/parameter2/parameter3/4", "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}")]
- [InlineData("/parameter1/parameter2/parameter3/CatchAll4", "{parameter1}/{parameter2}/{parameter3}/{*catchAll}")]
- public virtual async Task Match_IntegrationTest_MultipleEndpoints(string path, string expectedTemplate)
+ [Fact]
+ public virtual async Task Match_ExtraDefaultValues()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/a/{b}/{c}", new { b = "17", c = "18", d = "19" });
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/a");
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, new { b = "17", c = "18", d = "19" });
+ }
+
+ [Theory]
+ [InlineData("/a/{b=15}", "/54/b")]
+ [InlineData("/a/{b=15}", "/54/")]
+ [InlineData("/a/{b=15}", "/54")]
+ [InlineData("/a/{b=15}", "/a//")]
+ [InlineData("/a/{b=15}", "/54/43/23")]
+ [InlineData("/{a=19}/{b=15}", "/54/b/c")]
+ [InlineData("/a/{b=15}/c", "/a/b")] // Intermediate default values don't act like optional segments
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d")]
+ public virtual async Task NotMatch_DefaultValues(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ [Theory]
+ [InlineData("/{a?}/{b?}/{c?}", "/", null, null)]
+ [InlineData("/{a?}/{b?}/{c?}", "/a", new[] { "a", }, new[] { "a", })]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/", new[] { "a", }, new[] { "a", })]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/b", new[] { "a", "b", }, new[] { "a", "b", })]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/b/", new[] { "a", "b", }, new[] { "a", "b", })]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/b/c", new[] { "a", "b", "c", }, new[] { "a", "b", "c", })]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/b/c/", new[] { "a", "b", "c", }, new[] { "a", "b", "c", })]
+ [InlineData("/{c}/{a?}", "/h/i", new[] { "c", "a", }, new[] { "h", "i", })]
+ [InlineData("/{c}/{a?}", "/h/", new[] { "c", }, new[] { "h", })]
+ [InlineData("/{c}/{a?}", "/h", new[] { "c", }, new[] { "h", })]
+ [InlineData("/{c?}/{a?}", "/", null, null)]
+ [InlineData("/{c}/{a?}/{id?}", "/h/i/18", new[] { "c", "a", "id", }, new[] { "h", "i", "18", })]
+ [InlineData("/{c}/{a?}/{id?}", "/h/i", new[] { "c", "a", }, new[] { "h", "i", })]
+ [InlineData("/{c}/{a?}/{id?}", "/h", new[] { "c", }, new[] { "h", })]
+ [InlineData("template/{p:int?}", "/template/5", new[] { "p", }, new[] { "5", })]
+ [InlineData("template/{p:int?}", "/template", null, null)]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e", new[] { "b", "d", "f" }, new[] { "b", "d", null, })]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e/f", new[] { "b", "d", "f", }, new[] { "b", "d", "f", })]
+ public virtual async Task Match_OptionalParameter(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
+
+ [Theory]
+ [InlineData("/{a?}/{b?}/{c?}", "///")]
+ [InlineData("/{a?}/{b?}/{c?}", "/a//")]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/b//")]
+ [InlineData("/{a?}/{b?}/{c?}", "//b//")]
+ [InlineData("/{a?}/{b?}/{c?}", "///c")]
+ [InlineData("/{a?}/{b?}/{c?}", "///c/")]
+ [InlineData("/{a?}/{b?}/{c?}", "/a/b/c/d")]
+ [InlineData("/a/{b?}/{c?}", "/")]
+ [InlineData("template/{parameter:int?}", "/template/qwer")]
+ public virtual async Task NotMatch_OptionalParameter(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ [Theory]
+ [InlineData("/{a}/{*b}", "/a", new[] { "a", "b", }, new[] { "a", null, })]
+ [InlineData("/{a}/{*b}", "/a/", new[] { "a", "b", }, new[] { "a", null, })]
+ [InlineData("/{a}/{*b=b}", "/a", new[] { "a", "b", }, new[] { "a", "b", })]
+ [InlineData("/{a}/{*b=b}", "/a/", new[] { "a", "b", }, new[] { "a", "b", })]
+ [InlineData("/{a}/{*b=b}", "/a/hello", new[] { "a", "b", }, new[] { "a", "hello", })]
+ [InlineData("/{a}/{*b=b}", "/a/hello/goodbye", new[] { "a", "b", }, new[] { "a", "hello/goodbye", })]
+ [InlineData("/{a}/{*b=b}", "/a/b//", new[] { "a", "b", }, new[] { "a", "b//", })]
+ [InlineData("/{a}/{*b=b}", "/a/b/c/", new[] { "a", "b", }, new[] { "a", "b/c/", })]
+ [InlineData("/{a=1}/{b=2}/{c=3}/{d=4}", "/a/b/c", new[] { "a", "b", "c", "d", }, new[] { "a", "b", "c", "4", })]
+ [InlineData("a/{*path:regex(10/20/30)}", "/a/10/20/30", new[] { "path", }, new[] { "10/20/30" })]
+ public virtual async Task Match_CatchAllParameter(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
+
+ // Historically catchall segments don't match an empty segment, but only if it's
+ // the first one. So `/a/b//` would match, but `/a//` would not. This is pretty
+ // weird and inconsistent with the intent of using a catch all. The DfaMatcher
+ // fixes this issue.
+ [Theory]
+ [InlineData("/{a}/{*b=b}", "/a///", new[] { "a", "b", }, new[] { "a", "//" })]
+ [InlineData("/{a}/{*b=b}", "/a//c/", new[] { "a", "b", }, new[] { "a", "/c/" })]
+ public virtual async Task Quirks_CatchAllParameter(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+
+ // Need to access these to prevent a warning from the xUnit analyzer.
+ // Some of these tests will match (and process the values) and some will not.
+ GC.KeepAlive(keys);
+ GC.KeepAlive(values);
+ }
+
+ [Theory]
+ [InlineData("{p}x{s}", "/xxxxxxxxxx", new[] { "p", "s" }, new[] { "xxxxxxxx", "x", })]
+ [InlineData("{p}xyz{s}", "/xxxxyzxyzxxxxxxyz", new[] { "p", "s" }, new[] { "xxxxyz", "xxxxxxyz", })]
+ [InlineData("{p}xyz{s}", "/abcxxxxyzxyzxxxxxxyzxx", new[] { "p", "s" }, new[] { "abcxxxxyzxyzxxxxx", "xx", })]
+ [InlineData("{p}xyz{s}", "/xyzxyzxyzxyzxyz", new[] { "p", "s" }, new[] { "xyzxyzxyz", "xyz", })]
+ [InlineData("{p}xyz{s}", "/xyzxyzxyzxyzxyz1", new[] { "p", "s" }, new[] { "xyzxyzxyzxyz", "1", })]
+ [InlineData("{p}xyz{s}", "/xyzxyzxyz", new[] { "p", "s" }, new[] { "xyz", "xyz", })]
+ [InlineData("{p}aa{s}", "/aaaaa", new[] { "p", "s" }, new[] { "aa", "a", })]
+ [InlineData("{p}aaa{s}", "/aaaaa", new[] { "p", "s" }, new[] { "a", "a", })]
+ [InlineData("language/{lang=en}-{region=US}", "/language/xx-yy", new[] { "lang", "region" }, new[] { "xx", "yy", })]
+ [InlineData("language/{lang}-{region}", "/language/en-US", new[] { "lang", "region" }, new[] { "en", "US", })]
+ [InlineData("language/{lang}-{region}a", "/language/en-USa", new[] { "lang", "region" }, new[] { "en", "US", })]
+ [InlineData("language/a{lang}-{region}", "/language/aen-US", new[] { "lang", "region" }, new[] { "en", "US", })]
+ [InlineData("language/a{lang}-{region}a", "/language/aen-USa", new[] { "lang", "region" }, new[] { "en", "US", })]
+ [InlineData("language/{lang}-", "/language/en-", new[] { "lang", }, new[] { "en", })]
+ [InlineData("language/a{lang}", "/language/aen", new[] { "lang", }, new[] { "en", })]
+ [InlineData("language/a{lang}a", "/language/aena", new[] { "lang", }, new[] { "en", })]
+ public virtual async Task Match_ComplexSegment(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
+
+ [Theory]
+ [InlineData("language/a{lang}-{region}a", "/language/a-USa")]
+ [InlineData("language/a{lang}-{region}a", "/language/aen-a")]
+ [InlineData("language/{lang=en}-{region=US}", "/language")]
+ [InlineData("language/{lang=en}-{region=US}", "/language/-")]
+ [InlineData("language/{lang=en}-{region=US}", "/language/xx-")]
+ [InlineData("language/{lang=en}-{region=US}", "/language/-xx")]
+ public virtual async Task NotMatch_ComplexSegment(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", new[] { "p1", "p2", }, new[] { "foo", "bar" })]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo", new[] { "p1", }, new[] { "foo", })]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.foo", new[] { "p1", }, new[] { ".foo", })]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", new[] { "p1", "p2", }, new[] { "foo.", "bar" })]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", new[] { "p1", "p2", }, new[] { "foo.moo", "bar" })]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", new[] { "p1", }, new[] { "moo", })]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.foo.bar", new[] { "p1", "p2", }, new[] { "foo", "bar" })]
+ [InlineData("moo/.{p2?}", "/moo/.foo", new[] { "p2", }, new[] { "foo", })]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", new[] { "p1", "p2", }, new[] { "foo", "moo" })]
+ [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", new[] { "p1", "p2", "p3" }, new[] { "foo", "moo", "bar" })]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", new[] { "p1", "p3" }, new[] { "foo", "bar" })]
+ [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", new[] { "p1", "p3" }, new[] { ".foo", "bar" })]
+ [InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", new[] { "p1", "p2", "p3" }, new[] { "foo", "bar", "baz" })]
+ public virtual async Task Match_OptionalSeparator(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
+
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.")]
+ [InlineData("moo/{p1}.{p2}", "/foo.")]
+ [InlineData("moo/{p1}.{p2}", "/foo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
+ [InlineData("moo/.{p2?}", "/moo/.")]
+ [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
+ public virtual async Task NotMatch_OptionalSeparator(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ // Most of are copied from old routing tests that date back to the VS 2010 era. Enjoy!
+ [Theory]
+ [InlineData("{Controller}.mvc/../{action}", "/Home.mvc/../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
+ [InlineData("{Controller}.mvc/.../{action}", "/Home.mvc/.../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
+ [InlineData("{Controller}.mvc/../../../{action}", "/Home.mvc/../../../index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
+ [InlineData("{Controller}.mvc!/{action}", "/Home.mvc!/index", new string[] { "Controller", "action" }, new string[] { "Home", "index" })]
+ [InlineData("../{Controller}.mvc", "/../Home.mvc", new string[] { "Controller", }, new string[] { "Home", })]
+ [InlineData(@"\{Controller}.mvc", @"/\Home.mvc", new string[] { "Controller", }, new string[] { "Home", })]
+ [InlineData(@"{Controller}.mvc\{id}\{Param1}", @"/Home.mvc\123\p1", new string[] { "Controller", "id", "Param1" }, new string[] { "Home", "123", "p1" })]
+ [InlineData("(Controller).mvc", "/(Controller).mvc", new string[] { }, new string[] { })]
+ [InlineData("Controller.mvc/ ", "/Controller.mvc/ ", new string[] { }, new string[] { })]
+ [InlineData("Controller.mvc ", "/Controller.mvc ", new string[] { }, new string[] { })]
+ public virtual async Task Match_WeirdCharacterCases(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
+
+ [Theory]
+ [InlineData("template/5", "template/{parameter:int}")]
+ [InlineData("template/5", "template/{parameter}")]
+ [InlineData("template/5", "template/{*parameter:int}")]
+ [InlineData("template/5", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{parameter:alpha}")] // constraint doesn't match
+ [InlineData("template/{parameter:int}", "template/{parameter}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter}", "template/{*parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter}")]
+ public virtual async Task Match_SelectEndpoint_BasedOnPrecedence(string template1, string template2)
+ {
+ // Arrange
+ var expected = CreateEndpoint(template1);
+ var other = CreateEndpoint(template2);
+ var path = "/template/5";
+
+ // Arrange
+ var matcher = CreateMatcher(other, expected);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
+
+ [Theory]
+ [InlineData("template/5", "template/{parameter:int}")]
+ [InlineData("template/5", "template/{parameter}")]
+ [InlineData("template/5", "template/{*parameter:int}")]
+ [InlineData("template/5", "template/{*parameter}")]
+ [InlineData("template/{parameter:int}", "template/{parameter}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter}", "template/{*parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter}")]
+ [InlineData("template/5", "template/5")]
+ [InlineData("template/{parameter:int}", "template/{parameter:int}")]
+ [InlineData("template/{parameter}", "template/{parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{*parameter}", "template/{*parameter}")]
+ public virtual async Task Match_SelectEndpoint_BasedOnOrder(string template1, string template2)
+ {
+ // Arrange
+ var expected = CreateEndpoint(template1, order: 0);
+ var other = CreateEndpoint(template2, order: 1);
+ var path = "/template/5";
+
+ // Arrange
+ var matcher = CreateMatcher(other, expected);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
+
+ [Theory]
+ [InlineData("/", "")]
+ [InlineData("/Literal1", "Literal1")]
+ [InlineData("/Literal1/Literal2", "Literal1/Literal2")]
+ [InlineData("/Literal1/Literal2/Literal3", "Literal1/Literal2/Literal3")]
+ [InlineData("/Literal1/Literal2/Literal3/4", "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}")]
+ [InlineData("/Literal1/Literal2/Literal3/Literal4", "Literal1/Literal2/Literal3/{*catchAll}")]
+ [InlineData("/1", "{constrained1:int}")]
+ [InlineData("/1/2", "{constrained1:int}/{constrained2:int}")]
+ [InlineData("/1/2/3", "{constrained1:int}/{constrained2:int}/{constrained3:int}")]
+ [InlineData("/1/2/3/4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}")]
+ [InlineData("/1/2/3/CatchAll4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}")]
+ [InlineData("/parameter1", "{parameter1}")]
+ [InlineData("/parameter1/parameter2", "{parameter1}/{parameter2}")]
+ [InlineData("/parameter1/parameter2/parameter3", "{parameter1}/{parameter2}/{parameter3}")]
+ [InlineData("/parameter1/parameter2/parameter3/4", "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}")]
+ [InlineData("/parameter1/parameter2/parameter3/CatchAll4", "{parameter1}/{parameter2}/{parameter3}/{*catchAll}")]
+ public virtual async Task Match_IntegrationTest_MultipleEndpoints(string path, string expectedTemplate)
+ {
+ // Arrange
+ var templates = new[]
{
- // Arrange
- var templates = new[]
- {
"",
"Literal1",
"Literal1/Literal2",
@@ -431,25 +431,25 @@ namespace Microsoft.AspNetCore.Routing.Matching
"{parameter1}/{parameter2}/{parameter3}/{*catchAll}",
};
- var endpoints = templates.Select((t) => CreateEndpoint(t)).ToArray();
- var expected = endpoints[Array.IndexOf(templates, expectedTemplate)];
+ var endpoints = templates.Select((t) => CreateEndpoint(t)).ToArray();
+ var expected = endpoints[Array.IndexOf(templates, expectedTemplate)];
- var matcher = CreateMatcher(endpoints);
- var httpContext = CreateContext(path);
+ var matcher = CreateMatcher(endpoints);
+ var httpContext = CreateContext(path);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
- // https://github.com/dotnet/aspnetcore/issues/16579
- [Fact]
- public virtual async Task Match_Regression_16579_Order1()
+ // https://github.com/dotnet/aspnetcore/issues/16579
+ [Fact]
+ public virtual async Task Match_Regression_16579_Order1()
+ {
+ var endpoints = new RouteEndpoint[]
{
- var endpoints = new RouteEndpoint[]
- {
EndpointFactory.CreateRouteEndpoint(
"{controller}/folder/{*path}",
order: 0,
@@ -460,26 +460,26 @@ namespace Microsoft.AspNetCore.Routing.Matching
order: 1,
defaults: new { controller = "File", action = "Index", },
requiredValues: new { controller = "File", action = "Index", }),
- };
+ };
- var expected = endpoints[0];
+ var expected = endpoints[0];
- var matcher = CreateMatcher(endpoints);
- var httpContext = CreateContext("/file/folder/abc/abc");
+ var matcher = CreateMatcher(endpoints);
+ var httpContext = CreateContext("/file/folder/abc/abc");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
- // https://github.com/dotnet/aspnetcore/issues/16579
- [Fact]
- public virtual async Task Match_Regression_16579_Order2()
+ // https://github.com/dotnet/aspnetcore/issues/16579
+ [Fact]
+ public virtual async Task Match_Regression_16579_Order2()
+ {
+ var endpoints = new RouteEndpoint[]
{
- var endpoints = new RouteEndpoint[]
- {
EndpointFactory.CreateRouteEndpoint(
"{controller}/{action}/{filename}",
order: 0,
@@ -491,18 +491,17 @@ namespace Microsoft.AspNetCore.Routing.Matching
order: 1,
defaults: new { controller = "File", action = "Folder", },
requiredValues: new { controller = "File", }),
- };
+ };
- var expected = endpoints[1];
+ var expected = endpoints[1];
- var matcher = CreateMatcher(endpoints);
- var httpContext = CreateContext("/file/folder/abc/abc");
+ var matcher = CreateMatcher(endpoints);
+ var httpContext = CreateContext("/file/folder/abc/abc");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs
index b42026ed96..c2313d1b28 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// End-to-end tests for the host matching functionality
+public class HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest : HostMatcherPolicyIntegrationTestBase
{
- // End-to-end tests for the host matching functionality
- public class HostMatcherPolicyIEndpointSelectorPolicyIntegrationTest : HostMatcherPolicyIntegrationTestBase
- {
- protected override bool HasDynamicMetadata => true;
- }
+ protected override bool HasDynamicMetadata => true;
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyINodeBuilderPolicyIntegrationTest.cs b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyINodeBuilderPolicyIntegrationTest.cs
index 3ef23079f9..10c89d8b15 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyINodeBuilderPolicyIntegrationTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyINodeBuilderPolicyIntegrationTest.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// End-to-end tests for the host matching functionality
+public class HostMatcherPolicyINodeBuilderPolicyIntegrationTest : HostMatcherPolicyIntegrationTestBase
{
- // End-to-end tests for the host matching functionality
- public class HostMatcherPolicyINodeBuilderPolicyIntegrationTest : HostMatcherPolicyIntegrationTestBase
- {
- protected override bool HasDynamicMetadata => false;
- }
+ protected override bool HasDynamicMetadata => false;
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs
index bb0647aab7..fa06c2ad18 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs
@@ -10,434 +10,433 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// End-to-end tests for the host matching functionality
+public abstract class HostMatcherPolicyIntegrationTestBase
{
- // End-to-end tests for the host matching functionality
- public abstract class HostMatcherPolicyIntegrationTestBase
- {
- protected abstract bool HasDynamicMetadata { get; }
+ protected abstract bool HasDynamicMetadata { get; }
- [Fact]
- public async Task Match_Host()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com", });
+ [Fact]
+ public async Task Match_Host()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithPort()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithPort()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_Host_Unicode()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "æon.contoso.com", });
+ [Fact]
+ public async Task Match_Host_Unicode()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "æon.contoso.com", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "æon.contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "æon.contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithPort_IncorrectPort()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithPort_IncorrectPort()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com:1111");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com:1111");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact]
- public async Task Match_HostWithPort_IncorrectHost()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithPort_IncorrectHost()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "www.contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "www.contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact]
- public async Task Match_HostWithWildcard_Unicode()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithWildcard_Unicode()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "æon.contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "æon.contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithWildcard_NoSubdomain()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithWildcard_NoSubdomain()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact]
- public async Task Match_HostWithWildcard_Subdomain()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithWildcard_Subdomain()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "www.contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "www.contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithWildcard_MultipleSubdomains()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithWildcard_MultipleSubdomains()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "www.blog.contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "www.blog.contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithWildcard_PrefixNotInSubdomain()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostWithWildcard_PrefixNotInSubdomain()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*.contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "mycontoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "mycontoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact]
- public async Task Match_HostAndHostWithWildcard_NoSubdomain()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", "*.contoso.com:8080", });
+ [Fact]
+ public async Task Match_HostAndHostWithWildcard_NoSubdomain()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:8080", "*.contoso.com:8080", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com:8080");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com:8080");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_Host_CaseInsensitive()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "Contoso.COM", });
+ [Fact]
+ public async Task Match_Host_CaseInsensitive()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "Contoso.COM", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithPort_InferHttpPort()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:80", });
+ [Fact]
+ public async Task Match_HostWithPort_InferHttpPort()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:80", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com", "http");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com", "http");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithPort_InferHttpsPort()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:443", });
+ [Fact]
+ public async Task Match_HostWithPort_InferHttpsPort()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:443", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com", "https");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com", "https");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HostWithPort_NoHostHeader()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:443", });
+ [Fact]
+ public async Task Match_HostWithPort_NoHostHeader()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "contoso.com:443", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", null, "https");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", null, "https");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact]
- public async Task Match_Port_NoHostHeader_InferHttpsPort()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*:443", });
+ [Fact]
+ public async Task Match_Port_NoHostHeader_InferHttpsPort()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*:443", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", null, "https");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", null, "https");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_NoMetadata_MatchesAnyHost()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello");
+ [Fact]
+ public async Task Match_NoMetadata_MatchesAnyHost()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello");
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_EmptyHostList_MatchesAnyHost()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { });
+ [Fact]
+ public async Task Match_EmptyHostList_MatchesAnyHost()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_WildcardHost_MatchesAnyHost()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*", });
+ [Fact]
+ public async Task Match_WildcardHost_MatchesAnyHost()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_WildcardHostAndWildcardPort_MatchesAnyHost()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*:*", });
+ [Fact]
+ public async Task Match_WildcardHostAndWildcardPort_MatchesAnyHost()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", hosts: new string[] { "*:*", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_CatchAllRouteWithMatchingHost_Success()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{**path}", hosts: new string[] { "contoso.com", });
+ [Fact]
+ public async Task Match_CatchAllRouteWithMatchingHost_Success()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{**path}", hosts: new string[] { "contoso.com", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "contoso.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "contoso.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, new { path = "hello" });
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, new { path = "hello" });
+ }
- [Fact]
- public async Task Match_CatchAllRouteFailureHost_NoMatch()
- {
- // Arrange
- var endpoint = CreateEndpoint("/{**path}", hosts: new string[] { "contoso.com", });
+ [Fact]
+ public async Task Match_CatchAllRouteFailureHost_NoMatch()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/{**path}", hosts: new string[] { "contoso.com", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "nomatch.com");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "nomatch.com");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- private static Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ private static Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ {
+ var services = new ServiceCollection()
+ .AddOptions()
+ .AddLogging()
+ .AddRouting()
+ .BuildServiceProvider();
+
+ var builder = services.GetRequiredService<DfaMatcherBuilder>();
+ for (var i = 0; i < endpoints.Length; i++)
{
- var services = new ServiceCollection()
- .AddOptions()
- .AddLogging()
- .AddRouting()
- .BuildServiceProvider();
-
- var builder = services.GetRequiredService<DfaMatcherBuilder>();
- for (var i = 0; i < endpoints.Length; i++)
- {
- builder.AddEndpoint(endpoints[i]);
- }
-
- return builder.Build();
+ builder.AddEndpoint(endpoints[i]);
}
- internal static HttpContext CreateContext(
- string path,
- string host,
- string scheme = null)
- {
- var httpContext = new DefaultHttpContext();
- if (host != null)
- {
- httpContext.Request.Host = new HostString(host);
- }
- httpContext.Request.Path = path;
- httpContext.Request.Scheme = scheme;
-
- return httpContext;
- }
+ return builder.Build();
+ }
- internal RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- object constraints = null,
- int order = 0,
- string[] hosts = null)
+ internal static HttpContext CreateContext(
+ string path,
+ string host,
+ string scheme = null)
+ {
+ var httpContext = new DefaultHttpContext();
+ if (host != null)
{
- var metadata = new List<object>();
- if (hosts != null)
- {
- metadata.Add(new HostAttribute(hosts ?? Array.Empty<string>()));
- }
-
- if (HasDynamicMetadata)
- {
- metadata.Add(new DynamicEndpointMetadata());
- }
-
- var displayName = "endpoint: " + template + " " + string.Join(", ", hosts ?? new[] { "*:*" });
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template, defaults, constraints),
- order,
- new EndpointMetadataCollection(metadata),
- displayName);
+ httpContext.Request.Host = new HostString(host);
}
+ httpContext.Request.Path = path;
+ httpContext.Request.Scheme = scheme;
- internal (Matcher matcher, RouteEndpoint endpoint) CreateMatcher(string template)
+ return httpContext;
+ }
+
+ internal RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ object constraints = null,
+ int order = 0,
+ string[] hosts = null)
+ {
+ var metadata = new List<object>();
+ if (hosts != null)
{
- var endpoint = CreateEndpoint(template);
- return (CreateMatcher(endpoint), endpoint);
+ metadata.Add(new HostAttribute(hosts ?? Array.Empty<string>()));
}
- private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ if (HasDynamicMetadata)
{
- public bool IsDynamic => true;
+ metadata.Add(new DynamicEndpointMetadata());
}
+
+ var displayName = "endpoint: " + template + " " + string.Join(", ", hosts ?? new[] { "*:*" });
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template, defaults, constraints),
+ order,
+ new EndpointMetadataCollection(metadata),
+ displayName);
+ }
+
+ internal (Matcher matcher, RouteEndpoint endpoint) CreateMatcher(string template)
+ {
+ var endpoint = CreateEndpoint(template);
+ return (CreateMatcher(endpoint), endpoint);
+ }
+
+ private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ {
+ public bool IsDynamic => true;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyTest.cs b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyTest.cs
index b20597aa91..922edc402a 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyTest.cs
@@ -9,200 +9,200 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class HostMatcherPolicyTest
{
- public class HostMatcherPolicyTest
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse()
{
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse()
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", null), };
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", null), };
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutHosts_ReturnsFalse()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointWithoutHosts_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasHosts_ReturnsTrue()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasHosts_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", })),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasDynamicMetadata_ReturnsFalse()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToEndpoints_EndpointHasDynamicMetadata_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", }), new DynamicEndpointMetadata()),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
-
- [Theory]
- [InlineData(":")]
- [InlineData(":80")]
- [InlineData("80:")]
- [InlineData("")]
- [InlineData("::")]
- [InlineData("*:test")]
- public void INodeBuilderPolicy_AppliesToEndpoints_InvalidHosts(string host)
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host })), };
+ // Assert
+ Assert.False(result);
+ }
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ [Theory]
+ [InlineData(":")]
+ [InlineData(":80")]
+ [InlineData("80:")]
+ [InlineData("")]
+ [InlineData("::")]
+ [InlineData("*:test")]
+ public void INodeBuilderPolicy_AppliesToEndpoints_InvalidHosts(string host)
+ {
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host })), };
- // Act & Assert
- Assert.Throws<InvalidOperationException>(() =>
- {
- policy.AppliesToEndpoints(endpoints);
- });
- }
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsTrue()
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() =>
{
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
+ policy.AppliesToEndpoints(endpoints);
+ });
+ }
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutMetadata_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Assert
- Assert.True(result);
- }
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
+
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutHosts_ReturnsTrue()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointWithoutHosts_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(Array.Empty<string>()), new DynamicEndpointMetadata()),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasHosts_ReturnsTrue()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasHosts_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", }), new DynamicEndpointMetadata()),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasNoDynamicMetadata_ReturnsFalse()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_EndpointHasNoDynamicMetadata_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", new HostAttribute(new[] { "localhost", })),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Theory]
- [InlineData(":")]
- [InlineData(":80")]
- [InlineData("80:")]
- [InlineData("")]
- [InlineData("::")]
- [InlineData("*:test")]
- public void IEndpointSelectorPolicy_AppliesToEndpoints_InvalidHosts(string host)
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host }), new DynamicEndpointMetadata()), };
+ [Theory]
+ [InlineData(":")]
+ [InlineData(":80")]
+ [InlineData("80:")]
+ [InlineData("")]
+ [InlineData("::")]
+ [InlineData("*:test")]
+ public void IEndpointSelectorPolicy_AppliesToEndpoints_InvalidHosts(string host)
+ {
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", new HostAttribute(new[] { host }), new DynamicEndpointMetadata()), };
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act & Assert
- Assert.Throws<InvalidOperationException>(() =>
- {
- policy.AppliesToEndpoints(endpoints);
- });
- }
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() =>
+ {
+ policy.AppliesToEndpoints(endpoints);
+ });
+ }
- [Fact]
- public void GetEdges_GroupsByHost()
+ [Fact]
+ public void GetEdges_GroupsByHost()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HostAttribute(new[] { "*:5000", "*:5001", })),
CreateEndpoint("/", new HostAttribute(Array.Empty<string>())),
CreateEndpoint("/", hostMetadata: null),
@@ -213,82 +213,81 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HostAttribute("*:*")),
};
- var policy = CreatePolicy();
-
- // Act
- var edges = policy.GetEdges(endpoints);
-
- var data = edges.OrderBy(e => e.State).ToList();
-
- // Assert
- Assert.Collection(
- data,
- e =>
- {
- Assert.Equal("*:*", e.State.ToString());
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[7], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("*:5000", e.State.ToString());
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("*:5001", e.State.ToString());
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("*.contoso.com:*", e.State.ToString());
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("*.sub.contoso.com:*", e.State.ToString());
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("www.contoso.com:*", e.State.ToString());
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[5], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal("www.contoso.com:5000", e.State.ToString());
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[6], }, e.Endpoints.ToArray());
- });
- }
+ var policy = CreatePolicy();
- private static RouteEndpoint CreateEndpoint(string template, IHostMetadata hostMetadata, params object[] more)
- {
- var metadata = new List<object>();
- if (hostMetadata != null)
- {
- metadata.Add(hostMetadata);
- }
+ // Act
+ var edges = policy.GetEdges(endpoints);
+
+ var data = edges.OrderBy(e => e.State).ToList();
- if (more != null)
+ // Assert
+ Assert.Collection(
+ data,
+ e =>
{
- metadata.AddRange(more);
- }
-
- return new RouteEndpoint(
- (context) => Task.CompletedTask,
- RoutePatternFactory.Parse(template),
- 0,
- new EndpointMetadataCollection(metadata),
- $"test: {template} - {string.Join(", ", hostMetadata?.Hosts ?? Array.Empty<string>())}");
- }
+ Assert.Equal("*:*", e.State.ToString());
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[7], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("*:5000", e.State.ToString());
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("*:5001", e.State.ToString());
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("*.contoso.com:*", e.State.ToString());
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("*.sub.contoso.com:*", e.State.ToString());
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("www.contoso.com:*", e.State.ToString());
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[5], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal("www.contoso.com:5000", e.State.ToString());
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[6], }, e.Endpoints.ToArray());
+ });
+ }
- private static HostMatcherPolicy CreatePolicy()
+ private static RouteEndpoint CreateEndpoint(string template, IHostMetadata hostMetadata, params object[] more)
+ {
+ var metadata = new List<object>();
+ if (hostMetadata != null)
{
- return new HostMatcherPolicy();
+ metadata.Add(hostMetadata);
}
- private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ if (more != null)
{
- public bool IsDynamic => true;
+ metadata.AddRange(more);
}
+
+ return new RouteEndpoint(
+ (context) => Task.CompletedTask,
+ RoutePatternFactory.Parse(template),
+ 0,
+ new EndpointMetadataCollection(metadata),
+ $"test: {template} - {string.Join(", ", hostMetadata?.Hosts ?? Array.Empty<string>())}");
+ }
+
+ private static HostMatcherPolicy CreatePolicy()
+ {
+ return new HostMatcherPolicy();
+ }
+
+ private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ {
+ public bool IsDynamic => true;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs
index d26f4ddd11..5f41c1dc02 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTest.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// End-to-end tests for the HTTP method matching functionality
+public class HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTestBase : HttpMethodMatcherPolicyIntegrationTestBase
{
- // End-to-end tests for the HTTP method matching functionality
- public class HttpMethodMatcherPolicyIEndpointSelectorPolicyIntegrationTestBase : HttpMethodMatcherPolicyIntegrationTestBase
- {
- protected override bool HasDynamicMetadata => true;
- }
+ protected override bool HasDynamicMetadata => true;
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTest.cs b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTest.cs
index 49934d3ecf..a74c5eb7d2 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTest.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// End-to-end tests for the HTTP method matching functionality
+public class HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTestBase : HttpMethodMatcherPolicyIntegrationTestBase
{
- // End-to-end tests for the HTTP method matching functionality
- public class HttpMethodMatcherPolicyINodeBuilderPolicyIntegrationTestBase : HttpMethodMatcherPolicyIntegrationTestBase
- {
- protected override bool HasDynamicMetadata => false;
- }
+ protected override bool HasDynamicMetadata => false;
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs
index bd38bed90b..fa1ba98493 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs
@@ -12,391 +12,390 @@ using Microsoft.Net.Http.Headers;
using Xunit;
using static Microsoft.AspNetCore.Routing.Matching.HttpMethodMatcherPolicy;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// End-to-end tests for the HTTP method matching functionality
+public abstract class HttpMethodMatcherPolicyIntegrationTestBase
{
- // End-to-end tests for the HTTP method matching functionality
- public abstract class HttpMethodMatcherPolicyIntegrationTestBase
- {
- protected abstract bool HasDynamicMetadata { get; }
+ protected abstract bool HasDynamicMetadata { get; }
- [Fact]
- public async Task Match_HttpMethod()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", });
+ [Fact]
+ public async Task Match_HttpMethod()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HttpMethod_CORS()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", }, acceptCorsPreflight: true);
+ [Fact]
+ public async Task Match_HttpMethod_CORS()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", }, acceptCorsPreflight: true);
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_HttpMethod_CORS_Preflight()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", }, acceptCorsPreflight: true);
+ [Fact]
+ public async Task Match_HttpMethod_CORS_Preflight()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", }, acceptCorsPreflight: true);
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact] // Nothing here supports OPTIONS, so it goes to a 405.
- public async Task NotMatch_HttpMethod_CORS_Preflight()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", }, acceptCorsPreflight: false);
+ [Fact] // Nothing here supports OPTIONS, so it goes to a 405.
+ public async Task NotMatch_HttpMethod_CORS_Preflight()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { "GET", }, acceptCorsPreflight: false);
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.NotSame(endpoint, httpContext.GetEndpoint());
- Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, httpContext.GetEndpoint().DisplayName);
- }
+ // Assert
+ Assert.NotSame(endpoint, httpContext.GetEndpoint());
+ Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, httpContext.GetEndpoint().DisplayName);
+ }
- [Theory]
- [InlineData("GeT", "GET")]
- [InlineData("unKNOWN", "UNKNOWN")]
- public async Task Match_HttpMethod_CaseInsensitive(string endpointMethod, string requestMethod)
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { endpointMethod, });
+ [Theory]
+ [InlineData("GeT", "GET")]
+ [InlineData("unKNOWN", "UNKNOWN")]
+ public async Task Match_HttpMethod_CaseInsensitive(string endpointMethod, string requestMethod)
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { endpointMethod, });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", requestMethod);
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", requestMethod);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Theory]
- [InlineData("GeT", "GET")]
- [InlineData("unKNOWN", "UNKNOWN")]
- public async Task Match_HttpMethod_CaseInsensitive_CORS_Preflight(string endpointMethod, string requestMethod)
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { endpointMethod, }, acceptCorsPreflight: true);
+ [Theory]
+ [InlineData("GeT", "GET")]
+ [InlineData("unKNOWN", "UNKNOWN")]
+ public async Task Match_HttpMethod_CaseInsensitive_CORS_Preflight(string endpointMethod, string requestMethod)
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { endpointMethod, }, acceptCorsPreflight: true);
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", requestMethod, corsPreflight: true);
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", requestMethod, corsPreflight: true);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_NoMetadata_MatchesAnyHttpMethod()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello");
+ [Fact]
+ public async Task Match_NoMetadata_MatchesAnyHttpMethod()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello");
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_NoMetadata_MatchesAnyHttpMethod_CORS_Preflight()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", acceptCorsPreflight: true);
+ [Fact]
+ public async Task Match_NoMetadata_MatchesAnyHttpMethod_CORS_Preflight()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", acceptCorsPreflight: true);
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact] // This matches because the endpoint accepts OPTIONS
- public async Task Match_NoMetadata_MatchesAnyHttpMethod_CORS_Preflight_DoesNotSupportPreflight()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", acceptCorsPreflight: false);
+ [Fact] // This matches because the endpoint accepts OPTIONS
+ public async Task Match_NoMetadata_MatchesAnyHttpMethod_CORS_Preflight_DoesNotSupportPreflight()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", acceptCorsPreflight: false);
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET", corsPreflight: true);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact]
- public async Task Match_EmptyMethodList_MatchesAnyHttpMethod()
- {
- // Arrange
- var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { });
+ [Fact]
+ public async Task Match_EmptyMethodList_MatchesAnyHttpMethod()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint("/hello", httpMethods: new string[] { });
- var matcher = CreateMatcher(endpoint);
- var httpContext = CreateContext("/hello", "GET");
+ var matcher = CreateMatcher(endpoint);
+ var httpContext = CreateContext("/hello", "GET");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
- [Fact] // When all of the candidates handles specific verbs, use a 405 endpoint
- public async Task NotMatch_HttpMethod_Returns405Endpoint()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" });
- var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
+ [Fact] // When all of the candidates handles specific verbs, use a 405 endpoint
+ public async Task NotMatch_HttpMethod_Returns405Endpoint()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" });
+ var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "POST");
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "POST");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.NotSame(endpoint1, httpContext.GetEndpoint());
- Assert.NotSame(endpoint2, httpContext.GetEndpoint());
+ // Assert
+ Assert.NotSame(endpoint1, httpContext.GetEndpoint());
+ Assert.NotSame(endpoint2, httpContext.GetEndpoint());
- Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, httpContext.GetEndpoint().DisplayName);
+ Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, httpContext.GetEndpoint().DisplayName);
- // Invoke the endpoint
- await httpContext.GetEndpoint().RequestDelegate(httpContext);
- Assert.Equal(405, httpContext.Response.StatusCode);
- Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
- }
+ // Invoke the endpoint
+ await httpContext.GetEndpoint().RequestDelegate(httpContext);
+ Assert.Equal(405, httpContext.Response.StatusCode);
+ Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
+ }
- [Fact]
- public async Task NotMatch_HttpMethod_CORS_DoesNotReturn405()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" }, acceptCorsPreflight: true);
- var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
+ [Fact]
+ public async Task NotMatch_HttpMethod_CORS_DoesNotReturn405()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" }, acceptCorsPreflight: true);
+ var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "POST", corsPreflight: true);
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "POST", corsPreflight: true);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact] // When one of the candidates handles all verbs, dont use a 405 endpoint
- public async Task NotMatch_HttpMethod_WithAllMethodEndpoint_DoesNotReturn405()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/{x:int}", httpMethods: new string[] { });
- var endpoint2 = CreateEndpoint("/{hello:regex(hello)}", httpMethods: new string[] { "DELETE" });
+ [Fact] // When one of the candidates handles all verbs, dont use a 405 endpoint
+ public async Task NotMatch_HttpMethod_WithAllMethodEndpoint_DoesNotReturn405()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/{x:int}", httpMethods: new string[] { });
+ var endpoint2 = CreateEndpoint("/{hello:regex(hello)}", httpMethods: new string[] { "DELETE" });
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "POST");
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "POST");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
- [Fact]
- public async Task Match_EndpointWithHttpMethodPreferred()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", });
- var endpoint2 = CreateEndpoint("/bar");
+ [Fact]
+ public async Task Match_EndpointWithHttpMethodPreferred()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", });
+ var endpoint2 = CreateEndpoint("/bar");
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "GET");
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "GET");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint1);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint1);
+ }
- [Fact]
- public async Task Match_EndpointWithHttpMethodPreferred_EmptyList()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", });
- var endpoint2 = CreateEndpoint("/bar", httpMethods: new string[] { });
+ [Fact]
+ public async Task Match_EndpointWithHttpMethodPreferred_EmptyList()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", });
+ var endpoint2 = CreateEndpoint("/bar", httpMethods: new string[] { });
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "GET");
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "GET");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint1);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint1);
+ }
- [Fact] // The non-http-method-specific endpoint is part of the same candidate set
- public async Task Match_EndpointWithHttpMethodPreferred_FallsBackToNonSpecific()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/{x}", httpMethods: new string[] { "GET", });
- var endpoint2 = CreateEndpoint("/{x}", httpMethods: new string[] { });
+ [Fact] // The non-http-method-specific endpoint is part of the same candidate set
+ public async Task Match_EndpointWithHttpMethodPreferred_FallsBackToNonSpecific()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/{x}", httpMethods: new string[] { "GET", });
+ var endpoint2 = CreateEndpoint("/{x}", httpMethods: new string[] { });
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "POST");
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "POST");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint2, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint2, ignoreValues: true);
+ }
- [Fact] // See https://github.com/dotnet/aspnetcore/issues/6415
- public async Task NotMatch_HttpMethod_Returns405Endpoint_ReExecute()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" });
- var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
+ [Fact] // See https://github.com/dotnet/aspnetcore/issues/6415
+ public async Task NotMatch_HttpMethod_Returns405Endpoint_ReExecute()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" });
+ var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
- var matcher = CreateMatcher(endpoint1, endpoint2);
- var httpContext = CreateContext("/hello", "POST");
+ var matcher = CreateMatcher(endpoint1, endpoint2);
+ var httpContext = CreateContext("/hello", "POST");
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- Assert.NotSame(endpoint1, httpContext.GetEndpoint());
- Assert.NotSame(endpoint2, httpContext.GetEndpoint());
+ // Assert
+ Assert.NotSame(endpoint1, httpContext.GetEndpoint());
+ Assert.NotSame(endpoint2, httpContext.GetEndpoint());
- Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, httpContext.GetEndpoint().DisplayName);
+ Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, httpContext.GetEndpoint().DisplayName);
- // Invoke the endpoint
- await httpContext.GetEndpoint().RequestDelegate(httpContext);
- Assert.Equal(405, httpContext.Response.StatusCode);
- Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
+ // Invoke the endpoint
+ await httpContext.GetEndpoint().RequestDelegate(httpContext);
+ Assert.Equal(405, httpContext.Response.StatusCode);
+ Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
- // Invoke the endpoint again to verify headers not duplicated
- await httpContext.GetEndpoint().RequestDelegate(httpContext);
- Assert.Equal(405, httpContext.Response.StatusCode);
- Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
- }
+ // Invoke the endpoint again to verify headers not duplicated
+ await httpContext.GetEndpoint().RequestDelegate(httpContext);
+ Assert.Equal(405, httpContext.Response.StatusCode);
+ Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
+ }
- private static Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ private static Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ {
+ var services = new ServiceCollection()
+ .AddOptions()
+ .AddLogging()
+ .AddRouting()
+ .BuildServiceProvider();
+
+ var builder = services.GetRequiredService<DfaMatcherBuilder>();
+ for (var i = 0; i < endpoints.Length; i++)
{
- var services = new ServiceCollection()
- .AddOptions()
- .AddLogging()
- .AddRouting()
- .BuildServiceProvider();
-
- var builder = services.GetRequiredService<DfaMatcherBuilder>();
- for (var i = 0; i < endpoints.Length; i++)
- {
- builder.AddEndpoint(endpoints[i]);
- }
-
- return builder.Build();
+ builder.AddEndpoint(endpoints[i]);
}
- internal static HttpContext CreateContext(
- string path,
- string httpMethod,
- bool corsPreflight = false)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.Method = corsPreflight ? PreflightHttpMethod : httpMethod;
- httpContext.Request.Path = path;
-
- if (corsPreflight)
- {
- httpContext.Request.Headers[HeaderNames.Origin] = "example.com";
- httpContext.Request.Headers[HeaderNames.AccessControlRequestMethod] = httpMethod;
- }
+ return builder.Build();
+ }
- return httpContext;
- }
+ internal static HttpContext CreateContext(
+ string path,
+ string httpMethod,
+ bool corsPreflight = false)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Method = corsPreflight ? PreflightHttpMethod : httpMethod;
+ httpContext.Request.Path = path;
- internal RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- object constraints = null,
- int order = 0,
- string[] httpMethods = null,
- bool acceptCorsPreflight = false)
+ if (corsPreflight)
{
- var metadata = new List<object>();
- if (httpMethods != null)
- {
- metadata.Add(new HttpMethodMetadata(httpMethods ?? Array.Empty<string>(), acceptCorsPreflight));
- }
-
- if (HasDynamicMetadata)
- {
- metadata.Add(new DynamicEndpointMetadata());
- }
-
- var displayName = "endpoint: " + template + " " + string.Join(", ", httpMethods ?? new[] { "(any)" });
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template, defaults, constraints),
- order,
- new EndpointMetadataCollection(metadata),
- displayName);
+ httpContext.Request.Headers[HeaderNames.Origin] = "example.com";
+ httpContext.Request.Headers[HeaderNames.AccessControlRequestMethod] = httpMethod;
}
- internal (Matcher matcher, RouteEndpoint endpoint) CreateMatcher(string template)
+ return httpContext;
+ }
+
+ internal RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ object constraints = null,
+ int order = 0,
+ string[] httpMethods = null,
+ bool acceptCorsPreflight = false)
+ {
+ var metadata = new List<object>();
+ if (httpMethods != null)
{
- var endpoint = CreateEndpoint(template);
- return (CreateMatcher(endpoint), endpoint);
+ metadata.Add(new HttpMethodMetadata(httpMethods ?? Array.Empty<string>(), acceptCorsPreflight));
}
- private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ if (HasDynamicMetadata)
{
- public bool IsDynamic => true;
+ metadata.Add(new DynamicEndpointMetadata());
}
+
+ var displayName = "endpoint: " + template + " " + string.Join(", ", httpMethods ?? new[] { "(any)" });
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template, defaults, constraints),
+ order,
+ new EndpointMetadataCollection(metadata),
+ displayName);
+ }
+
+ internal (Matcher matcher, RouteEndpoint endpoint) CreateMatcher(string template)
+ {
+ var endpoint = CreateEndpoint(template);
+ return (CreateMatcher(endpoint), endpoint);
+ }
+
+ private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ {
+ public bool IsDynamic => true;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyTest.cs b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyTest.cs
index b6dbfcdad4..7bbfa4a93b 100644
--- a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyTest.cs
@@ -9,158 +9,158 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
using static Microsoft.AspNetCore.Routing.Matching.HttpMethodMatcherPolicy;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class HttpMethodMatcherPolicyTest
{
- public class HttpMethodMatcherPolicyTest
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToNode_EndpointWithoutMetadata_ReturnsFalse()
{
- [Fact]
- public void INodeBuilderPolicy_AppliesToNode_EndpointWithoutMetadata_ReturnsFalse()
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", null), };
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", null), };
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToNode_EndpointWithoutHttpMethods_ReturnsFalse()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToNode_EndpointWithoutHttpMethods_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>())),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToNode_EndpointHasHttpMethods_ReturnsTrue()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToNode_EndpointHasHttpMethods_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>())),
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void INodeBuilderPolicy_AppliesToNode_EndpointIsDynamic_ReturnsFalse()
+ [Fact]
+ public void INodeBuilderPolicy_AppliesToNode_EndpointIsDynamic_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>())),
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", }), new DynamicEndpointMetadata()),
};
- var policy = (INodeBuilderPolicy)CreatePolicy();
+ var policy = (INodeBuilderPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToNode_EndpointWithoutMetadata_ReturnsTrue()
- {
- // Arrange
- var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToNode_EndpointWithoutMetadata_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[] { CreateEndpoint("/", null, new DynamicEndpointMetadata()), };
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToNode_EndpointWithoutHttpMethods_ReturnsTrue()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToNode_EndpointWithoutHttpMethods_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToNode_EndpointHasHttpMethods_ReturnsTrue()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToNode_EndpointHasHttpMethods_ReturnsTrue()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>()), new DynamicEndpointMetadata()),
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.True(result);
- }
+ // Assert
+ Assert.True(result);
+ }
- [Fact]
- public void IEndpointSelectorPolicy_AppliesToNode_EndpointIsNotDynamic_ReturnsFalse()
+ [Fact]
+ public void IEndpointSelectorPolicy_AppliesToNode_EndpointIsNotDynamic_ReturnsFalse()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>())),
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
};
- var policy = (IEndpointSelectorPolicy)CreatePolicy();
+ var policy = (IEndpointSelectorPolicy)CreatePolicy();
- // Act
- var result = policy.AppliesToEndpoints(endpoints);
+ // Act
+ var result = policy.AppliesToEndpoints(endpoints);
- // Assert
- Assert.False(result);
- }
+ // Assert
+ Assert.False(result);
+ }
- [Fact]
- public void GetEdges_GroupsByHttpMethod()
+ [Fact]
+ public void GetEdges_GroupsByHttpMethod()
+ {
+ // Arrange
+ var endpoints = new[]
{
- // Arrange
- var endpoints = new[]
- {
// These are arrange in an order that we won't actually see in a product scenario. It's done
// this way so we can verify that ordering is preserved by GetEdges.
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
@@ -170,42 +170,42 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>())),
};
- var policy = CreatePolicy();
-
- // Act
- var edges = policy.GetEdges(endpoints);
-
- // Assert
- Assert.Collection(
- edges.OrderBy(e => e.State),
- e =>
- {
- Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- });
- }
+ var policy = CreatePolicy();
- [Fact]
- public void GetEdges_GroupsByHttpMethod_Cors()
- {
- // Arrange
- var endpoints = new[]
+ // Act
+ var edges = policy.GetEdges(endpoints);
+
+ // Assert
+ Assert.Collection(
+ edges.OrderBy(e => e.State),
+ e =>
+ {
+ Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
{
+ Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ });
+ }
+
+ [Fact]
+ public void GetEdges_GroupsByHttpMethod_Cors()
+ {
+ // Arrange
+ var endpoints = new[]
+ {
// These are arrange in an order that we won't actually see in a product scenario. It's done
// this way so we can verify that ordering is preserved by GetEdges.
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
@@ -215,62 +215,62 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HttpMethodMetadata(Array.Empty<string>(), acceptCorsPreflight: true)),
};
- var policy = CreatePolicy();
-
- // Act
- var edges = policy.GetEdges(endpoints);
-
- // Assert
- Assert.Collection(
- edges.OrderBy(e => e.State),
- e =>
- {
- Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
- });
- }
+ var policy = CreatePolicy();
- [Fact] // See explanation in GetEdges for how this case is different
- public void GetEdges_GroupsByHttpMethod_CreatesHttp405Endpoint()
- {
- // Arrange
- var endpoints = new[]
+ // Act
+ var edges = policy.GetEdges(endpoints);
+
+ // Assert
+ Assert.Collection(
+ edges.OrderBy(e => e.State),
+ e =>
+ {
+ Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
{
+ Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[2], endpoints[4], }, e.Endpoints.ToArray());
+ });
+ }
+
+ [Fact] // See explanation in GetEdges for how this case is different
+ public void GetEdges_GroupsByHttpMethod_CreatesHttp405Endpoint()
+ {
+ // Arrange
+ var endpoints = new[]
+ {
// These are arrange in an order that we won't actually see in a product scenario. It's done
// this way so we can verify that ordering is preserved by GetEdges.
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
@@ -278,43 +278,43 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HttpMethodMetadata(new[] { "PUT", "POST" })),
};
- var policy = CreatePolicy();
-
- // Act
- var edges = policy.GetEdges(endpoints);
-
- // Assert
- Assert.Collection(
- edges.OrderBy(e => e.State),
- e =>
- {
- Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
- Assert.Equal(Http405EndpointDisplayName, e.Endpoints.Single().DisplayName);
- },
- e =>
- {
- Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- });
+ var policy = CreatePolicy();
- }
+ // Act
+ var edges = policy.GetEdges(endpoints);
- [Fact] // See explanation in GetEdges for how this case is different
- public void GetEdges_GroupsByHttpMethod_CreatesHttp405Endpoint_CORS()
- {
- // Arrange
- var endpoints = new[]
+ // Assert
+ Assert.Collection(
+ edges.OrderBy(e => e.State),
+ e =>
+ {
+ Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
+ Assert.Equal(Http405EndpointDisplayName, e.Endpoints.Single().DisplayName);
+ },
+ e =>
{
+ Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ });
+
+ }
+
+ [Fact] // See explanation in GetEdges for how this case is different
+ public void GetEdges_GroupsByHttpMethod_CreatesHttp405Endpoint_CORS()
+ {
+ // Arrange
+ var endpoints = new[]
+ {
// These are arrange in an order that we won't actually see in a product scenario. It's done
// this way so we can verify that ordering is preserved by GetEdges.
CreateEndpoint("/", new HttpMethodMetadata(new[] { "GET", })),
@@ -322,80 +322,79 @@ namespace Microsoft.AspNetCore.Routing.Matching
CreateEndpoint("/", new HttpMethodMetadata(new[] { "PUT", "POST" })),
};
- var policy = CreatePolicy();
-
- // Act
- var edges = policy.GetEdges(endpoints);
-
- // Assert
- Assert.Collection(
- edges.OrderBy(e => e.State),
- e =>
- {
- Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
- Assert.Equal(Http405EndpointDisplayName, e.Endpoints.Single().DisplayName);
- },
- e =>
- {
- Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[0], endpoints[1], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[1], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[1], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
- Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
- },
- e =>
- {
- Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: true), e.State);
- Assert.Equal(new[] { endpoints[1], }, e.Endpoints.ToArray());
- });
- }
+ var policy = CreatePolicy();
- private static RouteEndpoint CreateEndpoint(string template, HttpMethodMetadata httpMethodMetadata, params object[] more)
- {
- var metadata = new List<object>();
- if (httpMethodMetadata != null)
- {
- metadata.Add(httpMethodMetadata);
- }
+ // Act
+ var edges = policy.GetEdges(endpoints);
- if (more != null)
+ // Assert
+ Assert.Collection(
+ edges.OrderBy(e => e.State),
+ e =>
{
- metadata.AddRange(more);
- }
-
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template),
- 0,
- new EndpointMetadataCollection(metadata),
- $"test: {template}");
- }
+ Assert.Equal(new EdgeKey(AnyMethod, isCorsPreflightRequest: false), e.State);
+ Assert.Equal(Http405EndpointDisplayName, e.Endpoints.Single().DisplayName);
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[0], endpoints[1], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("GET", isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[1], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("POST", isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[1], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: false), e.State);
+ Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray());
+ },
+ e =>
+ {
+ Assert.Equal(new EdgeKey("PUT", isCorsPreflightRequest: true), e.State);
+ Assert.Equal(new[] { endpoints[1], }, e.Endpoints.ToArray());
+ });
+ }
- private static HttpMethodMatcherPolicy CreatePolicy()
+ private static RouteEndpoint CreateEndpoint(string template, HttpMethodMetadata httpMethodMetadata, params object[] more)
+ {
+ var metadata = new List<object>();
+ if (httpMethodMetadata != null)
{
- return new HttpMethodMatcherPolicy();
+ metadata.Add(httpMethodMetadata);
}
- private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ if (more != null)
{
- public bool IsDynamic => true;
+ metadata.AddRange(more);
}
+
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template),
+ 0,
+ new EndpointMetadataCollection(metadata),
+ $"test: {template}");
+ }
+
+ private static HttpMethodMatcherPolicy CreatePolicy()
+ {
+ return new HttpMethodMatcherPolicy();
+ }
+
+ private class DynamicEndpointMetadata : IDynamicEndpointMetadata
+ {
+ public bool IsDynamic => true;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieFactoryTest.cs b/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieFactoryTest.cs
index 0b52ef77e9..e0b41d7a90 100644
--- a/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieFactoryTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieFactoryTest.cs
@@ -4,48 +4,47 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class ILEmitTrieFactoryTest
{
- public class ILEmitTrieFactoryTest
+ // We never vectorize on 32bit, so that's part of the test.
+ [Fact]
+ public void ShouldVectorize_ReturnsTrue_ForLargeEnoughStrings()
{
- // We never vectorize on 32bit, so that's part of the test.
- [Fact]
- public void ShouldVectorize_ReturnsTrue_ForLargeEnoughStrings()
- {
- // Arrange
- var is64Bit = IntPtr.Size == 8;
- var expected = is64Bit;
+ // Arrange
+ var is64Bit = IntPtr.Size == 8;
+ var expected = is64Bit;
- var entries = new[]
- {
+ var entries = new[]
+ {
("foo", 0),
("badr", 0),
("", 0),
};
- // Act
- var actual = ILEmitTrieFactory.ShouldVectorize(entries);
+ // Act
+ var actual = ILEmitTrieFactory.ShouldVectorize(entries);
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public void ShouldVectorize_ReturnsFalseForSmallStrings()
+ [Fact]
+ public void ShouldVectorize_ReturnsFalseForSmallStrings()
+ {
+ // Arrange
+ var entries = new[]
{
- // Arrange
- var entries = new[]
- {
("foo", 0),
("sma", 0),
("", 0),
};
- // Act
- var actual = ILEmitTrieFactory.ShouldVectorize(entries);
+ // Act
+ var actual = ILEmitTrieFactory.ShouldVectorize(entries);
- // Assert
- Assert.False(actual);
- }
+ // Assert
+ Assert.False(actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs
index b182730a23..7fff4c814b 100644
--- a/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/ILEmitTrieJumpTableTest.cs
@@ -1,226 +1,225 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Moq;
using System.Threading.Tasks;
+using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// We get a lot of good coverage of basics since this implementation is used
+// as the default in many cases. The tests here are focused on details of the
+// implementation (boundaries, casing, non-ASCII).
+public abstract class ILEmitTreeJumpTableTestBase : MultipleEntryJumpTableTest
{
- // We get a lot of good coverage of basics since this implementation is used
- // as the default in many cases. The tests here are focused on details of the
- // implementation (boundaries, casing, non-ASCII).
- public abstract class ILEmitTreeJumpTableTestBase : MultipleEntryJumpTableTest
+ public abstract bool Vectorize { get; }
+
+ internal override JumpTable CreateTable(
+ int defaultDestination,
+ int exitDestination,
+ params (string text, int destination)[] entries)
+ {
+ var fallback = new DictionaryJumpTable(defaultDestination, exitDestination, entries);
+ var table = new ILEmitTrieJumpTable(defaultDestination, exitDestination, entries, Vectorize, fallback);
+ table.InitializeILDelegate();
+ return table;
+ }
+
+ [Fact] // Not calling CreateTable here because we want to test the initialization
+ public async Task InitializeILDelegateAsync_ReplacesDelegate()
+ {
+ // Arrange
+ var table = new ILEmitTrieJumpTable(0, -1, new[] { ("hi", 1), }, Vectorize, Mock.Of<JumpTable>());
+ var original = table._getDestination;
+
+ // Act
+ await table.InitializeILDelegateAsync();
+
+ // Assert
+ Assert.NotSame(original, table._getDestination);
+ }
+
+ // Tests that we can detect non-ASCII characters and use the fallback jump table.
+ // Testing different indices since that affects which part of the code is running.
+ // \u007F = lowest non-ASCII character
+ // \uFFFF = highest non-ASCII character
+ [Theory]
+
+ // non-ASCII character in first section non-vectorized comparisons
+ [InlineData("he\u007F", "he\u007Flo-world", 0, 3)]
+ [InlineData("he\uFFFF", "he\uFFFFlo-world", 0, 3)]
+ [InlineData("e\u007F", "he\u007Flo-world", 1, 2)]
+ [InlineData("e\uFFFF", "he\uFFFFlo-world", 1, 2)]
+ [InlineData("\u007F", "he\u007Flo-world", 2, 1)]
+ [InlineData("\uFFFF", "he\uFFFFlo-world", 2, 1)]
+
+ // non-ASCII character in first section vectorized comparions
+ [InlineData("hel\u007F", "hel\u007Fo-world", 0, 4)]
+ [InlineData("hel\uFFFF", "hel\uFFFFo-world", 0, 4)]
+ [InlineData("el\u007Fo", "hel\u007Fo-world", 1, 4)]
+ [InlineData("el\uFFFFo", "hel\uFFFFo-world", 1, 4)]
+ [InlineData("l\u007Fo-", "hel\u007Fo-world", 2, 4)]
+ [InlineData("l\uFFFFo-", "hel\uFFFFo-world", 2, 4)]
+ [InlineData("\u007Fo-w", "hel\u007Fo-world", 3, 4)]
+ [InlineData("\uFFFFo-w", "hel\uFFFFo-world", 3, 4)]
+
+ // non-ASCII character in second section non-vectorized comparisons
+ [InlineData("hello-\u007F", "hello-\u007Forld", 0, 7)]
+ [InlineData("hello-\uFFFF", "hello-\uFFFForld", 0, 7)]
+ [InlineData("ello-\u007F", "hello-\u007Forld", 1, 6)]
+ [InlineData("ello-\uFFFF", "hello-\uFFFForld", 1, 6)]
+ [InlineData("llo-\u007F", "hello-\u007Forld", 2, 5)]
+ [InlineData("llo-\uFFFF", "hello-\uFFFFForld", 2, 5)]
+
+ // non-ASCII character in first section vectorized comparions
+ [InlineData("hello-w\u007F", "hello-w\u007Forld", 0, 8)]
+ [InlineData("hello-w\uFFFF", "hello-w\uFFFForld", 0, 8)]
+ [InlineData("ello-w\u007Fo", "hello-w\u007Forld", 1, 8)]
+ [InlineData("ello-w\uFFFFo", "hello-w\uFFFForld", 1, 8)]
+ [InlineData("llo-w\u007For", "hello-w\u007Forld", 2, 8)]
+ [InlineData("llo-w\uFFFFor", "hello-w\uFFFForld", 2, 8)]
+ [InlineData("lo-w\u007Forl", "hello-w\u007Forld", 3, 8)]
+ [InlineData("lo-w\uFFFForl", "hello-w\uFFFForld", 3, 8)]
+ public void GetDestination_Found_IncludesNonAsciiCharacters(string entry, string path, int start, int length)
+ {
+ // Makes it easy to spot invalid tests
+ Assert.Equal(entry.Length, length);
+ Assert.Equal(entry, path.Substring(start, length), ignoreCase: true);
+
+ // Arrange
+ var table = CreateTable(0, -1, new[] { (entry, 1), });
+
+ var segment = new PathSegment(start, length);
+
+ // Act
+ var result = table.GetDestination(path, segment);
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ // Tests for difference in casing with ASCII casing rules. Verifies our case
+ // manipulation algorthm is correct.
+ //
+ // We convert from upper case to lower
+ // 'A' and 'a' are 32 bits apart at the low end
+ // 'Z' and 'z' are 32 bits apart at the high end
+ [Theory]
+
+ // character in first section non-vectorized comparisons
+ [InlineData("heA", "healo-world", 0, 3)]
+ [InlineData("heZ", "hezlo-world", 0, 3)]
+ [InlineData("eA", "healo-world", 1, 2)]
+ [InlineData("eZ", "hezlo-world", 1, 2)]
+ [InlineData("A", "healo-world", 2, 1)]
+ [InlineData("Z", "hezlo-world", 2, 1)]
+
+ // character in first section vectorized comparions
+ [InlineData("helA", "helao-world", 0, 4)]
+ [InlineData("helZ", "helzo-world", 0, 4)]
+ [InlineData("elAo", "helao-world", 1, 4)]
+ [InlineData("elZo", "helzo-world", 1, 4)]
+ [InlineData("lAo-", "helao-world", 2, 4)]
+ [InlineData("lZo-", "helzo-world", 2, 4)]
+ [InlineData("Ao-w", "helao-world", 3, 4)]
+ [InlineData("Zo-w", "helzo-world", 3, 4)]
+
+ // character in second section non-vectorized comparisons
+ [InlineData("hello-A", "hello-aorld", 0, 7)]
+ [InlineData("hello-Z", "hello-zorld", 0, 7)]
+ [InlineData("ello-A", "hello-aorld", 1, 6)]
+ [InlineData("ello-Z", "hello-zorld", 1, 6)]
+ [InlineData("llo-A", "hello-aorld", 2, 5)]
+ [InlineData("llo-Z", "hello-zForld", 2, 5)]
+
+ // character in first section vectorized comparions
+ [InlineData("hello-wA", "hello-waorld", 0, 8)]
+ [InlineData("hello-wZ", "hello-wzorld", 0, 8)]
+ [InlineData("ello-wAo", "hello-waorld", 1, 8)]
+ [InlineData("ello-wZo", "hello-wzorld", 1, 8)]
+ [InlineData("llo-wAor", "hello-waorld", 2, 8)]
+ [InlineData("llo-wZor", "hello-wzorld", 2, 8)]
+ [InlineData("lo-wAorl", "hello-waorld", 3, 8)]
+ [InlineData("lo-wZorl", "hello-wzorld", 3, 8)]
+ public void GetDestination_Found_IncludesCharactersWithCasingDifference(string entry, string path, int start, int length)
{
- public abstract bool Vectorize { get; }
-
- internal override JumpTable CreateTable(
- int defaultDestination,
- int exitDestination,
- params (string text, int destination)[] entries)
- {
- var fallback = new DictionaryJumpTable(defaultDestination, exitDestination, entries);
- var table = new ILEmitTrieJumpTable(defaultDestination, exitDestination, entries, Vectorize, fallback);
- table.InitializeILDelegate();
- return table;
- }
-
- [Fact] // Not calling CreateTable here because we want to test the initialization
- public async Task InitializeILDelegateAsync_ReplacesDelegate()
- {
- // Arrange
- var table = new ILEmitTrieJumpTable(0, -1, new[] { ("hi", 1), }, Vectorize, Mock.Of<JumpTable>());
- var original = table._getDestination;
-
- // Act
- await table.InitializeILDelegateAsync();
-
- // Assert
- Assert.NotSame(original, table._getDestination);
- }
-
- // Tests that we can detect non-ASCII characters and use the fallback jump table.
- // Testing different indices since that affects which part of the code is running.
- // \u007F = lowest non-ASCII character
- // \uFFFF = highest non-ASCII character
- [Theory]
-
- // non-ASCII character in first section non-vectorized comparisons
- [InlineData("he\u007F", "he\u007Flo-world", 0, 3)]
- [InlineData("he\uFFFF", "he\uFFFFlo-world", 0, 3)]
- [InlineData("e\u007F", "he\u007Flo-world", 1, 2)]
- [InlineData("e\uFFFF", "he\uFFFFlo-world", 1, 2)]
- [InlineData("\u007F", "he\u007Flo-world", 2, 1)]
- [InlineData("\uFFFF", "he\uFFFFlo-world", 2, 1)]
-
- // non-ASCII character in first section vectorized comparions
- [InlineData("hel\u007F", "hel\u007Fo-world", 0, 4)]
- [InlineData("hel\uFFFF", "hel\uFFFFo-world", 0, 4)]
- [InlineData("el\u007Fo", "hel\u007Fo-world", 1, 4)]
- [InlineData("el\uFFFFo", "hel\uFFFFo-world", 1, 4)]
- [InlineData("l\u007Fo-", "hel\u007Fo-world", 2, 4)]
- [InlineData("l\uFFFFo-", "hel\uFFFFo-world", 2, 4)]
- [InlineData("\u007Fo-w", "hel\u007Fo-world", 3, 4)]
- [InlineData("\uFFFFo-w", "hel\uFFFFo-world", 3, 4)]
-
- // non-ASCII character in second section non-vectorized comparisons
- [InlineData("hello-\u007F", "hello-\u007Forld", 0, 7)]
- [InlineData("hello-\uFFFF", "hello-\uFFFForld", 0, 7)]
- [InlineData("ello-\u007F", "hello-\u007Forld", 1, 6)]
- [InlineData("ello-\uFFFF", "hello-\uFFFForld", 1, 6)]
- [InlineData("llo-\u007F", "hello-\u007Forld", 2, 5)]
- [InlineData("llo-\uFFFF", "hello-\uFFFFForld", 2, 5)]
-
- // non-ASCII character in first section vectorized comparions
- [InlineData("hello-w\u007F", "hello-w\u007Forld", 0, 8)]
- [InlineData("hello-w\uFFFF", "hello-w\uFFFForld", 0, 8)]
- [InlineData("ello-w\u007Fo", "hello-w\u007Forld", 1, 8)]
- [InlineData("ello-w\uFFFFo", "hello-w\uFFFForld", 1, 8)]
- [InlineData("llo-w\u007For", "hello-w\u007Forld", 2, 8)]
- [InlineData("llo-w\uFFFFor", "hello-w\uFFFForld", 2, 8)]
- [InlineData("lo-w\u007Forl", "hello-w\u007Forld", 3, 8)]
- [InlineData("lo-w\uFFFForl", "hello-w\uFFFForld", 3, 8)]
- public void GetDestination_Found_IncludesNonAsciiCharacters(string entry, string path, int start, int length)
- {
- // Makes it easy to spot invalid tests
- Assert.Equal(entry.Length, length);
- Assert.Equal(entry, path.Substring(start, length), ignoreCase: true);
-
- // Arrange
- var table = CreateTable(0, -1, new[] { (entry, 1), });
-
- var segment = new PathSegment(start, length);
-
- // Act
- var result = table.GetDestination(path, segment);
-
- // Assert
- Assert.Equal(1, result);
- }
-
- // Tests for difference in casing with ASCII casing rules. Verifies our case
- // manipulation algorthm is correct.
- //
- // We convert from upper case to lower
- // 'A' and 'a' are 32 bits apart at the low end
- // 'Z' and 'z' are 32 bits apart at the high end
- [Theory]
-
- // character in first section non-vectorized comparisons
- [InlineData("heA", "healo-world", 0, 3)]
- [InlineData("heZ", "hezlo-world", 0, 3)]
- [InlineData("eA", "healo-world", 1, 2)]
- [InlineData("eZ", "hezlo-world", 1, 2)]
- [InlineData("A", "healo-world", 2, 1)]
- [InlineData("Z", "hezlo-world", 2, 1)]
-
- // character in first section vectorized comparions
- [InlineData("helA", "helao-world", 0, 4)]
- [InlineData("helZ", "helzo-world", 0, 4)]
- [InlineData("elAo", "helao-world", 1, 4)]
- [InlineData("elZo", "helzo-world", 1, 4)]
- [InlineData("lAo-", "helao-world", 2, 4)]
- [InlineData("lZo-", "helzo-world", 2, 4)]
- [InlineData("Ao-w", "helao-world", 3, 4)]
- [InlineData("Zo-w", "helzo-world", 3, 4)]
-
- // character in second section non-vectorized comparisons
- [InlineData("hello-A", "hello-aorld", 0, 7)]
- [InlineData("hello-Z", "hello-zorld", 0, 7)]
- [InlineData("ello-A", "hello-aorld", 1, 6)]
- [InlineData("ello-Z", "hello-zorld", 1, 6)]
- [InlineData("llo-A", "hello-aorld", 2, 5)]
- [InlineData("llo-Z", "hello-zForld", 2, 5)]
-
- // character in first section vectorized comparions
- [InlineData("hello-wA", "hello-waorld", 0, 8)]
- [InlineData("hello-wZ", "hello-wzorld", 0, 8)]
- [InlineData("ello-wAo", "hello-waorld", 1, 8)]
- [InlineData("ello-wZo", "hello-wzorld", 1, 8)]
- [InlineData("llo-wAor", "hello-waorld", 2, 8)]
- [InlineData("llo-wZor", "hello-wzorld", 2, 8)]
- [InlineData("lo-wAorl", "hello-waorld", 3, 8)]
- [InlineData("lo-wZorl", "hello-wzorld", 3, 8)]
- public void GetDestination_Found_IncludesCharactersWithCasingDifference(string entry, string path, int start, int length)
- {
- // Makes it easy to spot invalid tests
- Assert.Equal(entry.Length, length);
- Assert.Equal(entry, path.Substring(start, length), ignoreCase: true);
-
- // Arrange
- var table = CreateTable(0, -1, new[] { (entry, 1), });
-
- var segment = new PathSegment(start, length);
-
- // Act
- var result = table.GetDestination(path, segment);
-
- // Assert
- Assert.Equal(1, result);
- }
-
- // Tests for difference in casing with ASCII casing rules. Verifies our case
- // manipulation algorthm is correct.
- //
- // We convert from upper case to lower
- // '@' and '`' are 32 bits apart at the low end
- // '[' and '}' are 32 bits apart at the high end
- //
- // How to understand these tests:
- // "an @ should not be converted to a ` since it is out of range"
- [Theory]
-
- // character in first section non-vectorized comparisons
- [InlineData("he@", "he`lo-world", 0, 3)]
- [InlineData("he[", "he{lo-world", 0, 3)]
- [InlineData("e@", "he`lo-world", 1, 2)]
- [InlineData("e[", "he{lo-world", 1, 2)]
- [InlineData("@", "he`lo-world", 2, 1)]
- [InlineData("[", "he{lo-world", 2, 1)]
-
- // character in first section vectorized comparions
- [InlineData("hel@", "hel`o-world", 0, 4)]
- [InlineData("hel[", "hel{o-world", 0, 4)]
- [InlineData("el@o", "hel`o-world", 1, 4)]
- [InlineData("el[o", "hel{o-world", 1, 4)]
- [InlineData("l@o-", "hel`o-world", 2, 4)]
- [InlineData("l[o-", "hel{o-world", 2, 4)]
- [InlineData("@o-w", "hel`o-world", 3, 4)]
- [InlineData("[o-w", "hel{o-world", 3, 4)]
-
- // character in second section non-vectorized comparisons
- [InlineData("hello-@", "hello-`orld", 0, 7)]
- [InlineData("hello-[", "hello-{orld", 0, 7)]
- [InlineData("ello-@", "hello-`orld", 1, 6)]
- [InlineData("ello-[", "hello-{orld", 1, 6)]
- [InlineData("llo-@", "hello-`orld", 2, 5)]
- [InlineData("llo-[", "hello-{Forld", 2, 5)]
-
- // character in first section vectorized comparions
- [InlineData("hello-w@", "hello-w`orld", 0, 8)]
- [InlineData("hello-w[", "hello-w{orld", 0, 8)]
- [InlineData("ello-w@o", "hello-w`orld", 1, 8)]
- [InlineData("ello-w[o", "hello-w{orld", 1, 8)]
- [InlineData("llo-w@or", "hello-w`orld", 2, 8)]
- [InlineData("llo-w[or", "hello-w{orld", 2, 8)]
- [InlineData("lo-w@orl", "hello-w`orld", 3, 8)]
- [InlineData("lo-w[orl", "hello-w{orld", 3, 8)]
- public void GetDestination_NotFound_IncludesCharactersWithCasingDifference(string entry, string path, int start, int length)
- {
- // Makes it easy to spot invalid tests
- Assert.Equal(entry.Length, length);
- Assert.NotEqual(entry, path.Substring(start, length));
-
- // Arrange
- var table = CreateTable(0, -1, new[] { (entry, 1), });
-
- var segment = new PathSegment(start, length);
-
- // Act
- var result = table.GetDestination(path, segment);
-
- // Assert
- Assert.Equal(0, result);
- }
+ // Makes it easy to spot invalid tests
+ Assert.Equal(entry.Length, length);
+ Assert.Equal(entry, path.Substring(start, length), ignoreCase: true);
+
+ // Arrange
+ var table = CreateTable(0, -1, new[] { (entry, 1), });
+
+ var segment = new PathSegment(start, length);
+
+ // Act
+ var result = table.GetDestination(path, segment);
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ // Tests for difference in casing with ASCII casing rules. Verifies our case
+ // manipulation algorthm is correct.
+ //
+ // We convert from upper case to lower
+ // '@' and '`' are 32 bits apart at the low end
+ // '[' and '}' are 32 bits apart at the high end
+ //
+ // How to understand these tests:
+ // "an @ should not be converted to a ` since it is out of range"
+ [Theory]
+
+ // character in first section non-vectorized comparisons
+ [InlineData("he@", "he`lo-world", 0, 3)]
+ [InlineData("he[", "he{lo-world", 0, 3)]
+ [InlineData("e@", "he`lo-world", 1, 2)]
+ [InlineData("e[", "he{lo-world", 1, 2)]
+ [InlineData("@", "he`lo-world", 2, 1)]
+ [InlineData("[", "he{lo-world", 2, 1)]
+
+ // character in first section vectorized comparions
+ [InlineData("hel@", "hel`o-world", 0, 4)]
+ [InlineData("hel[", "hel{o-world", 0, 4)]
+ [InlineData("el@o", "hel`o-world", 1, 4)]
+ [InlineData("el[o", "hel{o-world", 1, 4)]
+ [InlineData("l@o-", "hel`o-world", 2, 4)]
+ [InlineData("l[o-", "hel{o-world", 2, 4)]
+ [InlineData("@o-w", "hel`o-world", 3, 4)]
+ [InlineData("[o-w", "hel{o-world", 3, 4)]
+
+ // character in second section non-vectorized comparisons
+ [InlineData("hello-@", "hello-`orld", 0, 7)]
+ [InlineData("hello-[", "hello-{orld", 0, 7)]
+ [InlineData("ello-@", "hello-`orld", 1, 6)]
+ [InlineData("ello-[", "hello-{orld", 1, 6)]
+ [InlineData("llo-@", "hello-`orld", 2, 5)]
+ [InlineData("llo-[", "hello-{Forld", 2, 5)]
+
+ // character in first section vectorized comparions
+ [InlineData("hello-w@", "hello-w`orld", 0, 8)]
+ [InlineData("hello-w[", "hello-w{orld", 0, 8)]
+ [InlineData("ello-w@o", "hello-w`orld", 1, 8)]
+ [InlineData("ello-w[o", "hello-w{orld", 1, 8)]
+ [InlineData("llo-w@or", "hello-w`orld", 2, 8)]
+ [InlineData("llo-w[or", "hello-w{orld", 2, 8)]
+ [InlineData("lo-w@orl", "hello-w`orld", 3, 8)]
+ [InlineData("lo-w[orl", "hello-w{orld", 3, 8)]
+ public void GetDestination_NotFound_IncludesCharactersWithCasingDifference(string entry, string path, int start, int length)
+ {
+ // Makes it easy to spot invalid tests
+ Assert.Equal(entry.Length, length);
+ Assert.NotEqual(entry, path.Substring(start, length));
+
+ // Arrange
+ var table = CreateTable(0, -1, new[] { (entry, 1), });
+
+ var segment = new PathSegment(start, length);
+
+ // Act
+ var result = table.GetDestination(path, segment);
+
+ // Assert
+ Assert.Equal(0, result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/LinearSearchJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/LinearSearchJumpTableTest.cs
index c1ba75dbb8..ff177bf528 100644
--- a/src/Http/Routing/test/UnitTests/Matching/LinearSearchJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/LinearSearchJumpTableTest.cs
@@ -2,16 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class LinearSearchJumpTableTest : MultipleEntryJumpTableTest
{
- public class LinearSearchJumpTableTest : MultipleEntryJumpTableTest
+ internal override JumpTable CreateTable(
+ int defaultDestination,
+ int existDestination,
+ params (string text, int destination)[] entries)
{
- internal override JumpTable CreateTable(
- int defaultDestination,
- int existDestination,
- params (string text, int destination)[] entries)
- {
- return new LinearSearchJumpTable(defaultDestination, existDestination, entries);
- }
+ return new LinearSearchJumpTable(defaultDestination, existDestination, entries);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/MatcherAssert.cs b/src/Http/Routing/test/UnitTests/Matching/MatcherAssert.cs
index 35c3ab87e4..670e30e31a 100644
--- a/src/Http/Routing/test/UnitTests/Matching/MatcherAssert.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/MatcherAssert.cs
@@ -8,106 +8,105 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Xunit.Sdk;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal static class MatcherAssert
{
- internal static class MatcherAssert
+ public static void AssertRouteValuesEqual(object expectedValues, RouteValueDictionary actualValues)
{
- public static void AssertRouteValuesEqual(object expectedValues, RouteValueDictionary actualValues)
- {
- AssertRouteValuesEqual(new RouteValueDictionary(expectedValues), actualValues);
- }
+ AssertRouteValuesEqual(new RouteValueDictionary(expectedValues), actualValues);
+ }
- public static void AssertRouteValuesEqual(RouteValueDictionary expectedValues, RouteValueDictionary actualValues)
+ public static void AssertRouteValuesEqual(RouteValueDictionary expectedValues, RouteValueDictionary actualValues)
+ {
+ if (expectedValues.Count != actualValues.Count ||
+ !expectedValues.OrderBy(kvp => kvp.Key).SequenceEqual(actualValues.OrderBy(kvp => kvp.Key)))
{
- if (expectedValues.Count != actualValues.Count ||
- !expectedValues.OrderBy(kvp => kvp.Key).SequenceEqual(actualValues.OrderBy(kvp => kvp.Key)))
- {
- throw new XunitException(
- $"Expected values:{FormatRouteValues(expectedValues)} Actual values: {FormatRouteValues(actualValues)}.");
- }
+ throw new XunitException(
+ $"Expected values:{FormatRouteValues(expectedValues)} Actual values: {FormatRouteValues(actualValues)}.");
}
+ }
- public static void AssertMatch(HttpContext httpContext, Endpoint expected)
- {
- AssertMatch(httpContext, expected, new RouteValueDictionary());
- }
+ public static void AssertMatch(HttpContext httpContext, Endpoint expected)
+ {
+ AssertMatch(httpContext, expected, new RouteValueDictionary());
+ }
- public static void AssertMatch(HttpContext httpContext, Endpoint expected, bool ignoreValues)
- {
- AssertMatch(httpContext, expected, new RouteValueDictionary(), ignoreValues);
- }
+ public static void AssertMatch(HttpContext httpContext, Endpoint expected, bool ignoreValues)
+ {
+ AssertMatch(httpContext, expected, new RouteValueDictionary(), ignoreValues);
+ }
+
+ public static void AssertMatch(HttpContext httpContext, Endpoint expected, object values)
+ {
+ AssertMatch(httpContext, expected, new RouteValueDictionary(values));
+ }
+
+ public static void AssertMatch(HttpContext httpContext, Endpoint expected, string[] keys, string[] values)
+ {
+ keys = keys ?? Array.Empty<string>();
+ values = values ?? Array.Empty<string>();
- public static void AssertMatch(HttpContext httpContext, Endpoint expected, object values)
+ if (keys.Length != values.Length)
{
- AssertMatch(httpContext, expected, new RouteValueDictionary(values));
+ throw new XunitException("Keys and Values must be the same length.");
}
- public static void AssertMatch(HttpContext httpContext, Endpoint expected, string[] keys, string[] values)
+ var zipped = keys.Zip(values, (k, v) => new KeyValuePair<string, object>(k, v));
+ AssertMatch(httpContext, expected, new RouteValueDictionary(zipped));
+ }
+
+ public static void AssertMatch(
+ HttpContext httpContext,
+ Endpoint expected,
+ RouteValueDictionary values,
+ bool ignoreValues = false)
+ {
+ if (httpContext.GetEndpoint() == null)
{
- keys = keys ?? Array.Empty<string>();
- values = values ?? Array.Empty<string>();
+ throw new XunitException($"Was expected to match '{expected.DisplayName}' but did not match.");
+ }
- if (keys.Length != values.Length)
- {
- throw new XunitException("Keys and Values must be the same length.");
- }
+ var actualValues = httpContext.Request.RouteValues;
- var zipped = keys.Zip(values, (k, v) => new KeyValuePair<string, object>(k, v));
- AssertMatch(httpContext, expected, new RouteValueDictionary(zipped));
+ if (actualValues == null)
+ {
+ throw new XunitException("RouteValues is null.");
}
- public static void AssertMatch(
- HttpContext httpContext,
- Endpoint expected,
- RouteValueDictionary values,
- bool ignoreValues = false)
+ if (!object.ReferenceEquals(expected, httpContext.GetEndpoint()))
{
- if (httpContext.GetEndpoint() == null)
- {
- throw new XunitException($"Was expected to match '{expected.DisplayName}' but did not match.");
- }
-
- var actualValues = httpContext.Request.RouteValues;
-
- if (actualValues == null)
- {
- throw new XunitException("RouteValues is null.");
- }
-
- if (!object.ReferenceEquals(expected, httpContext.GetEndpoint()))
- {
- throw new XunitException(
- $"Was expected to match '{expected.DisplayName}' but matched " +
- $"'{httpContext.GetEndpoint().DisplayName}' with values: {FormatRouteValues(actualValues)}.");
- }
-
- if (!ignoreValues)
- {
- // Note: this comparison is intended for unit testing, and is stricter than necessary to make tests
- // more precise. Route value comparisons in product code are more flexible than a simple .Equals.
- if (values.Count != actualValues.Count ||
- !values.OrderBy(kvp => kvp.Key).SequenceEqual(actualValues.OrderBy(kvp => kvp.Key)))
- {
- throw new XunitException(
- $"Was expected to match '{expected.DisplayName}' with values {FormatRouteValues(values)} but matched " +
- $"values: {FormatRouteValues(actualValues)}.");
- }
- }
+ throw new XunitException(
+ $"Was expected to match '{expected.DisplayName}' but matched " +
+ $"'{httpContext.GetEndpoint().DisplayName}' with values: {FormatRouteValues(actualValues)}.");
}
- public static void AssertNotMatch(HttpContext httpContext)
+ if (!ignoreValues)
{
- if (httpContext.GetEndpoint() != null)
+ // Note: this comparison is intended for unit testing, and is stricter than necessary to make tests
+ // more precise. Route value comparisons in product code are more flexible than a simple .Equals.
+ if (values.Count != actualValues.Count ||
+ !values.OrderBy(kvp => kvp.Key).SequenceEqual(actualValues.OrderBy(kvp => kvp.Key)))
{
throw new XunitException(
- $"Was expected not to match '{httpContext.GetEndpoint().DisplayName}' " +
- $"but matched with values: {FormatRouteValues(httpContext.Request.RouteValues)}.");
+ $"Was expected to match '{expected.DisplayName}' with values {FormatRouteValues(values)} but matched " +
+ $"values: {FormatRouteValues(actualValues)}.");
}
}
+ }
- private static string FormatRouteValues(RouteValueDictionary values)
+ public static void AssertNotMatch(HttpContext httpContext)
+ {
+ if (httpContext.GetEndpoint() != null)
{
- return values == null ? "{}" : "{" + string.Join(", ", values.Select(kvp => $"{kvp.Key} = '{kvp.Value}'")) + "}";
+ throw new XunitException(
+ $"Was expected not to match '{httpContext.GetEndpoint().DisplayName}' " +
+ $"but matched with values: {FormatRouteValues(httpContext.Request.RouteValues)}.");
}
}
+
+ private static string FormatRouteValues(RouteValueDictionary values)
+ {
+ return values == null ? "{}" : "{" + string.Join(", ", values.Select(kvp => $"{kvp.Key} = '{kvp.Value}'")) + "}";
+ }
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.MultipleEndpoint.cs b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.MultipleEndpoint.cs
index faf86cce41..c8c12fbf32 100644
--- a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.MultipleEndpoint.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.MultipleEndpoint.cs
@@ -1,9 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public abstract partial class MatcherConformanceTest
{
- public abstract partial class MatcherConformanceTest
- {
- }
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.SingleEndpoint.cs b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.SingleEndpoint.cs
index 1813f9f3e6..5bf400dc41 100644
--- a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.SingleEndpoint.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.SingleEndpoint.cs
@@ -4,326 +4,325 @@
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public abstract partial class MatcherConformanceTest
{
- public abstract partial class MatcherConformanceTest
+ [Fact]
+ public virtual async Task Match_EmptyRoute()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/");
+ var httpContext = CreateContext("/");
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ [Fact]
+ public virtual async Task Match_SingleLiteralSegment()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/simple");
+ var httpContext = CreateContext("/simple");
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ [Fact]
+ public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/simple");
+ var httpContext = CreateContext("/simple/");
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ [Theory]
+ [InlineData("/simple")]
+ [InlineData("/sImpLe")]
+ [InlineData("/SIMPLE")]
+ public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/Simple");
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ // Some matchers will optimize for the ASCII case
+ [Theory]
+ [InlineData("/SÏmple", "/SÏmple")]
+ [InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
+ public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ // Matchers should operate on the decoded representation - a matcher that calls
+ // `httpContext.Request.Path.ToString()` will break this test.
+ [Theory]
+ [InlineData("/S%mple", "/S%mple")]
+ [InlineData("/S\\imple", "/S\\imple")] // surrogate pair
+ public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ [Theory]
+ [InlineData("/")]
+ [InlineData("/imple")]
+ [InlineData("/siple")]
+ [InlineData("/simple1")]
+ [InlineData("/simple/not-simple")]
+ [InlineData("/simple/a/b/c")]
+ public virtual async Task NotMatch_SingleLiteralSegment(string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/simple");
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ [Theory]
+ [InlineData("simple")]
+ [InlineData("/simple")]
+ [InlineData("~/simple")]
+ public virtual async Task Match_Sanitizies_Template(string template)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext("/simple");
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ // Matchers do their own 'splitting' of the path into segments, so including
+ // some extra variation here
+ [Theory]
+ [InlineData("/a/b", "/a/b")]
+ [InlineData("/a/b", "/A/B")]
+ [InlineData("/a/b", "/a/b/")]
+ [InlineData("/a/b/c", "/a/b/c")]
+ [InlineData("/a/b/c", "/a/b/c/")]
+ [InlineData("/a/b/c/d", "/a/b/c/d")]
+ [InlineData("/a/b/c/d", "/a/b/c/d/")]
+ public virtual async Task Match_MultipleLiteralSegments(string template, string path)
{
- [Fact]
- public virtual async Task Match_EmptyRoute()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/");
- var httpContext = CreateContext("/");
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- [Fact]
- public virtual async Task Match_SingleLiteralSegment()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/simple");
- var httpContext = CreateContext("/simple");
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- [Fact]
- public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/simple");
- var httpContext = CreateContext("/simple/");
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- [Theory]
- [InlineData("/simple")]
- [InlineData("/sImpLe")]
- [InlineData("/SIMPLE")]
- public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/Simple");
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- // Some matchers will optimize for the ASCII case
- [Theory]
- [InlineData("/SÏmple", "/SÏmple")]
- [InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
- public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- // Matchers should operate on the decoded representation - a matcher that calls
- // `httpContext.Request.Path.ToString()` will break this test.
- [Theory]
- [InlineData("/S%mple", "/S%mple")]
- [InlineData("/S\\imple", "/S\\imple")] // surrogate pair
- public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- [Theory]
- [InlineData("/")]
- [InlineData("/imple")]
- [InlineData("/siple")]
- [InlineData("/simple1")]
- [InlineData("/simple/not-simple")]
- [InlineData("/simple/a/b/c")]
- public virtual async Task NotMatch_SingleLiteralSegment(string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/simple");
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- [Theory]
- [InlineData("simple")]
- [InlineData("/simple")]
- [InlineData("~/simple")]
- public virtual async Task Match_Sanitizies_Template(string template)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext("/simple");
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- // Matchers do their own 'splitting' of the path into segments, so including
- // some extra variation here
- [Theory]
- [InlineData("/a/b", "/a/b")]
- [InlineData("/a/b", "/A/B")]
- [InlineData("/a/b", "/a/b/")]
- [InlineData("/a/b/c", "/a/b/c")]
- [InlineData("/a/b/c", "/a/b/c/")]
- [InlineData("/a/b/c/d", "/a/b/c/d")]
- [InlineData("/a/b/c/d", "/a/b/c/d/")]
- public virtual async Task Match_MultipleLiteralSegments(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint);
- }
-
- // Matchers do their own 'splitting' of the path into segments, so including
- // some extra variation here
- [Theory]
- [InlineData("/a/b", "/")]
- [InlineData("/a/b", "/a")]
- [InlineData("/a/b", "/a/")]
- [InlineData("/a/b", "/a//")]
- [InlineData("/a/b", "/aa/")]
- [InlineData("/a/b", "/a/bb")]
- [InlineData("/a/b", "/a/bb/")]
- [InlineData("/a/b/c", "/aa/b/c")]
- [InlineData("/a/b/c", "/a/bb/c/")]
- [InlineData("/a/b/c", "/a/b/cab")]
- [InlineData("/a/b/c", "/d/b/c/")]
- [InlineData("/a/b/c", "//b/c")]
- [InlineData("/a/b/c", "/a/b//")]
- [InlineData("/a/b/c", "/a/b/c/d")]
- [InlineData("/a/b/c", "/a/b/c/d/e")]
- public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- [Fact]
- public virtual async Task Match_SingleParameter()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/{p}");
- var httpContext = CreateContext("/14");
- var values = new RouteValueDictionary(new { p = "14", });
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, values);
- }
-
- [Fact]
- public virtual async Task Match_Constraint()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/{p:int}");
- var httpContext = CreateContext("/14");
- var values = new RouteValueDictionary(new { p = "14", });
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, values);
- }
-
- [Fact]
- public virtual async Task Match_SingleParameter_TrailingSlash()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/{p}");
- var httpContext = CreateContext("/14/");
- var values = new RouteValueDictionary(new { p = "14", });
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, values);
- }
-
- [Fact]
- public virtual async Task Match_SingleParameter_WeirdNames()
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/foo/{ }/{.!$%}/{dynamic.data}");
- var httpContext = CreateContext("/foo/space/weirdmatch/matcherid");
- var values = new RouteValueDictionary()
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint);
+ }
+
+ // Matchers do their own 'splitting' of the path into segments, so including
+ // some extra variation here
+ [Theory]
+ [InlineData("/a/b", "/")]
+ [InlineData("/a/b", "/a")]
+ [InlineData("/a/b", "/a/")]
+ [InlineData("/a/b", "/a//")]
+ [InlineData("/a/b", "/aa/")]
+ [InlineData("/a/b", "/a/bb")]
+ [InlineData("/a/b", "/a/bb/")]
+ [InlineData("/a/b/c", "/aa/b/c")]
+ [InlineData("/a/b/c", "/a/bb/c/")]
+ [InlineData("/a/b/c", "/a/b/cab")]
+ [InlineData("/a/b/c", "/d/b/c/")]
+ [InlineData("/a/b/c", "//b/c")]
+ [InlineData("/a/b/c", "/a/b//")]
+ [InlineData("/a/b/c", "/a/b/c/d")]
+ [InlineData("/a/b/c", "/a/b/c/d/e")]
+ public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ [Fact]
+ public virtual async Task Match_SingleParameter()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/{p}");
+ var httpContext = CreateContext("/14");
+ var values = new RouteValueDictionary(new { p = "14", });
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, values);
+ }
+
+ [Fact]
+ public virtual async Task Match_Constraint()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/{p:int}");
+ var httpContext = CreateContext("/14");
+ var values = new RouteValueDictionary(new { p = "14", });
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, values);
+ }
+
+ [Fact]
+ public virtual async Task Match_SingleParameter_TrailingSlash()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/{p}");
+ var httpContext = CreateContext("/14/");
+ var values = new RouteValueDictionary(new { p = "14", });
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, values);
+ }
+
+ [Fact]
+ public virtual async Task Match_SingleParameter_WeirdNames()
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/foo/{ }/{.!$%}/{dynamic.data}");
+ var httpContext = CreateContext("/foo/space/weirdmatch/matcherid");
+ var values = new RouteValueDictionary()
{
{ " ", "space" },
{ ".!$%", "weirdmatch" },
{ "dynamic.data", "matcherid" },
};
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, values);
- }
-
- [Theory]
- [InlineData("/")]
- [InlineData("/a/b")]
- [InlineData("/a/b/c")]
- [InlineData("//")]
- public virtual async Task NotMatch_SingleParameter(string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher("/{p}");
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
-
- [Theory]
- [InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] { "54", })]
- [InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
- [InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
- [InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
- [InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
- [InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
- [InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
- public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
- }
-
- [Theory]
- [InlineData("/{a}/b", "/54/bb")]
- [InlineData("/{a}/b", "/54/b/17")]
- [InlineData("/{a}/b", "/54/b//")]
- [InlineData("/{a}/{b}", "//73")]
- [InlineData("/{a}/{b}", "/54//")]
- [InlineData("/{a}/{b}", "/54/73/18")]
- [InlineData("/a/{b}/c", "/aa/b/c")]
- [InlineData("/a/{b}/c", "/a/b/cc")]
- [InlineData("/a/{b}/c", "/a/b/c/d")]
- [InlineData("/{a}/b/{c}", "/54/bb/c")]
- [InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
- [InlineData("/{a}/{b}/{c}", "/54/b/c//")]
- [InlineData("/{a}/{b}/{c}", "//b/c/")]
- [InlineData("/{a}/{b}/{c}", "/54//c/")]
- [InlineData("/{a}/{b}/{c}", "/54/b//")]
- public virtual async Task NotMatch_MultipleParameters(string template, string path)
- {
- // Arrange
- var (matcher, endpoint) = CreateMatcher(template);
- var httpContext = CreateContext(path);
-
- // Act
- await matcher.MatchAsync(httpContext);
-
- // Assert
- MatcherAssert.AssertNotMatch(httpContext);
- }
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, values);
+ }
+
+ [Theory]
+ [InlineData("/")]
+ [InlineData("/a/b")]
+ [InlineData("/a/b/c")]
+ [InlineData("//")]
+ public virtual async Task NotMatch_SingleParameter(string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher("/{p}");
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
+ }
+
+ [Theory]
+ [InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] { "54", })]
+ [InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
+ [InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
+ [InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
+ [InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
+ [InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
+ [InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
+ public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, endpoint, keys, values);
+ }
+
+ [Theory]
+ [InlineData("/{a}/b", "/54/bb")]
+ [InlineData("/{a}/b", "/54/b/17")]
+ [InlineData("/{a}/b", "/54/b//")]
+ [InlineData("/{a}/{b}", "//73")]
+ [InlineData("/{a}/{b}", "/54//")]
+ [InlineData("/{a}/{b}", "/54/73/18")]
+ [InlineData("/a/{b}/c", "/aa/b/c")]
+ [InlineData("/a/{b}/c", "/a/b/cc")]
+ [InlineData("/a/{b}/c", "/a/b/c/d")]
+ [InlineData("/{a}/b/{c}", "/54/bb/c")]
+ [InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
+ [InlineData("/{a}/{b}/{c}", "/54/b/c//")]
+ [InlineData("/{a}/{b}/{c}", "//b/c/")]
+ [InlineData("/{a}/{b}/{c}", "/54//c/")]
+ [InlineData("/{a}/{b}/{c}", "/54/b//")]
+ public virtual async Task NotMatch_MultipleParameters(string template, string path)
+ {
+ // Arrange
+ var (matcher, endpoint) = CreateMatcher(template);
+ var httpContext = CreateContext(path);
+
+ // Act
+ await matcher.MatchAsync(httpContext);
+
+ // Assert
+ MatcherAssert.AssertNotMatch(httpContext);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs
index 0eebfe66e6..293ef2fcb6 100644
--- a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs
@@ -8,47 +8,46 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public abstract partial class MatcherConformanceTest
{
- public abstract partial class MatcherConformanceTest
- {
- internal abstract Matcher CreateMatcher(params RouteEndpoint[] endpoints);
+ internal abstract Matcher CreateMatcher(params RouteEndpoint[] endpoints);
- internal static HttpContext CreateContext(string path)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.Request.Method = "TEST";
- httpContext.Request.Path = path;
- httpContext.RequestServices = CreateServices();
- return httpContext;
- }
+ internal static HttpContext CreateContext(string path)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Request.Method = "TEST";
+ httpContext.Request.Path = path;
+ httpContext.RequestServices = CreateServices();
+ return httpContext;
+ }
- // The older routing implementations retrieve services when they first execute.
- internal static IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddLogging();
- return services.BuildServiceProvider();
- }
+ // The older routing implementations retrieve services when they first execute.
+ internal static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddLogging();
+ return services.BuildServiceProvider();
+ }
- internal static RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- object constraints = null,
- int? order = null)
- {
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template, defaults, constraints),
- order ?? 0,
- EndpointMetadataCollection.Empty,
- "endpoint: " + template);
- }
+ internal static RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ object constraints = null,
+ int? order = null)
+ {
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template, defaults, constraints),
+ order ?? 0,
+ EndpointMetadataCollection.Empty,
+ "endpoint: " + template);
+ }
- internal (Matcher matcher, RouteEndpoint endpoint) CreateMatcher(string template)
- {
- var endpoint = CreateEndpoint(template);
- return (CreateMatcher(endpoint), endpoint);
- }
+ internal (Matcher matcher, RouteEndpoint endpoint) CreateMatcher(string template)
+ {
+ var endpoint = CreateEndpoint(template);
+ return (CreateMatcher(endpoint), endpoint);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs
index bec41c3f05..92736dd515 100644
--- a/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/MultipleEntryJumpTableTest.cs
@@ -3,78 +3,77 @@
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public abstract class MultipleEntryJumpTableTest
{
- public abstract class MultipleEntryJumpTableTest
+ internal abstract JumpTable CreateTable(
+ int defaultDestination,
+ int exitDestination,
+ params (string text, int destination)[] entries);
+
+ [Fact]
+ public void GetDestination_ZeroLengthSegment_JumpsToExit()
+ {
+ // Arrange
+ var table = CreateTable(0, 1, ("text", 2));
+
+ // Act
+ var result = table.GetDestination("ignored", new PathSegment(0, 0));
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void GetDestination_NonMatchingSegment_JumpsToDefault()
+ {
+ // Arrange
+ var table = CreateTable(0, 1, ("text", 2));
+
+ // Act
+ var result = table.GetDestination("text", new PathSegment(1, 2));
+
+ // Assert
+ Assert.Equal(0, result);
+ }
+
+ [Fact]
+ public void GetDestination_SegmentMatchingText_JumpsToDestination()
{
- internal abstract JumpTable CreateTable(
- int defaultDestination,
- int exitDestination,
- params (string text, int destination)[] entries);
-
- [Fact]
- public void GetDestination_ZeroLengthSegment_JumpsToExit()
- {
- // Arrange
- var table = CreateTable(0, 1, ("text", 2));
-
- // Act
- var result = table.GetDestination("ignored", new PathSegment(0, 0));
-
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public void GetDestination_NonMatchingSegment_JumpsToDefault()
- {
- // Arrange
- var table = CreateTable(0, 1, ("text", 2));
-
- // Act
- var result = table.GetDestination("text", new PathSegment(1, 2));
-
- // Assert
- Assert.Equal(0, result);
- }
-
- [Fact]
- public void GetDestination_SegmentMatchingText_JumpsToDestination()
- {
- // Arrange
- var table = CreateTable(0, 1, ("text", 2));
-
- // Act
- var result = table.GetDestination("some-text", new PathSegment(5, 4));
-
- // Assert
- Assert.Equal(2, result);
- }
-
- [Fact]
- public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination()
- {
- // Arrange
- var table = CreateTable(0, 1, ("text", 2));
-
- // Act
- var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
-
- // Assert
- Assert.Equal(2, result);
- }
-
- [Fact]
- public void GetDestination_SegmentMatchingTextIgnoreCase_MultipleEntries()
- {
- // Arrange
- var table = CreateTable(0, 1, ("tezt", 2), ("text", 3));
-
- // Act
- var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
-
- // Assert
- Assert.Equal(3, result);
- }
+ // Arrange
+ var table = CreateTable(0, 1, ("text", 2));
+
+ // Act
+ var result = table.GetDestination("some-text", new PathSegment(5, 4));
+
+ // Assert
+ Assert.Equal(2, result);
+ }
+
+ [Fact]
+ public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination()
+ {
+ // Arrange
+ var table = CreateTable(0, 1, ("text", 2));
+
+ // Act
+ var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
+
+ // Assert
+ Assert.Equal(2, result);
+ }
+
+ [Fact]
+ public void GetDestination_SegmentMatchingTextIgnoreCase_MultipleEntries()
+ {
+ // Arrange
+ var table = CreateTable(0, 1, ("tezt", 2), ("text", 3));
+
+ // Act
+ var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
+
+ // Assert
+ Assert.Equal(3, result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/NonVectorizedILEmitTrieJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/NonVectorizedILEmitTrieJumpTableTest.cs
index c90b67dfc4..f0b991426a 100644
--- a/src/Http/Routing/test/UnitTests/Matching/NonVectorizedILEmitTrieJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/NonVectorizedILEmitTrieJumpTableTest.cs
@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class NonVectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
{
- public class NonVectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
- {
- public override bool Vectorize => false;
- }
+ public override bool Vectorize => false;
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs b/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs
index 474551f4a8..67fb3451d9 100644
--- a/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs
@@ -5,33 +5,32 @@ using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// This is an adapter to use Route in the conformance tests
+internal class RouteMatcher : Matcher
{
- // This is an adapter to use Route in the conformance tests
- internal class RouteMatcher : Matcher
+ private readonly RouteCollection _inner;
+
+ internal RouteMatcher(RouteCollection inner)
{
- private readonly RouteCollection _inner;
+ _inner = inner;
+ }
- internal RouteMatcher(RouteCollection inner)
+ public override async Task MatchAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
{
- _inner = inner;
+ throw new ArgumentNullException(nameof(httpContext));
}
- public override async Task MatchAsync(HttpContext httpContext)
+ var routeContext = new RouteContext(httpContext);
+ await _inner.RouteAsync(routeContext);
+
+ if (routeContext.Handler != null)
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var routeContext = new RouteContext(httpContext);
- await _inner.RouteAsync(routeContext);
-
- if (routeContext.Handler != null)
- {
- httpContext.Request.RouteValues = routeContext.RouteData.Values;
- await routeContext.Handler(httpContext);
- }
+ httpContext.Request.RouteValues = routeContext.RouteData.Values;
+ await routeContext.Handler(httpContext);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs
index ac23e13bf4..93cd89a3cf 100644
--- a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs
@@ -11,99 +11,98 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class RouteMatcherBuilder : MatcherBuilder
{
- internal class RouteMatcherBuilder : MatcherBuilder
- {
- private readonly IInlineConstraintResolver _constraintResolver;
- private readonly List<RouteEndpoint> _endpoints;
+ private readonly IInlineConstraintResolver _constraintResolver;
+ private readonly List<RouteEndpoint> _endpoints;
- public RouteMatcherBuilder()
- {
- _constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider());
- _endpoints = new List<RouteEndpoint>();
- }
+ public RouteMatcherBuilder()
+ {
+ _constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider());
+ _endpoints = new List<RouteEndpoint>();
+ }
- public override void AddEndpoint(RouteEndpoint endpoint)
- {
- _endpoints.Add(endpoint);
- }
+ public override void AddEndpoint(RouteEndpoint endpoint)
+ {
+ _endpoints.Add(endpoint);
+ }
- public override Matcher Build()
- {
- var selector = new DefaultEndpointSelector();
+ public override Matcher Build()
+ {
+ var selector = new DefaultEndpointSelector();
- var groups = _endpoints
- .GroupBy(e => (e.Order, e.RoutePattern.InboundPrecedence, e.RoutePattern.RawText))
- .OrderBy(g => g.Key.Order)
- .ThenBy(g => g.Key.InboundPrecedence);
+ var groups = _endpoints
+ .GroupBy(e => (e.Order, e.RoutePattern.InboundPrecedence, e.RoutePattern.RawText))
+ .OrderBy(g => g.Key.Order)
+ .ThenBy(g => g.Key.InboundPrecedence);
- var routes = new RouteCollection();
+ var routes = new RouteCollection();
- foreach (var group in groups)
+ foreach (var group in groups)
+ {
+ var candidates = group.ToArray();
+ var endpoint = group.First();
+
+ // RoutePattern.Defaults contains the default values parsed from the template
+ // as well as those specified with a literal. We need to separate those
+ // for legacy cases.
+ //
+ // To do this we re-parse the original text and compare.
+ var withoutDefaults = RoutePatternFactory.Parse(endpoint.RoutePattern.RawText);
+ var defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
+ for (var i = 0; i < withoutDefaults.Parameters.Count; i++)
{
- var candidates = group.ToArray();
- var endpoint = group.First();
-
- // RoutePattern.Defaults contains the default values parsed from the template
- // as well as those specified with a literal. We need to separate those
- // for legacy cases.
- //
- // To do this we re-parse the original text and compare.
- var withoutDefaults = RoutePatternFactory.Parse(endpoint.RoutePattern.RawText);
- var defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
- for (var i = 0; i < withoutDefaults.Parameters.Count; i++)
+ var parameter = withoutDefaults.Parameters[i];
+ if (parameter.Default != null)
{
- var parameter = withoutDefaults.Parameters[i];
- if (parameter.Default != null)
- {
- defaults.Remove(parameter.Name);
- }
+ defaults.Remove(parameter.Name);
}
-
- routes.Add(new Route(
- new SelectorRouter(selector, candidates),
- endpoint.RoutePattern.RawText,
- defaults,
- new Dictionary<string, object>(),
- new RouteValueDictionary(),
- _constraintResolver));
}
- return new RouteMatcher(routes);
+ routes.Add(new Route(
+ new SelectorRouter(selector, candidates),
+ endpoint.RoutePattern.RawText,
+ defaults,
+ new Dictionary<string, object>(),
+ new RouteValueDictionary(),
+ _constraintResolver));
}
- private class SelectorRouter : IRouter
+ return new RouteMatcher(routes);
+ }
+
+ private class SelectorRouter : IRouter
+ {
+ private readonly EndpointSelector _selector;
+ private readonly RouteEndpoint[] _candidates;
+ private readonly RouteValueDictionary[] _values;
+ private readonly int[] _scores;
+
+ public SelectorRouter(EndpointSelector selector, RouteEndpoint[] candidates)
{
- private readonly EndpointSelector _selector;
- private readonly RouteEndpoint[] _candidates;
- private readonly RouteValueDictionary[] _values;
- private readonly int[] _scores;
+ _selector = selector;
+ _candidates = candidates;
- public SelectorRouter(EndpointSelector selector, RouteEndpoint[] candidates)
- {
- _selector = selector;
- _candidates = candidates;
+ _values = new RouteValueDictionary[_candidates.Length];
+ _scores = new int[_candidates.Length];
+ }
- _values = new RouteValueDictionary[_candidates.Length];
- _scores = new int[_candidates.Length];
- }
+ public VirtualPathData GetVirtualPath(VirtualPathContext context)
+ {
+ throw new NotImplementedException();
+ }
- public VirtualPathData GetVirtualPath(VirtualPathContext context)
- {
- throw new NotImplementedException();
- }
+ public async Task RouteAsync(RouteContext routeContext)
+ {
+ // This is needed due to a quirk of our tests - they reuse the endpoint feature.
+ routeContext.HttpContext.SetEndpoint(null);
- public async Task RouteAsync(RouteContext routeContext)
+ await _selector.SelectAsync(routeContext.HttpContext, new CandidateSet(_candidates, _values, _scores));
+ if (routeContext.HttpContext.GetEndpoint() != null)
{
- // This is needed due to a quirk of our tests - they reuse the endpoint feature.
- routeContext.HttpContext.SetEndpoint(null);
-
- await _selector.SelectAsync(routeContext.HttpContext, new CandidateSet(_candidates, _values, _scores));
- if (routeContext.HttpContext.GetEndpoint() != null)
- {
- routeContext.Handler = (_) => Task.CompletedTask;
- }
+ routeContext.Handler = (_) => Task.CompletedTask;
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherConformanceTest.cs
index 4b7b3a7ac2..010080be46 100644
--- a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherConformanceTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherConformanceTest.cs
@@ -4,21 +4,21 @@
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class RouteMatcherConformanceTest : FullFeaturedMatcherConformanceTest
{
- public class RouteMatcherConformanceTest : FullFeaturedMatcherConformanceTest
+ // https://github.com/dotnet/aspnetcore/issues/18677
+ //
+ [Theory]
+ [InlineData("/middleware", 1)]
+ [InlineData("/middleware/test", 1)]
+ [InlineData("/middleware/test1/test2", 1)]
+ [InlineData("/bill/boga", 0)]
+ public async Task Match_Regression_1867(string path, int endpointIndex)
{
- // https://github.com/dotnet/aspnetcore/issues/18677
- //
- [Theory]
- [InlineData("/middleware", 1)]
- [InlineData("/middleware/test", 1)]
- [InlineData("/middleware/test1/test2", 1)]
- [InlineData("/bill/boga", 0)]
- public async Task Match_Regression_1867(string path, int endpointIndex)
+ var endpoints = new RouteEndpoint[]
{
- var endpoints = new RouteEndpoint[]
- {
EndpointFactory.CreateRouteEndpoint(
"{firstName}/{lastName}",
order: 0,
@@ -27,28 +27,27 @@ namespace Microsoft.AspNetCore.Routing.Matching
EndpointFactory.CreateRouteEndpoint(
"middleware/{**_}",
order: 0),
- };
+ };
- var expected = endpoints[endpointIndex];
+ var expected = endpoints[endpointIndex];
- var matcher = CreateMatcher(endpoints);
- var httpContext = CreateContext(path);
+ var matcher = CreateMatcher(endpoints);
+ var httpContext = CreateContext(path);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
- internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ {
+ var builder = new RouteMatcherBuilder();
+ for (var i = 0; i < endpoints.Length; i++)
{
- var builder = new RouteMatcherBuilder();
- for (var i = 0; i < endpoints.Length; i++)
- {
- builder.AddEndpoint(endpoints[i]);
- }
- return builder.Build();
+ builder.AddEndpoint(endpoints[i]);
}
+ return builder.Build();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/SingleEntryAsciiJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/SingleEntryAsciiJumpTableTest.cs
index b8ad193fa6..2bfea66a51 100644
--- a/src/Http/Routing/test/UnitTests/Matching/SingleEntryAsciiJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/SingleEntryAsciiJumpTableTest.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class SingleEntryAsciiJumpTableTest : SingleEntryJumpTableTestBase
{
- public class SingleEntryAsciiJumpTableTest : SingleEntryJumpTableTestBase
+ private protected override JumpTable CreateJumpTable(int defaultDestination, int exitDestination, string text, int destination)
{
- private protected override JumpTable CreateJumpTable(int defaultDestination, int exitDestination, string text, int destination)
- {
- return new SingleEntryAsciiJumpTable(defaultDestination, exitDestination, text, destination);
- }
+ return new SingleEntryAsciiJumpTable(defaultDestination, exitDestination, text, destination);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTest.cs
index 78b50d120a..08da6fba03 100644
--- a/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTest.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class SingleEntryJumpTableTest : SingleEntryJumpTableTestBase
{
- public class SingleEntryJumpTableTest : SingleEntryJumpTableTestBase
+ private protected override JumpTable CreateJumpTable(int defaultDestination, int exitDestination, string text, int destination)
{
- private protected override JumpTable CreateJumpTable(int defaultDestination, int exitDestination, string text, int destination)
- {
- return new SingleEntryJumpTable(defaultDestination, exitDestination, text, destination);
- }
+ return new SingleEntryJumpTable(defaultDestination, exitDestination, text, destination);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs b/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs
index 4310c0a6df..ed9d040d6d 100644
--- a/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/SingleEntryJumpTableTestBase.cs
@@ -3,66 +3,65 @@
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public abstract class SingleEntryJumpTableTestBase
{
- public abstract class SingleEntryJumpTableTestBase
- {
- private protected abstract JumpTable CreateJumpTable(
- int defaultDestination,
- int exitDestination,
- string text,
- int destination);
+ private protected abstract JumpTable CreateJumpTable(
+ int defaultDestination,
+ int exitDestination,
+ string text,
+ int destination);
- [Fact]
- public void GetDestination_ZeroLengthSegment_JumpsToExit()
- {
- // Arrange
- var table = CreateJumpTable(0, 1, "text", 2);
+ [Fact]
+ public void GetDestination_ZeroLengthSegment_JumpsToExit()
+ {
+ // Arrange
+ var table = CreateJumpTable(0, 1, "text", 2);
- // Act
- var result = table.GetDestination("ignored", new PathSegment(0, 0));
+ // Act
+ var result = table.GetDestination("ignored", new PathSegment(0, 0));
- // Assert
- Assert.Equal(1, result);
- }
+ // Assert
+ Assert.Equal(1, result);
+ }
- [Fact]
- public void GetDestination_NonMatchingSegment_JumpsToDefault()
- {
- // Arrange
- var table = CreateJumpTable(0, 1, "text", 2);
+ [Fact]
+ public void GetDestination_NonMatchingSegment_JumpsToDefault()
+ {
+ // Arrange
+ var table = CreateJumpTable(0, 1, "text", 2);
- // Act
- var result = table.GetDestination("text", new PathSegment(1, 2));
+ // Act
+ var result = table.GetDestination("text", new PathSegment(1, 2));
- // Assert
- Assert.Equal(0, result);
- }
+ // Assert
+ Assert.Equal(0, result);
+ }
- [Fact]
- public void GetDestination_SegmentMatchingText_JumpsToDestination()
- {
- // Arrange
- var table = CreateJumpTable(0, 1, "text", 2);
+ [Fact]
+ public void GetDestination_SegmentMatchingText_JumpsToDestination()
+ {
+ // Arrange
+ var table = CreateJumpTable(0, 1, "text", 2);
- // Act
- var result = table.GetDestination("some-text", new PathSegment(5, 4));
+ // Act
+ var result = table.GetDestination("some-text", new PathSegment(5, 4));
- // Assert
- Assert.Equal(2, result);
- }
+ // Assert
+ Assert.Equal(2, result);
+ }
- [Fact]
- public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination()
- {
- // Arrange
- var table = CreateJumpTable(0, 1, "text", 2);
+ [Fact]
+ public void GetDestination_SegmentMatchingTextIgnoreCase_JumpsToDestination()
+ {
+ // Arrange
+ var table = CreateJumpTable(0, 1, "text", 2);
- // Act
- var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
+ // Act
+ var result = table.GetDestination("some-tExt", new PathSegment(5, 4));
- // Assert
- Assert.Equal(2, result);
- }
+ // Assert
+ Assert.Equal(2, result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs
index eca1b96494..990b071afc 100644
--- a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs
@@ -7,33 +7,32 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Tree;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+// This is an adapter to use TreeRouter in the conformance tests
+internal class TreeRouterMatcher : Matcher
{
- // This is an adapter to use TreeRouter in the conformance tests
- internal class TreeRouterMatcher : Matcher
+ private readonly TreeRouter _inner;
+
+ internal TreeRouterMatcher(TreeRouter inner)
{
- private readonly TreeRouter _inner;
+ _inner = inner;
+ }
- internal TreeRouterMatcher(TreeRouter inner)
+ public override async Task MatchAsync(HttpContext httpContext)
+ {
+ if (httpContext == null)
{
- _inner = inner;
+ throw new ArgumentNullException(nameof(httpContext));
}
- public override async Task MatchAsync(HttpContext httpContext)
+ var routeContext = new RouteContext(httpContext);
+ await _inner.RouteAsync(routeContext);
+
+ if (routeContext.Handler != null)
{
- if (httpContext == null)
- {
- throw new ArgumentNullException(nameof(httpContext));
- }
-
- var routeContext = new RouteContext(httpContext);
- await _inner.RouteAsync(routeContext);
-
- if (routeContext.Handler != null)
- {
- httpContext.Request.RouteValues = routeContext.RouteData.Values;
- await routeContext.Handler(httpContext);
- }
+ httpContext.Request.RouteValues = routeContext.RouteData.Values;
+ await routeContext.Handler(httpContext);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs
index 00c239a634..c9eece1d44 100644
--- a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs
@@ -14,97 +14,96 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+internal class TreeRouterMatcherBuilder : MatcherBuilder
{
- internal class TreeRouterMatcherBuilder : MatcherBuilder
- {
- private readonly List<RouteEndpoint> _endpoints;
+ private readonly List<RouteEndpoint> _endpoints;
- public TreeRouterMatcherBuilder()
- {
- _endpoints = new List<RouteEndpoint>();
- }
+ public TreeRouterMatcherBuilder()
+ {
+ _endpoints = new List<RouteEndpoint>();
+ }
- public override void AddEndpoint(RouteEndpoint endpoint)
- {
- _endpoints.Add(endpoint);
- }
+ public override void AddEndpoint(RouteEndpoint endpoint)
+ {
+ _endpoints.Add(endpoint);
+ }
- public override Matcher Build()
- {
- var builder = new TreeRouteBuilder(
- NullLoggerFactory.Instance,
- new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
- new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider()));
+ public override Matcher Build()
+ {
+ var builder = new TreeRouteBuilder(
+ NullLoggerFactory.Instance,
+ new DefaultObjectPool<UriBuildingContext>(new UriBuilderContextPooledObjectPolicy()),
+ new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider()));
- var selector = new DefaultEndpointSelector();
+ var selector = new DefaultEndpointSelector();
- var groups = _endpoints
- .GroupBy(e => (e.Order, e.RoutePattern.InboundPrecedence, e.RoutePattern.RawText))
- .OrderBy(g => g.Key.Order)
- .ThenBy(g => g.Key.InboundPrecedence);
+ var groups = _endpoints
+ .GroupBy(e => (e.Order, e.RoutePattern.InboundPrecedence, e.RoutePattern.RawText))
+ .OrderBy(g => g.Key.Order)
+ .ThenBy(g => g.Key.InboundPrecedence);
- var routes = new RouteCollection();
+ var routes = new RouteCollection();
- foreach (var group in groups)
+ foreach (var group in groups)
+ {
+ var candidates = group.ToArray();
+
+ // RouteEndpoint.Values contains the default values parsed from the template
+ // as well as those specified with a literal. We need to separate those
+ // for legacy cases.
+ var endpoint = group.First();
+ var defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
+ for (var i = 0; i < endpoint.RoutePattern.Parameters.Count; i++)
{
- var candidates = group.ToArray();
-
- // RouteEndpoint.Values contains the default values parsed from the template
- // as well as those specified with a literal. We need to separate those
- // for legacy cases.
- var endpoint = group.First();
- var defaults = new RouteValueDictionary(endpoint.RoutePattern.Defaults);
- for (var i = 0; i < endpoint.RoutePattern.Parameters.Count; i++)
+ var parameter = endpoint.RoutePattern.Parameters[i];
+ if (parameter.Default != null)
{
- var parameter = endpoint.RoutePattern.Parameters[i];
- if (parameter.Default != null)
- {
- defaults.Remove(parameter.Name);
- }
+ defaults.Remove(parameter.Name);
}
-
- builder.MapInbound(
- new SelectorRouter(selector, candidates),
- new RouteTemplate(endpoint.RoutePattern),
- routeName: null,
- order: endpoint.Order);
}
- return new TreeRouterMatcher(builder.Build());
+ builder.MapInbound(
+ new SelectorRouter(selector, candidates),
+ new RouteTemplate(endpoint.RoutePattern),
+ routeName: null,
+ order: endpoint.Order);
}
- private class SelectorRouter : IRouter
+ return new TreeRouterMatcher(builder.Build());
+ }
+
+ private class SelectorRouter : IRouter
+ {
+ private readonly EndpointSelector _selector;
+ private readonly RouteEndpoint[] _candidates;
+ private readonly RouteValueDictionary[] _values;
+ private readonly int[] _scores;
+
+ public SelectorRouter(EndpointSelector selector, RouteEndpoint[] candidates)
{
- private readonly EndpointSelector _selector;
- private readonly RouteEndpoint[] _candidates;
- private readonly RouteValueDictionary[] _values;
- private readonly int[] _scores;
+ _selector = selector;
+ _candidates = candidates;
- public SelectorRouter(EndpointSelector selector, RouteEndpoint[] candidates)
- {
- _selector = selector;
- _candidates = candidates;
+ _values = new RouteValueDictionary[_candidates.Length];
+ _scores = new int[_candidates.Length];
+ }
- _values = new RouteValueDictionary[_candidates.Length];
- _scores = new int[_candidates.Length];
- }
+ public VirtualPathData GetVirtualPath(VirtualPathContext context)
+ {
+ throw new NotImplementedException();
+ }
- public VirtualPathData GetVirtualPath(VirtualPathContext context)
- {
- throw new NotImplementedException();
- }
+ public async Task RouteAsync(RouteContext routeContext)
+ {
+ // This is needed due to a quirk of our tests - they reuse the endpoint feature.
+ routeContext.HttpContext.SetEndpoint(null);
- public async Task RouteAsync(RouteContext routeContext)
+ await _selector.SelectAsync(routeContext.HttpContext, new CandidateSet(_candidates, _values, _scores));
+ if (routeContext.HttpContext.GetEndpoint() != null)
{
- // This is needed due to a quirk of our tests - they reuse the endpoint feature.
- routeContext.HttpContext.SetEndpoint(null);
-
- await _selector.SelectAsync(routeContext.HttpContext, new CandidateSet(_candidates, _values, _scores));
- if (routeContext.HttpContext.GetEndpoint() != null)
- {
- routeContext.Handler = (_) => Task.CompletedTask;
- }
+ routeContext.Handler = (_) => Task.CompletedTask;
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherConformanceTest.cs
index 1ae231a324..70a2837bf9 100644
--- a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherConformanceTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherConformanceTest.cs
@@ -4,35 +4,35 @@
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class TreeRouterMatcherConformanceTest : FullFeaturedMatcherConformanceTest
{
- public class TreeRouterMatcherConformanceTest : FullFeaturedMatcherConformanceTest
+ // TreeRouter doesn't support non-inline default values.
+ [Fact]
+ public override Task Match_NonInlineDefaultValues()
{
- // TreeRouter doesn't support non-inline default values.
- [Fact]
- public override Task Match_NonInlineDefaultValues()
- {
- return Task.CompletedTask;
- }
+ return Task.CompletedTask;
+ }
- // TreeRouter doesn't support non-inline default values.
- [Fact]
- public override Task Match_ExtraDefaultValues()
- {
- return Task.CompletedTask;
- }
+ // TreeRouter doesn't support non-inline default values.
+ [Fact]
+ public override Task Match_ExtraDefaultValues()
+ {
+ return Task.CompletedTask;
+ }
- // https://github.com/dotnet/aspnetcore/issues/18677
- //
- [Theory]
- [InlineData("/middleware", 1)]
- [InlineData("/middleware/test", 1)]
- [InlineData("/middleware/test1/test2", 1)]
- [InlineData("/bill/boga", 0)]
- public async Task Match_Regression_1867(string path, int endpointIndex)
+ // https://github.com/dotnet/aspnetcore/issues/18677
+ //
+ [Theory]
+ [InlineData("/middleware", 1)]
+ [InlineData("/middleware/test", 1)]
+ [InlineData("/middleware/test1/test2", 1)]
+ [InlineData("/bill/boga", 0)]
+ public async Task Match_Regression_1867(string path, int endpointIndex)
+ {
+ var endpoints = new RouteEndpoint[]
{
- var endpoints = new RouteEndpoint[]
- {
EndpointFactory.CreateRouteEndpoint(
"{firstName}/{lastName}",
order: 0,
@@ -41,28 +41,27 @@ namespace Microsoft.AspNetCore.Routing.Matching
EndpointFactory.CreateRouteEndpoint(
"middleware/{**_}",
order: 0),
- };
+ };
- var expected = endpoints[endpointIndex];
+ var expected = endpoints[endpointIndex];
- var matcher = CreateMatcher(endpoints);
- var httpContext = CreateContext(path);
+ var matcher = CreateMatcher(endpoints);
+ var httpContext = CreateContext(path);
- // Act
- await matcher.MatchAsync(httpContext);
+ // Act
+ await matcher.MatchAsync(httpContext);
- // Assert
- MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
- }
+ // Assert
+ MatcherAssert.AssertMatch(httpContext, expected, ignoreValues: true);
+ }
- internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ internal override Matcher CreateMatcher(params RouteEndpoint[] endpoints)
+ {
+ var builder = new TreeRouterMatcherBuilder();
+ for (var i = 0; i < endpoints.Length; i++)
{
- var builder = new TreeRouterMatcherBuilder();
- for (var i = 0; i < endpoints.Length; i++)
- {
- builder.AddEndpoint(endpoints[i]);
- }
- return builder.Build();
+ builder.AddEndpoint(endpoints[i]);
}
+ return builder.Build();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/VectorizedILEmitTrieJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/VectorizedILEmitTrieJumpTableTest.cs
index d0321db276..a9920124ba 100644
--- a/src/Http/Routing/test/UnitTests/Matching/VectorizedILEmitTrieJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/VectorizedILEmitTrieJumpTableTest.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class VectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
{
- public class VectorizedILEmitTrieJumpTableTest : ILEmitTreeJumpTableTestBase
- {
- // We can still run the vectorized implementation on 32 bit, we just
- // don't expect it to be performant - it will still be correct.
- public override bool Vectorize => true;
- }
+ // We can still run the vectorized implementation on 32 bit, we just
+ // don't expect it to be performant - it will still be correct.
+ public override bool Vectorize => true;
}
diff --git a/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs b/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs
index 1db558483e..0fedd5218f 100644
--- a/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs
+++ b/src/Http/Routing/test/UnitTests/Matching/ZeroEntryJumpTableTest.cs
@@ -3,34 +3,33 @@
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Matching
+namespace Microsoft.AspNetCore.Routing.Matching;
+
+public class ZeroEntryJumpTableTest
{
- public class ZeroEntryJumpTableTest
+ [Fact]
+ public void GetDestination_ZeroLengthSegment_JumpsToExit()
+ {
+ // Arrange
+ var table = new ZeroEntryJumpTable(0, 1);
+
+ // Act
+ var result = table.GetDestination("ignored", new PathSegment(0, 0));
+
+ // Assert
+ Assert.Equal(1, result);
+ }
+
+ [Fact]
+ public void GetDestination_SegmentWithLength_JumpsToDefault()
{
- [Fact]
- public void GetDestination_ZeroLengthSegment_JumpsToExit()
- {
- // Arrange
- var table = new ZeroEntryJumpTable(0, 1);
-
- // Act
- var result = table.GetDestination("ignored", new PathSegment(0, 0));
-
- // Assert
- Assert.Equal(1, result);
- }
-
- [Fact]
- public void GetDestination_SegmentWithLength_JumpsToDefault()
- {
- // Arrange
- var table = new ZeroEntryJumpTable(0, 1);
-
- // Act
- var result = table.GetDestination("ignored", new PathSegment(0, 1));
-
- // Assert
- Assert.Equal(0, result);
- }
+ // Arrange
+ var table = new ZeroEntryJumpTable(0, 1);
+
+ // Act
+ var result = table.GetDestination("ignored", new PathSegment(0, 1));
+
+ // Assert
+ Assert.Equal(0, result);
}
}
diff --git a/src/Http/Routing/test/UnitTests/PathTokenizerTest.cs b/src/Http/Routing/test/UnitTests/PathTokenizerTest.cs
index 8d29d7142f..2feb5100c0 100644
--- a/src/Http/Routing/test/UnitTests/PathTokenizerTest.cs
+++ b/src/Http/Routing/test/UnitTests/PathTokenizerTest.cs
@@ -5,15 +5,15 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class PathTokenizerTest
{
- public class PathTokenizerTest
+ public static TheoryData<string, StringSegment[]> TokenizationData
{
- public static TheoryData<string, StringSegment[]> TokenizationData
+ get
{
- get
- {
- return new TheoryData<string, StringSegment[]>
+ return new TheoryData<string, StringSegment[]>
{
{ string.Empty, new StringSegment[] { } },
{ "/", new StringSegment[] { } },
@@ -72,46 +72,45 @@ namespace Microsoft.AspNetCore.Routing
}
},
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(TokenizationData))]
- public void PathTokenizer_Count(string path, StringSegment[] expectedSegments)
- {
- // Arrange
- var tokenizer = new PathTokenizer(new PathString(path));
+ [Theory]
+ [MemberData(nameof(TokenizationData))]
+ public void PathTokenizer_Count(string path, StringSegment[] expectedSegments)
+ {
+ // Arrange
+ var tokenizer = new PathTokenizer(new PathString(path));
- // Act
- var count = tokenizer.Count;
+ // Act
+ var count = tokenizer.Count;
- // Assert
- Assert.Equal(expectedSegments.Length, count);
- }
+ // Assert
+ Assert.Equal(expectedSegments.Length, count);
+ }
- [Theory]
- [MemberData(nameof(TokenizationData))]
- public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments)
- {
- // Arrange
- var tokenizer = new PathTokenizer(new PathString(path));
+ [Theory]
+ [MemberData(nameof(TokenizationData))]
+ public void PathTokenizer_Indexer(string path, StringSegment[] expectedSegments)
+ {
+ // Arrange
+ var tokenizer = new PathTokenizer(new PathString(path));
- // Act & Assert
- for (var i = 0; i < expectedSegments.Length; i++)
- {
- Assert.Equal(expectedSegments[i], tokenizer[i]);
- }
+ // Act & Assert
+ for (var i = 0; i < expectedSegments.Length; i++)
+ {
+ Assert.Equal(expectedSegments[i], tokenizer[i]);
}
+ }
- [Theory]
- [MemberData(nameof(TokenizationData))]
- public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments)
- {
- // Arrange
- var tokenizer = new PathTokenizer(new PathString(path));
+ [Theory]
+ [MemberData(nameof(TokenizationData))]
+ public void PathTokenizer_Enumerator(string path, StringSegment[] expectedSegments)
+ {
+ // Arrange
+ var tokenizer = new PathTokenizer(new PathString(path));
- // Act & Assert
- Assert.Equal<StringSegment>(expectedSegments, tokenizer);
- }
+ // Act & Assert
+ Assert.Equal<StringSegment>(expectedSegments, tokenizer);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Patterns/DefaultRoutePatternTransformerTest.cs b/src/Http/Routing/test/UnitTests/Patterns/DefaultRoutePatternTransformerTest.cs
index 9b17791d41..1f53fb71b3 100644
--- a/src/Http/Routing/test/UnitTests/Patterns/DefaultRoutePatternTransformerTest.cs
+++ b/src/Http/Routing/test/UnitTests/Patterns/DefaultRoutePatternTransformerTest.cs
@@ -1,403 +1,402 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.AspNetCore.Routing.Constraints;
-using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.AspNetCore.Routing.Constraints;
+using Microsoft.Extensions.DependencyInjection;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+public class DefaultRoutePatternTransformerTest
{
- public class DefaultRoutePatternTransformerTest
+ public DefaultRoutePatternTransformerTest()
+ {
+ var services = new ServiceCollection();
+ services.AddRouting();
+ services.AddOptions();
+ Transformer = services.BuildServiceProvider().GetRequiredService<RoutePatternTransformer>();
+ }
+
+ public RoutePatternTransformer Transformer { get; }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptNullForAnyKey()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { a = (string)null, b = "", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("a", null), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("b", string.Empty), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_RejectsNullForParameter()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = string.Empty, };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_AllowRequiredValueAnyForParameter()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = RoutePattern.RequiredValueAny, };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.Defaults.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp)); // default is preserved
+
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", RoutePattern.RequiredValueAny), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_RejectsNullForOutOfLineDefault()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { area = "Admin" };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { area = string.Empty, };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_RejectsRequiredValueAnyForOutOfLineDefault()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { area = RoutePattern.RequiredValueAny };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { area = string.Empty, };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForParameter()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForParameter_WithSameDefault()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+
+ // We should not need to rewrite anything in this case.
+ Assert.Same(actual.Defaults, original.Defaults);
+ Assert.Same(actual.Parameters, original.Parameters);
+ Assert.Same(actual.PathSegments, original.PathSegments);
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForParameter_WithDifferentDefault()
+ {
+ // Arrange
+ var template = "{controller=Blog}/{action=ReadPost}/{id?}";
+ var defaults = new { area = "Admin", };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { area = "Admin", controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+
+ // We should not need to rewrite anything in this case.
+ Assert.NotSame(actual.Defaults, original.Defaults);
+ Assert.NotSame(actual.Parameters, original.Parameters);
+ Assert.NotSame(actual.PathSegments, original.PathSegments);
+
+ // other defaults were wiped out
+ Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), Assert.Single(actual.Defaults));
+ Assert.Null(actual.GetParameter("controller").Default);
+ Assert.False(actual.Defaults.ContainsKey("controller"));
+ Assert.Null(actual.GetParameter("action").Default);
+ Assert.False(actual.Defaults.ContainsKey("action"));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForParameter_WithMatchingConstraint()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id?}";
+ var defaults = new { };
+ var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanRejectValueForParameter_WithNonMatchingConstraint()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id?}";
+ var defaults = new { };
+ var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Blog", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue()
{
- public DefaultRoutePatternTransformerTest()
- {
- var services = new ServiceCollection();
- services.AddRouting();
- services.AddOptions();
- Transformer = services.BuildServiceProvider().GetRequiredService<RoutePatternTransformer>();
- }
-
- public RoutePatternTransformer Transformer { get; }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptNullForAnyKey()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { a = (string)null, b = "", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("a", null), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("b", string.Empty), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_RejectsNullForParameter()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = string.Empty, };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Null(actual);
- }
-
- [Fact]
- public void SubstituteRequiredValues_AllowRequiredValueAnyForParameter()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = RoutePattern.RequiredValueAny, };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.Defaults.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp)); // default is preserved
-
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", RoutePattern.RequiredValueAny), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_RejectsNullForOutOfLineDefault()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { area = "Admin" };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { area = string.Empty, };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Null(actual);
- }
-
- [Fact]
- public void SubstituteRequiredValues_RejectsRequiredValueAnyForOutOfLineDefault()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { area = RoutePattern.RequiredValueAny };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { area = string.Empty, };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Null(actual);
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForParameter()
- {
- // Arrange
- var template = "{controller}/{action}/{id?}";
- var defaults = new { };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForParameter_WithSameDefault()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
-
- // We should not need to rewrite anything in this case.
- Assert.Same(actual.Defaults, original.Defaults);
- Assert.Same(actual.Parameters, original.Parameters);
- Assert.Same(actual.PathSegments, original.PathSegments);
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForParameter_WithDifferentDefault()
- {
- // Arrange
- var template = "{controller=Blog}/{action=ReadPost}/{id?}";
- var defaults = new { area = "Admin", };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { area = "Admin", controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
-
- // We should not need to rewrite anything in this case.
- Assert.NotSame(actual.Defaults, original.Defaults);
- Assert.NotSame(actual.Parameters, original.Parameters);
- Assert.NotSame(actual.PathSegments, original.PathSegments);
-
- // other defaults were wiped out
- Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), Assert.Single(actual.Defaults));
- Assert.Null(actual.GetParameter("controller").Default);
- Assert.False(actual.Defaults.ContainsKey("controller"));
- Assert.Null(actual.GetParameter("action").Default);
- Assert.False(actual.Defaults.ContainsKey("action"));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForParameter_WithMatchingConstraint()
- {
- // Arrange
- var template = "{controller}/{action}/{id?}";
- var defaults = new { };
- var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanRejectValueForParameter_WithNonMatchingConstraint()
- {
- // Arrange
- var template = "{controller}/{action}/{id?}";
- var defaults = new { };
- var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Blog", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Null(actual);
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue()
- {
- // Arrange
- var template = "Home/Index/{id?}";
- var defaults = new { controller = "Home", action = "Index", };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanRejectValueForDefault_WithDifferentValue()
- {
- // Arrange
- var template = "Home/Index/{id?}";
- var defaults = new { controller = "Home", action = "Index", };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Blog", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Null(actual);
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_Null()
- {
- // Arrange
- var template = "Home/Index/{id?}";
- var defaults = new { controller = (string)null, action = "", };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = string.Empty, action = (string)null, };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", null), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", ""), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_WithMatchingConstraint()
- {
- // Arrange
- var template = "Home/Index/{id?}";
- var defaults = new { controller = "Home", action = "Index", };
- var policies = new { controller = "Home", };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanRejectValueForDefault_WithSameValue_WithNonMatchingConstraint()
- {
- // Arrange
- var template = "Home/Index/{id?}";
- var defaults = new { controller = "Home", action = "Index", };
- var policies = new { controller = "Home", };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
- }
-
- [Fact]
- public void SubstituteRequiredValues_CanMergeExistingRequiredValues()
- {
- // Arrange
- var template = "Home/Index/{id?}";
- var defaults = new { area = "Admin", controller = "Home", action = "Index", };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies, new { area = "Admin", controller = "Home", });
-
- var requiredValues = new { controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Collection(
- actual.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
- kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
- }
+ // Arrange
+ var template = "Home/Index/{id?}";
+ var defaults = new { controller = "Home", action = "Index", };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanRejectValueForDefault_WithDifferentValue()
+ {
+ // Arrange
+ var template = "Home/Index/{id?}";
+ var defaults = new { controller = "Home", action = "Index", };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Blog", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_Null()
+ {
+ // Arrange
+ var template = "Home/Index/{id?}";
+ var defaults = new { controller = (string)null, action = "", };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = string.Empty, action = (string)null, };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", null), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", ""), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_WithMatchingConstraint()
+ {
+ // Arrange
+ var template = "Home/Index/{id?}";
+ var defaults = new { controller = "Home", action = "Index", };
+ var policies = new { controller = "Home", };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanRejectValueForDefault_WithSameValue_WithNonMatchingConstraint()
+ {
+ // Arrange
+ var template = "Home/Index/{id?}";
+ var defaults = new { controller = "Home", action = "Index", };
+ var policies = new { controller = "Home", };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_CanMergeExistingRequiredValues()
+ {
+ // Arrange
+ var template = "Home/Index/{id?}";
+ var defaults = new { area = "Admin", controller = "Home", action = "Index", };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies, new { area = "Admin", controller = "Home", });
+
+ var requiredValues = new { controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ actual.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
+ kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
+ }
+
+ [Fact]
+ public void SubstituteRequiredValues_NullRequiredValueParameter_Fail()
+ {
+ // Arrange
+ var template = "PageRoute/Attribute/{page}";
+ var defaults = new { area = (string)null, page = (string)null, controller = "Home", action = "Index", };
+ var policies = new { };
+
+ var original = RoutePatternFactory.Parse(template, defaults, policies);
+
+ var requiredValues = new { area = (string)null, page = (string)null, controller = "Home", action = "Index", };
+
+ // Act
+ var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
- [Fact]
- public void SubstituteRequiredValues_NullRequiredValueParameter_Fail()
- {
- // Arrange
- var template = "PageRoute/Attribute/{page}";
- var defaults = new { area = (string)null, page = (string)null, controller = "Home", action = "Index", };
- var policies = new { };
-
- var original = RoutePatternFactory.Parse(template, defaults, policies);
-
- var requiredValues = new { area = (string)null, page = (string)null, controller = "Home", action = "Index", };
-
- // Act
- var actual = Transformer.SubstituteRequiredValues(original, requiredValues);
-
- // Assert
- Assert.Null(actual);
- }
+ // Assert
+ Assert.Null(actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Patterns/InlineRouteParameterParserTest.cs b/src/Http/Routing/test/UnitTests/Patterns/InlineRouteParameterParserTest.cs
index a65c940c31..5fbee00c6d 100644
--- a/src/Http/Routing/test/UnitTests/Patterns/InlineRouteParameterParserTest.cs
+++ b/src/Http/Routing/test/UnitTests/Patterns/InlineRouteParameterParserTest.cs
@@ -5,1072 +5,1071 @@ using System.Linq;
using Microsoft.AspNetCore.Routing.Constraints;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+public class InlineRouteParameterParserTest
{
- public class InlineRouteParameterParserTest
- {
- [Theory]
- [InlineData("=")]
- [InlineData(":")]
- public void ParseRouteParameter_WithoutADefaultValue(string parameterName)
- {
- // Arrange & Act
- var templatePart = ParseParameter(parameterName);
-
- // Assert
- Assert.Equal(parameterName, templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.Empty(templatePart.ParameterPolicies);
- }
-
- [Fact]
- public void ParseRouteParameter_WithEmptyDefaultValue()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param=");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("", templatePart.Default);
- Assert.Empty(templatePart.ParameterPolicies);
- }
-
- [Fact]
- public void ParseRouteParameter_WithoutAConstraintName()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param:");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.Empty(templatePart.ParameterPolicies);
- }
-
- [Fact]
- public void ParseRouteParameter_WithoutAConstraintNameOrParameterName()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param:=");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("", templatePart.Default);
- Assert.Empty(templatePart.ParameterPolicies);
- }
-
- [Fact]
- public void ParseRouteParameter_WithADefaultValueContainingConstraintSeparator()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param=:");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal(":", templatePart.Default);
- Assert.Empty(templatePart.ParameterPolicies);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintAndDefault_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param:int=111111");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("111111", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("int", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithArgumentsAndDefault_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)=111111");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("111111", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\d+)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintAndOptional_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("int", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int=12?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("12", templatePart.Default);
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("int", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValueWithQuestionMark_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int=12??");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("12?", templatePart.Default);
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("int", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\d+)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)=abc?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.True(templatePart.IsOptional);
-
- Assert.Equal("abc", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\d+)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(d+):test(w+)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Collection(templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(d+)", constraint.Content),
- constraint => Assert.Equal(@"test(w+)", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_DoubleDelimiters_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param::test(d+)::test(w+)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Collection(
- templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(d+)", constraint.Content),
- constraint => Assert.Equal(@"test(w+)", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_ColonInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+):test(\w:+)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Collection(templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(\d+)", constraint.Content),
- constraint => Assert.Equal(@"test(\w:+)", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+):test(\w+)=qwer");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("qwer", templatePart.Default);
-
- Assert.Collection(templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(\d+)", constraint.Content),
- constraint => Assert.Equal(@"test(\w+)", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_DoubleDelimiters_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\d+)::test(\w+)==qwer");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("=qwer", templatePart.Default);
-
- Assert.Collection(
- templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(\d+)", constraint.Content),
- constraint => Assert.Equal(@"test(\w+)", constraint.Content));
- }
-
- [Theory]
- [InlineData("=")]
- [InlineData("+=")]
- [InlineData(">= || <= || ==")]
- public void ParseRouteParameter_WithDefaultValue_ContainingDelimiter(string defaultValue)
- {
- // Arrange & Act
- var templatePart = ParseParameter($"comparison-operator:length(6)={defaultValue}");
-
- // Assert
- Assert.Equal("comparison-operator", templatePart.Name);
- Assert.Equal(defaultValue, templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("length(6)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
- {
- // Arrange & Act
- var routePattern = RoutePatternFactory.Parse(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
-
- // Assert
- var parameters = routePattern.Parameters.ToArray();
-
- var param1 = parameters[0];
- Assert.Equal("p1", param1.Name);
- Assert.Equal("hello", param1.Default);
- Assert.False(param1.IsOptional);
-
- Assert.Collection(param1.ParameterPolicies,
- constraint => Assert.Equal("int", constraint.Content),
- constraint => Assert.Equal("test(3)", constraint.Content)
- );
-
- var param2 = parameters[1];
- Assert.Equal("p2", param2.Name);
- Assert.Equal("abc", param2.Default);
- Assert.False(param2.IsOptional);
-
- var param3 = parameters[2];
- Assert.Equal("p3", param3.Name);
- Assert.True(param3.IsOptional);
- }
-
- [Fact]
- public void ParseRouteParameter_NoTokens_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter("world");
-
- // Assert
- Assert.Equal("world", templatePart.Name);
- }
-
- [Fact]
- public void ParseRouteParameter_ParamDefault_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter("param=world");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("world", templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_ClosingBraceIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\})");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\})", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\})=wer");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("wer", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\})", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingParenInPattern_ClosingParenIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\))");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\))", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosingParenInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\))=fsd");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("fsd", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\))", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonInPattern_ColonIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(:)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(:)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(:)=mnf");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("mnf", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(:)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonsInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(a:b:c)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(a:b:c)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonInParamName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@":param:test=12");
-
- // Assert
- Assert.Equal(":param", templatePart.Name);
+ [Theory]
+ [InlineData("=")]
+ [InlineData(":")]
+ public void ParseRouteParameter_WithoutADefaultValue(string parameterName)
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(parameterName);
+
+ // Assert
+ Assert.Equal(parameterName, templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.Empty(templatePart.ParameterPolicies);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithEmptyDefaultValue()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param=");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("", templatePart.Default);
+ Assert.Empty(templatePart.ParameterPolicies);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithoutAConstraintName()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param:");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.Empty(templatePart.ParameterPolicies);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithoutAConstraintNameOrParameterName()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param:=");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("", templatePart.Default);
+ Assert.Empty(templatePart.ParameterPolicies);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithADefaultValueContainingConstraintSeparator()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param=:");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal(":", templatePart.Default);
+ Assert.Empty(templatePart.ParameterPolicies);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndDefault_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param:int=111111");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("111111", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("int", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithArgumentsAndDefault_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)=111111");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("111111", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\d+)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndOptional_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.True(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("int", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int=12?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("12", templatePart.Default);
+ Assert.True(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("int", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintAndOptional_WithDefaultValueWithQuestionMark_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int=12??");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("12?", templatePart.Default);
+ Assert.True(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("int", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.True(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\d+)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)=abc?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.True(templatePart.IsOptional);
+
+ Assert.Equal("abc", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\d+)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(d+):test(w+)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Collection(templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(d+)", constraint.Content),
+ constraint => Assert.Equal(@"test(w+)", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_DoubleDelimiters_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param::test(d+)::test(w+)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Collection(
+ templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(d+)", constraint.Content),
+ constraint => Assert.Equal(@"test(w+)", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_ColonInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+):test(\w:+)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Collection(templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(\d+)", constraint.Content),
+ constraint => Assert.Equal(@"test(\w:+)", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+):test(\w+)=qwer");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("qwer", templatePart.Default);
+
+ Assert.Collection(templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(\d+)", constraint.Content),
+ constraint => Assert.Equal(@"test(\w+)", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ChainedConstraints_WithDefaultValue_DoubleDelimiters_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\d+)::test(\w+)==qwer");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("=qwer", templatePart.Default);
+
+ Assert.Collection(
+ templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(\d+)", constraint.Content),
+ constraint => Assert.Equal(@"test(\w+)", constraint.Content));
+ }
+
+ [Theory]
+ [InlineData("=")]
+ [InlineData("+=")]
+ [InlineData(">= || <= || ==")]
+ public void ParseRouteParameter_WithDefaultValue_ContainingDelimiter(string defaultValue)
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter($"comparison-operator:length(6)={defaultValue}");
+
+ // Assert
+ Assert.Equal("comparison-operator", templatePart.Name);
+ Assert.Equal(defaultValue, templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("length(6)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var routePattern = RoutePatternFactory.Parse(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
+
+ // Assert
+ var parameters = routePattern.Parameters.ToArray();
+
+ var param1 = parameters[0];
+ Assert.Equal("p1", param1.Name);
+ Assert.Equal("hello", param1.Default);
+ Assert.False(param1.IsOptional);
+
+ Assert.Collection(param1.ParameterPolicies,
+ constraint => Assert.Equal("int", constraint.Content),
+ constraint => Assert.Equal("test(3)", constraint.Content)
+ );
+
+ var param2 = parameters[1];
+ Assert.Equal("p2", param2.Name);
+ Assert.Equal("abc", param2.Default);
+ Assert.False(param2.IsOptional);
+
+ var param3 = parameters[2];
+ Assert.Equal("p3", param3.Name);
+ Assert.True(param3.IsOptional);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_NoTokens_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("world");
+
+ // Assert
+ Assert.Equal("world", templatePart.Name);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ParamDefault_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter("param=world");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("world", templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_ClosingBraceIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\})");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\})", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\})=wer");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("wer", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\})", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingParenInPattern_ClosingParenIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\))");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\))", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosingParenInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\))=fsd");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("fsd", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\))", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonInPattern_ColonIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(:)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(:)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(:)=mnf");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("mnf", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(:)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonsInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(a:b:c)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(a:b:c)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonInParamName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@":param:test=12");
+
+ // Assert
+ Assert.Equal(":param", templatePart.Name);
+
+ Assert.Equal("12", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("test", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithTwoColonInParamName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@":param::test=12");
+
+ // Assert
+ Assert.Equal(":param", templatePart.Name);
+
+ Assert.Equal("12", templatePart.Default);
+
+ Assert.Collection(
+ templatePart.ParameterPolicies,
+ constraint => Assert.Equal("test", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_EmptyConstraint_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@":param:test:");
+
+ // Assert
+ Assert.Equal(":param", templatePart.Name);
+
+ Assert.Collection(
+ templatePart.ParameterPolicies,
+ constraint => Assert.Equal("test", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithCommaInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\w,\w)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\w,\w)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithCommaInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par,am:test(\w)");
+
+ // Assert
+ Assert.Equal("par,am", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\w)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithCommaInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\w,\w)=jsd");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("jsd", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\w,\w)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualsFollowedByQuestionMark_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:int=?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("", templatePart.Default);
+
+ Assert.True(templatePart.IsOptional);
- Assert.Equal("12", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("test", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithTwoColonInParamName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@":param::test=12");
-
- // Assert
- Assert.Equal(":param", templatePart.Name);
-
- Assert.Equal("12", templatePart.Default);
-
- Assert.Collection(
- templatePart.ParameterPolicies,
- constraint => Assert.Equal("test", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_EmptyConstraint_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@":param:test:");
-
- // Assert
- Assert.Equal(":param", templatePart.Name);
-
- Assert.Collection(
- templatePart.ParameterPolicies,
- constraint => Assert.Equal("test", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithCommaInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\w,\w)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\w,\w)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithCommaInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par,am:test(\w)");
-
- // Assert
- Assert.Equal("par,am", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\w)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithCommaInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\w,\w)=jsd");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("jsd", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\w,\w)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualsFollowedByQuestionMark_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:int=?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("", templatePart.Default);
-
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("int", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(=)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("test(=)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_EqualsSignInDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param=test=bar");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("test=bar", templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(a==b)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("test(a==b)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(a==b)=dvds");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("dvds", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("test(a==b)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_EqualEqualSignInName_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par==am:test=dvds");
-
- // Assert
- Assert.Equal("par", templatePart.Name);
- Assert.Equal("=am:test=dvds", templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_EqualEqualSignInDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test==dvds");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("=dvds", templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_DefaultValueWithColonAndParens_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par=am:test(asd)");
-
- // Assert
- Assert.Equal("par", templatePart.Name);
- Assert.Equal("am:test(asd)", templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_DefaultValueWithEqualsSignIn_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par=test(am):est=asd");
-
- // Assert
- Assert.Equal("par", templatePart.Name);
- Assert.Equal("test(am):est=asd", templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(=)=sds");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("sds", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("test(=)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\{)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\{)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenBraceInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par{am:test(\sd)");
-
- // Assert
- Assert.Equal("par{am", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\sd)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\{)=xvc");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("xvc", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\{)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par(am:test(\()");
-
- // Assert
- Assert.Equal("par(am", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\()", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\()");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\()", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenNoCloseParen_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#$%");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal("test(#$%", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenAndColon_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#:test1");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Collection(templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(#", constraint.Content),
- constraint => Assert.Equal(@"test1", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenAndColonWithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(abc:somevalue):name(test1:differentname=default-value");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("default-value", templatePart.Default);
-
- Assert.Collection(templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Content),
- constraint => Assert.Equal(@"name(test1", constraint.Content),
- constraint => Assert.Equal(@"differentname", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenAndDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(constraintvalue=test1");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("test1", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(constraintvalue", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithOpenParenInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\()=djk");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
-
- Assert.Equal("djk", templatePart.Default);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\()", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\?)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\?)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)=sdf");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("sdf", templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\?)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_WithDefaultValue_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(\?)=sdf?");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Equal("sdf", templatePart.Default);
- Assert.True(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\?)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithQuestionMarkInName_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"par?am:test(\?)");
-
- // Assert
- Assert.Equal("par?am", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(\?)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithClosedParenAndColonInPattern_ParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#):$)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- Assert.Collection(templatePart.ParameterPolicies,
- constraint => Assert.Equal(@"test(#)", constraint.Content),
- constraint => Assert.Equal(@"$)", constraint.Content));
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithColonAndClosedParenInPattern_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"param:test(#:)$)");
-
- // Assert
- Assert.Equal("param", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"test(#:)$)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ContainingMultipleUnclosedParenthesisInConstraint()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"foo:regex(\\(\\(\\(\\()");
-
- // Assert
- Assert.Equal("foo", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithBraces_PatternIsParsedCorrectly()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)"); // ssn
-
- // Assert
- Assert.Equal("p1", templatePart.Name);
- Assert.Null(templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_ConstraintWithBraces_WithDefaultValue()
- {
- // Arrange & Act
- var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)=123-456-7890"); // ssn
-
- // Assert
- Assert.Equal("p1", templatePart.Name);
- Assert.Equal("123-456-7890", templatePart.Default);
- Assert.False(templatePart.IsOptional);
-
- var constraint = Assert.Single(templatePart.ParameterPolicies);
- Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Content);
- }
-
- [Theory]
- [InlineData("", "")]
- [InlineData("?", "")]
- [InlineData("*", "")]
- [InlineData("**", "")]
- [InlineData(" ", " ")]
- [InlineData("\t", "\t")]
- [InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
- [InlineData(",,,", ",,,")]
- public void ParseRouteParameter_ParameterWithoutInlineConstraint_ReturnsTemplatePartWithEmptyInlineValues(
- string parameter,
- string expectedParameterName)
- {
- // Arrange & Act
- var templatePart = ParseParameter(parameter);
-
- // Assert
- Assert.Equal(expectedParameterName, templatePart.Name);
- Assert.Empty(templatePart.ParameterPolicies);
- Assert.Null(templatePart.Default);
- }
-
- [Fact]
- public void ParseRouteParameter_WithSingleAsteriskCatchAll_IsParsedCorrectly()
- {
- // Arrange & Act
- var parameterPart = ParseParameter("*path");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
- Assert.True(parameterPart.EncodeSlashes);
- }
-
- [Fact]
- public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndDefaultValue_IsParsedCorrectly()
- {
- // Arrange & Act
- var parameterPart = ParseParameter("*path=a/b/c");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.NotNull(parameterPart.Default);
- Assert.Equal("a/b/c", parameterPart.Default.ToString());
- Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
- Assert.True(parameterPart.EncodeSlashes);
- }
-
- [Fact]
- public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndConstraints_IsParsedCorrectly()
- {
- // Arrange
- var constraintContent = "regex(^(/[^/ ]*)+/?$)";
-
- // Act
- var parameterPart = ParseParameter($"*path:{constraintContent}");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
- var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
- Assert.Equal(constraintContent, constraintReference.Content);
- Assert.True(parameterPart.EncodeSlashes);
- }
-
- [Fact]
- public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndConstraints_AndDefaultValue_IsParsedCorrectly()
- {
- // Arrange
- var constraintContent = "regex(^(/[^/ ]*)+/?$)";
-
- // Act
- var parameterPart = ParseParameter($"*path:{constraintContent}=a/b/c");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
- var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
- Assert.Equal(constraintContent, constraintReference.Content);
- Assert.NotNull(parameterPart.Default);
- Assert.Equal("a/b/c", parameterPart.Default.ToString());
- Assert.True(parameterPart.EncodeSlashes);
- }
-
- [Fact]
- public void ParseRouteParameter_WithDoubleAsteriskCatchAll_IsParsedCorrectly()
- {
- // Arrange & Act
- var parameterPart = ParseParameter("**path");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.False(parameterPart.EncodeSlashes);
- }
-
- [Fact]
- public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndDefaultValue_IsParsedCorrectly()
- {
- // Arrange & Act
- var parameterPart = ParseParameter("**path=a/b/c");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.NotNull(parameterPart.Default);
- Assert.Equal("a/b/c", parameterPart.Default.ToString());
- Assert.False(parameterPart.EncodeSlashes);
- }
-
- [Fact]
- public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndConstraints_IsParsedCorrectly()
- {
- // Arrange
- var constraintContent = "regex(^(/[^/ ]*)+/?$)";
-
- // Act
- var parameterPart = ParseParameter($"**path:{constraintContent}");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.False(parameterPart.EncodeSlashes);
- var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
- Assert.Equal(constraintContent, constraintReference.Content);
- }
-
- [Fact]
- public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndConstraints_AndDefaultValue_IsParsedCorrectly()
- {
- // Arrange
- var constraintContent = "regex(^(/[^/ ]*)+/?$)";
-
- // Act
- var parameterPart = ParseParameter($"**path:{constraintContent}=a/b/c");
-
- // Assert
- Assert.Equal("path", parameterPart.Name);
- Assert.True(parameterPart.IsCatchAll);
- Assert.False(parameterPart.EncodeSlashes);
- var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
- Assert.Equal(constraintContent, constraintReference.Content);
- Assert.NotNull(parameterPart.Default);
- Assert.Equal("a/b/c", parameterPart.Default.ToString());
- }
-
- private RoutePatternParameterPart ParseParameter(string routeParameter)
- {
- // See: #475 - these tests don't pass the 'whole' text.
- var templatePart = RouteParameterParser.ParseRouteParameter(routeParameter);
- return templatePart;
- }
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("int", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(=)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("test(=)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_EqualsSignInDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param=test=bar");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("test=bar", templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(a==b)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("test(a==b)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualEqualSignInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(a==b)=dvds");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("dvds", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("test(a==b)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_EqualEqualSignInName_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par==am:test=dvds");
+
+ // Assert
+ Assert.Equal("par", templatePart.Name);
+ Assert.Equal("=am:test=dvds", templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_EqualEqualSignInDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test==dvds");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("=dvds", templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_DefaultValueWithColonAndParens_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par=am:test(asd)");
+
+ // Assert
+ Assert.Equal("par", templatePart.Name);
+ Assert.Equal("am:test(asd)", templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_DefaultValueWithEqualsSignIn_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par=test(am):est=asd");
+
+ // Assert
+ Assert.Equal("par", templatePart.Name);
+ Assert.Equal("test(am):est=asd", templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(=)=sds");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("sds", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("test(=)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\{)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\{)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenBraceInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par{am:test(\sd)");
+
+ // Assert
+ Assert.Equal("par{am", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\sd)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\{)=xvc");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("xvc", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\{)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par(am:test(\()");
+
+ // Assert
+ Assert.Equal("par(am", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\()", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\()");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\()", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenNoCloseParen_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#$%");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal("test(#$%", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenAndColon_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#:test1");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Collection(templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(#", constraint.Content),
+ constraint => Assert.Equal(@"test1", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenAndColonWithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(abc:somevalue):name(test1:differentname=default-value");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("default-value", templatePart.Default);
+
+ Assert.Collection(templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(abc:somevalue)", constraint.Content),
+ constraint => Assert.Equal(@"name(test1", constraint.Content),
+ constraint => Assert.Equal(@"differentname", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenAndDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(constraintvalue=test1");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("test1", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(constraintvalue", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithOpenParenInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\()=djk");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+
+ Assert.Equal("djk", templatePart.Default);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\()", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\?)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.True(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\?)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)=sdf");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("sdf", templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\?)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_Optional_WithDefaultValue_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(\?)=sdf?");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Equal("sdf", templatePart.Default);
+ Assert.True(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\?)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithQuestionMarkInName_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"par?am:test(\?)");
+
+ // Assert
+ Assert.Equal("par?am", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(\?)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithClosedParenAndColonInPattern_ParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#):$)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ Assert.Collection(templatePart.ParameterPolicies,
+ constraint => Assert.Equal(@"test(#)", constraint.Content),
+ constraint => Assert.Equal(@"$)", constraint.Content));
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithColonAndClosedParenInPattern_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"param:test(#:)$)");
+
+ // Assert
+ Assert.Equal("param", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"test(#:)$)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ContainingMultipleUnclosedParenthesisInConstraint()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"foo:regex(\\(\\(\\(\\()");
+
+ // Assert
+ Assert.Equal("foo", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"regex(\\(\\(\\(\\()", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithBraces_PatternIsParsedCorrectly()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)"); // ssn
+
+ // Assert
+ Assert.Equal("p1", templatePart.Name);
+ Assert.Null(templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_ConstraintWithBraces_WithDefaultValue()
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(@"p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)=123-456-7890"); // ssn
+
+ // Assert
+ Assert.Equal("p1", templatePart.Name);
+ Assert.Equal("123-456-7890", templatePart.Default);
+ Assert.False(templatePart.IsOptional);
+
+ var constraint = Assert.Single(templatePart.ParameterPolicies);
+ Assert.Equal(@"regex(^\d{{3}}-\d{{3}}-\d{{4}}$)", constraint.Content);
+ }
+
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("?", "")]
+ [InlineData("*", "")]
+ [InlineData("**", "")]
+ [InlineData(" ", " ")]
+ [InlineData("\t", "\t")]
+ [InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
+ [InlineData(",,,", ",,,")]
+ public void ParseRouteParameter_ParameterWithoutInlineConstraint_ReturnsTemplatePartWithEmptyInlineValues(
+ string parameter,
+ string expectedParameterName)
+ {
+ // Arrange & Act
+ var templatePart = ParseParameter(parameter);
+
+ // Assert
+ Assert.Equal(expectedParameterName, templatePart.Name);
+ Assert.Empty(templatePart.ParameterPolicies);
+ Assert.Null(templatePart.Default);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithSingleAsteriskCatchAll_IsParsedCorrectly()
+ {
+ // Arrange & Act
+ var parameterPart = ParseParameter("*path");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
+ Assert.True(parameterPart.EncodeSlashes);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndDefaultValue_IsParsedCorrectly()
+ {
+ // Arrange & Act
+ var parameterPart = ParseParameter("*path=a/b/c");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.NotNull(parameterPart.Default);
+ Assert.Equal("a/b/c", parameterPart.Default.ToString());
+ Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
+ Assert.True(parameterPart.EncodeSlashes);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndConstraints_IsParsedCorrectly()
+ {
+ // Arrange
+ var constraintContent = "regex(^(/[^/ ]*)+/?$)";
+
+ // Act
+ var parameterPart = ParseParameter($"*path:{constraintContent}");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
+ var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
+ Assert.Equal(constraintContent, constraintReference.Content);
+ Assert.True(parameterPart.EncodeSlashes);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithSingleAsteriskCatchAll_AndConstraints_AndDefaultValue_IsParsedCorrectly()
+ {
+ // Arrange
+ var constraintContent = "regex(^(/[^/ ]*)+/?$)";
+
+ // Act
+ var parameterPart = ParseParameter($"*path:{constraintContent}=a/b/c");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.Equal(RoutePatternParameterKind.CatchAll, parameterPart.ParameterKind);
+ var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
+ Assert.Equal(constraintContent, constraintReference.Content);
+ Assert.NotNull(parameterPart.Default);
+ Assert.Equal("a/b/c", parameterPart.Default.ToString());
+ Assert.True(parameterPart.EncodeSlashes);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithDoubleAsteriskCatchAll_IsParsedCorrectly()
+ {
+ // Arrange & Act
+ var parameterPart = ParseParameter("**path");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.False(parameterPart.EncodeSlashes);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndDefaultValue_IsParsedCorrectly()
+ {
+ // Arrange & Act
+ var parameterPart = ParseParameter("**path=a/b/c");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.NotNull(parameterPart.Default);
+ Assert.Equal("a/b/c", parameterPart.Default.ToString());
+ Assert.False(parameterPart.EncodeSlashes);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndConstraints_IsParsedCorrectly()
+ {
+ // Arrange
+ var constraintContent = "regex(^(/[^/ ]*)+/?$)";
+
+ // Act
+ var parameterPart = ParseParameter($"**path:{constraintContent}");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.False(parameterPart.EncodeSlashes);
+ var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
+ Assert.Equal(constraintContent, constraintReference.Content);
+ }
+
+ [Fact]
+ public void ParseRouteParameter_WithDoubleAsteriskCatchAll_AndConstraints_AndDefaultValue_IsParsedCorrectly()
+ {
+ // Arrange
+ var constraintContent = "regex(^(/[^/ ]*)+/?$)";
+
+ // Act
+ var parameterPart = ParseParameter($"**path:{constraintContent}=a/b/c");
+
+ // Assert
+ Assert.Equal("path", parameterPart.Name);
+ Assert.True(parameterPart.IsCatchAll);
+ Assert.False(parameterPart.EncodeSlashes);
+ var constraintReference = Assert.Single(parameterPart.ParameterPolicies);
+ Assert.Equal(constraintContent, constraintReference.Content);
+ Assert.NotNull(parameterPart.Default);
+ Assert.Equal("a/b/c", parameterPart.Default.ToString());
+ }
+
+ private RoutePatternParameterPart ParseParameter(string routeParameter)
+ {
+ // See: #475 - these tests don't pass the 'whole' text.
+ var templatePart = RouteParameterParser.ParseRouteParameter(routeParameter);
+ return templatePart;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs
index b0bbc3b0cb..68878bd8f5 100644
--- a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs
+++ b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs
@@ -9,713 +9,712 @@ using Microsoft.AspNetCore.Routing.Template;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+public class RoutePatternFactoryTest
{
- public class RoutePatternFactoryTest
+ [Fact]
+ public void Pattern_MergesDefaultValues()
{
- [Fact]
- public void Pattern_MergesDefaultValues()
- {
- // Arrange
- var template = "{a}/{b}/{c=19}";
- var defaults = new { a = "15", b = 17 };
- var constraints = new { };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
-
- // Assert
- Assert.Equal("15", actual.GetParameter("a").Default);
- Assert.Equal(17, actual.GetParameter("b").Default);
- Assert.Equal("19", actual.GetParameter("c").Default);
-
- Assert.Collection(
- actual.Defaults.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("a", kvp.Key); Assert.Equal("15", kvp.Value); },
- kvp => { Assert.Equal("b", kvp.Key); Assert.Equal(17, kvp.Value); },
- kvp => { Assert.Equal("c", kvp.Key); Assert.Equal("19", kvp.Value); });
- }
-
- [Fact]
- public void Pattern_ExtraDefaultValues()
- {
- // Arrange
- var template = "{a}/{b}/{c}";
- var defaults = new { d = "15", e = 17 };
- var constraints = new { };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
-
- // Assert
- Assert.Collection(
- actual.Defaults.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("d", kvp.Key); Assert.Equal("15", kvp.Value); },
- kvp => { Assert.Equal("e", kvp.Key); Assert.Equal(17, kvp.Value); });
- }
-
- [Fact]
- public void Pattern_DifferentDuplicateDefaultValue_Throws()
- {
- // Arrange
- var template = "{a=13}/{b}/{c}";
- var defaults = new { a = "15", };
- var constraints = new { };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments));
-
- // Assert
- Assert.Equal(
- "The route parameter 'a' has both an inline default value and an explicit default " +
- "value specified. A route parameter cannot contain an inline default value when a " +
- "default value is specified explicitly. Consider removing one of them.",
- ex.Message);
- }
-
- [Fact]
- public void Pattern_SameDuplicateDefaultValue()
- {
- // Arrange
- var template = "{a=13}/{b}/{c}";
- var defaults = new { a = "13", };
- var constraints = new { };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
-
- // Assert
- Assert.Collection(
- actual.Defaults,
- kvp => { Assert.Equal("a", kvp.Key); Assert.Equal("13", kvp.Value); });
- }
-
- [Fact]
- public void Pattern_OptionalParameterDefaultValue_Throws()
- {
- // Arrange
- var template = "{a}/{b}/{c?}";
- var defaults = new { c = "15", };
- var constraints = new { };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments));
-
- // Assert
- Assert.Equal(
- "An optional parameter cannot have default value.",
- ex.Message);
- }
-
- [Fact]
- public void Pattern_MergesConstraints()
- {
- // Arrange
- var template = "{a:int}/{b}/{c}";
- var defaults = new { };
- var constraints = new { a = new RegexRouteConstraint("foo"), b = new RegexRouteConstraint("bar") };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
-
- // Assert
- Assert.Collection(
- actual.GetParameter("a").ParameterPolicies,
- c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy),
- c => Assert.Equal("int", c.Content));
- Assert.Collection(
- actual.GetParameter("b").ParameterPolicies,
- c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
-
- Assert.Collection(
- actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("a", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy),
- c => Assert.Equal("int", c.Content));
- },
- kvp =>
- {
- Assert.Equal("b", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
- });
- }
-
- [Fact]
- public void Pattern_ExtraConstraints()
- {
- // Arrange
- var template = "{a}/{b}/{c}";
- var defaults = new { };
- var constraints = new { d = new RegexRouteConstraint("foo"), e = new RegexRouteConstraint("bar") };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
-
- // Assert
- Assert.Collection(
- actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("d", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
- },
- kvp =>
- {
- Assert.Equal("e", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
- });
- }
-
- [Fact]
- public void Pattern_ExtraConstraints_MultipleConstraintsForKey()
- {
- // Arrange
- var template = "{a}/{b}/{c}";
- var defaults = new { };
- var constraints = new { d = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
-
- var original = RoutePatternFactory.Parse(template);
+ // Arrange
+ var template = "{a}/{b}/{c=19}";
+ var defaults = new { a = "15", b = 17 };
+ var constraints = new { };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Equal("15", actual.GetParameter("a").Default);
+ Assert.Equal(17, actual.GetParameter("b").Default);
+ Assert.Equal("19", actual.GetParameter("c").Default);
+
+ Assert.Collection(
+ actual.Defaults.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("a", kvp.Key); Assert.Equal("15", kvp.Value); },
+ kvp => { Assert.Equal("b", kvp.Key); Assert.Equal(17, kvp.Value); },
+ kvp => { Assert.Equal("c", kvp.Key); Assert.Equal("19", kvp.Value); });
+ }
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
+ [Fact]
+ public void Pattern_ExtraDefaultValues()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c}";
+ var defaults = new { d = "15", e = 17 };
+ var constraints = new { };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.Defaults.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("d", kvp.Key); Assert.Equal("15", kvp.Value); },
+ kvp => { Assert.Equal("e", kvp.Key); Assert.Equal(17, kvp.Value); });
+ }
- // Assert
- Assert.Collection(
- actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("d", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
- c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
- c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
- });
- }
-
- [Fact]
- public void Pattern_ExtraConstraints_MergeMultipleConstraintsForKey()
- {
- // Arrange
- var template = "{a:int}/{b}/{c:int}";
- var defaults = new { };
- var constraints = new { b = "fizz", c = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
+ [Fact]
+ public void Pattern_DifferentDuplicateDefaultValue_Throws()
+ {
+ // Arrange
+ var template = "{a=13}/{b}/{c}";
+ var defaults = new { a = "15", };
+ var constraints = new { };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments));
+
+ // Assert
+ Assert.Equal(
+ "The route parameter 'a' has both an inline default value and an explicit default " +
+ "value specified. A route parameter cannot contain an inline default value when a " +
+ "default value is specified explicitly. Consider removing one of them.",
+ ex.Message);
+ }
- var original = RoutePatternFactory.Parse(template);
+ [Fact]
+ public void Pattern_SameDuplicateDefaultValue()
+ {
+ // Arrange
+ var template = "{a=13}/{b}/{c}";
+ var defaults = new { a = "13", };
+ var constraints = new { };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.Defaults,
+ kvp => { Assert.Equal("a", kvp.Key); Assert.Equal("13", kvp.Value); });
+ }
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
+ [Fact]
+ public void Pattern_OptionalParameterDefaultValue_Throws()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c?}";
+ var defaults = new { c = "15", };
+ var constraints = new { };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments));
+
+ // Assert
+ Assert.Equal(
+ "An optional parameter cannot have default value.",
+ ex.Message);
+ }
- // Assert
- Assert.Collection(
- actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("a", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.Equal("int", c.Content));
- },
- kvp =>
- {
- Assert.Equal("b", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.Equal("^(fizz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
- },
- kvp =>
- {
- Assert.Equal("c", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
- c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
- c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
- c => Assert.Equal("int", c.Content));
- });
- }
-
- [Fact]
- public void Pattern_ExtraConstraints_NestedArray_Throws()
- {
- // Arrange
- var template = "{a}/{b}/{c:int}";
- var defaults = new { };
- var constraints = new { c = new object[] { new object[0] } };
+ [Fact]
+ public void Pattern_MergesConstraints()
+ {
+ // Arrange
+ var template = "{a:int}/{b}/{c}";
+ var defaults = new { };
+ var constraints = new { a = new RegexRouteConstraint("foo"), b = new RegexRouteConstraint("bar") };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.GetParameter("a").ParameterPolicies,
+ c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy),
+ c => Assert.Equal("int", c.Content));
+ Assert.Collection(
+ actual.GetParameter("b").ParameterPolicies,
+ c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
+
+ Assert.Collection(
+ actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("a", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy),
+ c => Assert.Equal("int", c.Content));
+ },
+ kvp =>
+ {
+ Assert.Equal("b", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
+ });
+ }
- var original = RoutePatternFactory.Parse(template);
+ [Fact]
+ public void Pattern_ExtraConstraints()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c}";
+ var defaults = new { };
+ var constraints = new { d = new RegexRouteConstraint("foo"), e = new RegexRouteConstraint("bar") };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("d", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
+ },
+ kvp =>
+ {
+ Assert.Equal("e", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy));
+ });
+ }
- // Act & Assert
- Assert.Throws<InvalidOperationException>(() =>
+ [Fact]
+ public void Pattern_ExtraConstraints_MultipleConstraintsForKey()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c}";
+ var defaults = new { };
+ var constraints = new { d = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
+ kvp =>
{
- RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
+ Assert.Equal("d", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
+ c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
+ c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
});
- }
+ }
- [Fact]
- public void Pattern_ExtraConstraints_RouteConstraint()
- {
- // Arrange
- var template = "{a}/{b}/{c}";
- var defaults = new { };
- var constraints = new { d = Mock.Of<IRouteConstraint>(), e = Mock.Of<IRouteConstraint>(), };
+ [Fact]
+ public void Pattern_ExtraConstraints_MergeMultipleConstraintsForKey()
+ {
+ // Arrange
+ var template = "{a:int}/{b}/{c:int}";
+ var defaults = new { };
+ var constraints = new { b = "fizz", c = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("a", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.Equal("int", c.Content));
+ },
+ kvp =>
+ {
+ Assert.Equal("b", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.Equal("^(fizz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
+ },
+ kvp =>
+ {
+ Assert.Equal("c", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
+ c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
+ c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
+ c => Assert.Equal("int", c.Content));
+ });
+ }
- var original = RoutePatternFactory.Parse(template);
+ [Fact]
+ public void Pattern_ExtraConstraints_NestedArray_Throws()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c:int}";
+ var defaults = new { };
+ var constraints = new { c = new object[] { new object[0] } };
- // Act
- var actual = RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments);
+ var original = RoutePatternFactory.Parse(template);
- // Assert
- Assert.Collection(
- actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("d", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.NotNull(c.ParameterPolicy));
- },
- kvp =>
- {
- Assert.Equal("e", kvp.Key);
- Assert.Collection(
- kvp.Value,
- c => Assert.NotNull(c.ParameterPolicy));
- });
- }
-
- [Fact]
- public void Pattern_CreatesConstraintFromString()
+ // Act & Assert
+ Assert.Throws<InvalidOperationException>(() =>
{
- // Arrange
- var template = "{a}/{b}/{c}";
- var defaults = new { };
- var constraints = new { d = "foo", };
-
- var original = RoutePatternFactory.Parse(template);
-
- // Act
- var actual = RoutePatternFactory.Pattern(
+ RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);
+ });
+ }
- // Assert
- Assert.Collection(
- actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
- kvp =>
- {
- Assert.Equal("d", kvp.Key);
- var regex = Assert.IsType<RegexRouteConstraint>(Assert.Single(kvp.Value).ParameterPolicy);
- Assert.Equal("^(foo)$", regex.Constraint.ToString());
- });
- }
-
- [Fact]
- public void Pattern_InvalidConstraintTypeThrows()
- {
- // Arrange
- var template = "{a}/{b}/{c}";
- var defaults = new { };
- var constraints = new { d = 17, };
-
- var original = RoutePatternFactory.Parse(template);
+ [Fact]
+ public void Pattern_ExtraConstraints_RouteConstraint()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c}";
+ var defaults = new { };
+ var constraints = new { d = Mock.Of<IRouteConstraint>(), e = Mock.Of<IRouteConstraint>(), };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("d", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.NotNull(c.ParameterPolicy));
+ },
+ kvp =>
+ {
+ Assert.Equal("e", kvp.Key);
+ Assert.Collection(
+ kvp.Value,
+ c => Assert.NotNull(c.ParameterPolicy));
+ });
+ }
- // Act
- var ex = Assert.Throws<InvalidOperationException>(() => RoutePatternFactory.Pattern(
- original.RawText,
- defaults,
- constraints,
- original.PathSegments));
+ [Fact]
+ public void Pattern_CreatesConstraintFromString()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c}";
+ var defaults = new { };
+ var constraints = new { d = "foo", };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var actual = RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments);
+
+ // Assert
+ Assert.Collection(
+ actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
+ kvp =>
+ {
+ Assert.Equal("d", kvp.Key);
+ var regex = Assert.IsType<RegexRouteConstraint>(Assert.Single(kvp.Value).ParameterPolicy);
+ Assert.Equal("^(foo)$", regex.Constraint.ToString());
+ });
+ }
- // Assert
- Assert.Equal(
- $"Invalid constraint '17'. A constraint must be of type 'string' or '{typeof(IRouteConstraint)}'.",
- ex.Message);
- }
+ [Fact]
+ public void Pattern_InvalidConstraintTypeThrows()
+ {
+ // Arrange
+ var template = "{a}/{b}/{c}";
+ var defaults = new { };
+ var constraints = new { d = 17, };
+
+ var original = RoutePatternFactory.Parse(template);
+
+ // Act
+ var ex = Assert.Throws<InvalidOperationException>(() => RoutePatternFactory.Pattern(
+ original.RawText,
+ defaults,
+ constraints,
+ original.PathSegments));
+
+ // Assert
+ Assert.Equal(
+ $"Invalid constraint '17'. A constraint must be of type 'string' or '{typeof(IRouteConstraint)}'.",
+ ex.Message);
+ }
- [Fact]
- public void Pattern_ArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
- {
- // Arrange
- var literalPartA = RoutePatternFactory.LiteralPart("A");
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var segments = new[]
- {
+ [Fact]
+ public void Pattern_ArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
+ {
+ // Arrange
+ var literalPartA = RoutePatternFactory.LiteralPart("A");
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var segments = new[]
+ {
RoutePatternFactory.Segment(literalPartA, paramPartB),
RoutePatternFactory.Segment(paramPartC, literalPartA),
RoutePatternFactory.Segment(paramPartD),
RoutePatternFactory.Segment(literalPartA)
};
- // Act
- var actual = RoutePatternFactory.Pattern(segments);
- segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
- Array.Resize(ref segments, 2);
+ // Act
+ var actual = RoutePatternFactory.Pattern(segments);
+ segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
+ Array.Resize(ref segments, 2);
- // Assert
- Assert.Equal(3, actual.Parameters.Count);
- Assert.Same(paramPartB, actual.Parameters[0]);
- Assert.Same(paramPartC, actual.Parameters[1]);
- Assert.Same(paramPartD, actual.Parameters[2]);
- }
+ // Assert
+ Assert.Equal(3, actual.Parameters.Count);
+ Assert.Same(paramPartB, actual.Parameters[0]);
+ Assert.Same(paramPartC, actual.Parameters[1]);
+ Assert.Same(paramPartD, actual.Parameters[2]);
+ }
- [Fact]
- public void Pattern_RawTextAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
- {
- // Arrange
- var rawText = "raw";
- var literalPartA = RoutePatternFactory.LiteralPart("A");
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var segments = new[]
- {
+ [Fact]
+ public void Pattern_RawTextAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
+ {
+ // Arrange
+ var rawText = "raw";
+ var literalPartA = RoutePatternFactory.LiteralPart("A");
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var segments = new[]
+ {
RoutePatternFactory.Segment(literalPartA, paramPartB),
RoutePatternFactory.Segment(paramPartC, literalPartA),
RoutePatternFactory.Segment(paramPartD),
RoutePatternFactory.Segment(literalPartA)
};
- // Act
- var actual = RoutePatternFactory.Pattern(rawText, segments);
- segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
- Array.Resize(ref segments, 2);
+ // Act
+ var actual = RoutePatternFactory.Pattern(rawText, segments);
+ segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
+ Array.Resize(ref segments, 2);
- // Assert
- Assert.Equal(3, actual.Parameters.Count);
- Assert.Same(paramPartB, actual.Parameters[0]);
- Assert.Same(paramPartC, actual.Parameters[1]);
- Assert.Same(paramPartD, actual.Parameters[2]);
- }
+ // Assert
+ Assert.Equal(3, actual.Parameters.Count);
+ Assert.Same(paramPartB, actual.Parameters[0]);
+ Assert.Same(paramPartC, actual.Parameters[1]);
+ Assert.Same(paramPartD, actual.Parameters[2]);
+ }
- [Fact]
- public void Pattern_DefaultsAndParameterPoliciesAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
- {
- // Arrange
- object defaults = new { B = 12, C = 4 };
- object parameterPolicies = null;
- var literalPartA = RoutePatternFactory.LiteralPart("A");
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var segments = new[]
- {
+ [Fact]
+ public void Pattern_DefaultsAndParameterPoliciesAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
+ {
+ // Arrange
+ object defaults = new { B = 12, C = 4 };
+ object parameterPolicies = null;
+ var literalPartA = RoutePatternFactory.LiteralPart("A");
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var segments = new[]
+ {
RoutePatternFactory.Segment(literalPartA, paramPartB),
RoutePatternFactory.Segment(paramPartC, literalPartA),
RoutePatternFactory.Segment(paramPartD),
RoutePatternFactory.Segment(literalPartA)
};
- // Act
- var actual = RoutePatternFactory.Pattern(defaults, parameterPolicies, segments);
- segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
- Array.Resize(ref segments, 2);
-
- // Assert
- Assert.Equal(3, actual.Parameters.Count);
- Assert.Equal(paramPartB.Name, actual.Parameters[0].Name);
- Assert.Equal(12, actual.Parameters[0].Default);
- Assert.Null(paramPartB.Default);
- Assert.NotSame(paramPartB, actual.Parameters[0]);
- Assert.Equal(paramPartC.Name, actual.Parameters[1].Name);
- Assert.Equal(4, actual.Parameters[1].Default);
- Assert.NotSame(paramPartC, actual.Parameters[1]);
- Assert.Null(paramPartC.Default);
- Assert.Equal(paramPartD.Name, actual.Parameters[2].Name);
- Assert.Null(actual.Parameters[2].Default);
- Assert.Same(paramPartD, actual.Parameters[2]);
- Assert.Null(paramPartD.Default);
- }
-
- [Fact]
- public void Pattern_RawTextAndDefaultsAndParameterPoliciesAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
- {
- // Arrange
- var rawText = "raw";
- object defaults = new { B = 12, C = 4 };
- object parameterPolicies = null;
- var literalPartA = RoutePatternFactory.LiteralPart("A");
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var segments = new[]
- {
+ // Act
+ var actual = RoutePatternFactory.Pattern(defaults, parameterPolicies, segments);
+ segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
+ Array.Resize(ref segments, 2);
+
+ // Assert
+ Assert.Equal(3, actual.Parameters.Count);
+ Assert.Equal(paramPartB.Name, actual.Parameters[0].Name);
+ Assert.Equal(12, actual.Parameters[0].Default);
+ Assert.Null(paramPartB.Default);
+ Assert.NotSame(paramPartB, actual.Parameters[0]);
+ Assert.Equal(paramPartC.Name, actual.Parameters[1].Name);
+ Assert.Equal(4, actual.Parameters[1].Default);
+ Assert.NotSame(paramPartC, actual.Parameters[1]);
+ Assert.Null(paramPartC.Default);
+ Assert.Equal(paramPartD.Name, actual.Parameters[2].Name);
+ Assert.Null(actual.Parameters[2].Default);
+ Assert.Same(paramPartD, actual.Parameters[2]);
+ Assert.Null(paramPartD.Default);
+ }
+
+ [Fact]
+ public void Pattern_RawTextAndDefaultsAndParameterPoliciesAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments()
+ {
+ // Arrange
+ var rawText = "raw";
+ object defaults = new { B = 12, C = 4 };
+ object parameterPolicies = null;
+ var literalPartA = RoutePatternFactory.LiteralPart("A");
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var segments = new[]
+ {
RoutePatternFactory.Segment(literalPartA, paramPartB),
RoutePatternFactory.Segment(paramPartC, literalPartA),
RoutePatternFactory.Segment(paramPartD),
RoutePatternFactory.Segment(literalPartA)
};
- // Act
- var actual = RoutePatternFactory.Pattern(rawText, defaults, parameterPolicies, segments);
- segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
- Array.Resize(ref segments, 2);
-
- // Assert
- Assert.Equal(3, actual.Parameters.Count);
- Assert.Equal(paramPartB.Name, actual.Parameters[0].Name);
- Assert.Equal(12, actual.Parameters[0].Default);
- Assert.Null(paramPartB.Default);
- Assert.NotSame(paramPartB, actual.Parameters[0]);
- Assert.Equal(paramPartC.Name, actual.Parameters[1].Name);
- Assert.Equal(4, actual.Parameters[1].Default);
- Assert.NotSame(paramPartC, actual.Parameters[1]);
- Assert.Null(paramPartC.Default);
- Assert.Equal(paramPartD.Name, actual.Parameters[2].Name);
- Assert.Null(actual.Parameters[2].Default);
- Assert.Same(paramPartD, actual.Parameters[2]);
- Assert.Null(paramPartD.Default);
- }
-
- [Fact]
- public void Parse_WithRequiredValues()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { area = "Admin", };
- var policies = new { };
- var requiredValues = new { area = "Admin", controller = "Store", action = "Index", };
-
- // Act
- var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
-
- // Assert
- Assert.Collection(
- action.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
- kvp => { Assert.Equal("area", kvp.Key); Assert.Equal("Admin", kvp.Value); },
- kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
- }
+ // Act
+ var actual = RoutePatternFactory.Pattern(rawText, defaults, parameterPolicies, segments);
+ segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E"));
+ Array.Resize(ref segments, 2);
+
+ // Assert
+ Assert.Equal(3, actual.Parameters.Count);
+ Assert.Equal(paramPartB.Name, actual.Parameters[0].Name);
+ Assert.Equal(12, actual.Parameters[0].Default);
+ Assert.Null(paramPartB.Default);
+ Assert.NotSame(paramPartB, actual.Parameters[0]);
+ Assert.Equal(paramPartC.Name, actual.Parameters[1].Name);
+ Assert.Equal(4, actual.Parameters[1].Default);
+ Assert.NotSame(paramPartC, actual.Parameters[1]);
+ Assert.Null(paramPartC.Default);
+ Assert.Equal(paramPartD.Name, actual.Parameters[2].Name);
+ Assert.Null(actual.Parameters[2].Default);
+ Assert.Same(paramPartD, actual.Parameters[2]);
+ Assert.Null(paramPartD.Default);
+ }
- [Fact]
- public void Parse_WithRequiredValues_AllowsNullRequiredValue()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
- var requiredValues = new { area = (string)null, controller = "Store", action = "Index", };
+ [Fact]
+ public void Parse_WithRequiredValues()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { area = "Admin", };
+ var policies = new { };
+ var requiredValues = new { area = "Admin", controller = "Store", action = "Index", };
+
+ // Act
+ var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ action.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
+ kvp => { Assert.Equal("area", kvp.Key); Assert.Equal("Admin", kvp.Value); },
+ kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
+ }
- // Act
- var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
+ [Fact]
+ public void Parse_WithRequiredValues_AllowsNullRequiredValue()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+ var requiredValues = new { area = (string)null, controller = "Store", action = "Index", };
+
+ // Act
+ var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ action.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
+ kvp => { Assert.Equal("area", kvp.Key); Assert.Null(kvp.Value); },
+ kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
+ }
- // Assert
- Assert.Collection(
- action.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
- kvp => { Assert.Equal("area", kvp.Key); Assert.Null(kvp.Value); },
- kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
- }
+ [Fact]
+ public void Parse_WithRequiredValues_AllowsEmptyRequiredValue()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+ var requiredValues = new { area = "", controller = "Store", action = "Index", };
+
+ // Act
+ var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
+
+ // Assert
+ Assert.Collection(
+ action.RequiredValues.OrderBy(kvp => kvp.Key),
+ kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
+ kvp => { Assert.Equal("area", kvp.Key); Assert.Equal("", kvp.Value); },
+ kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
+ }
- [Fact]
- public void Parse_WithRequiredValues_AllowsEmptyRequiredValue()
+ [Fact]
+ public void Parse_WithRequiredValues_ThrowsForNonParameterNonDefault()
+ {
+ // Arrange
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new { };
+ var policies = new { };
+ var requiredValues = new { area = "Admin", controller = "Store", action = "Index", };
+
+ // Act
+ var exception = Assert.Throws<InvalidOperationException>(() =>
{
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
- var requiredValues = new { area = "", controller = "Store", action = "Index", };
-
- // Act
var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
+ });
+
+ // Assert
+ Assert.Equal(
+ "No corresponding parameter or default value could be found for the required value " +
+ "'area=Admin'. A non-null required value must correspond to a route parameter or the " +
+ "route pattern must have a matching default value.",
+ exception.Message);
+ }
- // Assert
- Assert.Collection(
- action.RequiredValues.OrderBy(kvp => kvp.Key),
- kvp => { Assert.Equal("action", kvp.Key); Assert.Equal("Index", kvp.Value); },
- kvp => { Assert.Equal("area", kvp.Key); Assert.Equal("", kvp.Value); },
- kvp => { Assert.Equal("controller", kvp.Key); Assert.Equal("Store", kvp.Value); });
- }
+ [Fact]
+ public void ParameterPart_ParameterNameAndDefaultAndParameterKindAndArrayOfParameterPolicies_ShouldMakeCopyOfParameterPolicies()
+ {
+ // Arrange (going through hoops to get an array of RoutePatternParameterPolicyReference)
+ const string name = "Id";
+ var defaults = new { a = "13", };
+ var x = new InlineConstraint("x");
+ var y = new InlineConstraint("y");
+ var z = new InlineConstraint("z");
+ var constraints = new[] { x, y, z };
+ var templatePart = TemplatePart.CreateParameter("t", false, false, null, constraints);
+ var routePatternParameterPart = (RoutePatternParameterPart)templatePart.ToRoutePatternPart();
+ var policies = routePatternParameterPart.ParameterPolicies.ToArray();
+
+ // Act
+ var parameterPart = RoutePatternFactory.ParameterPart(name, defaults, RoutePatternParameterKind.Standard, policies);
+ policies[0] = null;
+ Array.Resize(ref policies, 2);
+
+ // Assert
+ Assert.NotNull(parameterPart.ParameterPolicies);
+ Assert.Equal(3, parameterPart.ParameterPolicies.Count);
+ Assert.NotNull(parameterPart.ParameterPolicies[0]);
+ Assert.NotNull(parameterPart.ParameterPolicies[1]);
+ Assert.NotNull(parameterPart.ParameterPolicies[2]);
+ }
- [Fact]
- public void Parse_WithRequiredValues_ThrowsForNonParameterNonDefault()
- {
- // Arrange
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new { };
- var policies = new { };
- var requiredValues = new { area = "Admin", controller = "Store", action = "Index", };
-
- // Act
- var exception = Assert.Throws<InvalidOperationException>(() =>
- {
- var action = RoutePatternFactory.Parse(template, defaults, policies, requiredValues);
- });
+ [Fact]
+ public void ParameterPart_ParameterNameAndDefaultAndParameterKindAndEnumerableOfParameterPolicies_ShouldMakeCopyOfParameterPolicies()
+ {
+ // Arrange (going through hoops to get an enumerable of RoutePatternParameterPolicyReference)
+ const string name = "Id";
+ var defaults = new { a = "13", };
+ var x = new InlineConstraint("x");
+ var y = new InlineConstraint("y");
+ var z = new InlineConstraint("z");
+ var constraints = new[] { x, y, z };
+ var templatePart = TemplatePart.CreateParameter("t", false, false, null, constraints);
+ var routePatternParameterPart = (RoutePatternParameterPart)templatePart.ToRoutePatternPart();
+ var policies = routePatternParameterPart.ParameterPolicies.ToList();
+
+ // Act
+ var parameterPart = RoutePatternFactory.ParameterPart(name, defaults, RoutePatternParameterKind.Standard, policies);
+ policies[0] = null;
+ policies.RemoveAt(1);
+
+ // Assert
+ Assert.NotNull(parameterPart.ParameterPolicies);
+ Assert.Equal(3, parameterPart.ParameterPolicies.Count);
+ Assert.NotNull(parameterPart.ParameterPolicies[0]);
+ Assert.NotNull(parameterPart.ParameterPolicies[1]);
+ Assert.NotNull(parameterPart.ParameterPolicies[2]);
+ }
- // Assert
- Assert.Equal(
- "No corresponding parameter or default value could be found for the required value " +
- "'area=Admin'. A non-null required value must correspond to a route parameter or the " +
- "route pattern must have a matching default value.",
- exception.Message);
- }
+ [Fact]
+ public void Segment_EnumerableOfParts()
+ {
+ // Arrange
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var parts = new[] { paramPartB, paramPartC, paramPartD };
+
+ // Act
+ var actual = RoutePatternFactory.Segment((IEnumerable<RoutePatternParameterPart>)parts);
+ parts[1] = RoutePatternFactory.ParameterPart("E");
+ Array.Resize(ref parts, 2);
+
+ // Assert
+ Assert.Equal(3, actual.Parts.Count);
+ Assert.Same(paramPartB, actual.Parts[0]);
+ Assert.Same(paramPartC, actual.Parts[1]);
+ Assert.Same(paramPartD, actual.Parts[2]);
+ }
- [Fact]
- public void ParameterPart_ParameterNameAndDefaultAndParameterKindAndArrayOfParameterPolicies_ShouldMakeCopyOfParameterPolicies()
- {
- // Arrange (going through hoops to get an array of RoutePatternParameterPolicyReference)
- const string name = "Id";
- var defaults = new { a = "13", };
- var x = new InlineConstraint("x");
- var y = new InlineConstraint("y");
- var z = new InlineConstraint("z");
- var constraints = new[] { x, y, z };
- var templatePart = TemplatePart.CreateParameter("t", false, false, null, constraints);
- var routePatternParameterPart = (RoutePatternParameterPart) templatePart.ToRoutePatternPart();
- var policies = routePatternParameterPart.ParameterPolicies.ToArray();
-
- // Act
- var parameterPart = RoutePatternFactory.ParameterPart(name, defaults, RoutePatternParameterKind.Standard, policies);
- policies[0] = null;
- Array.Resize(ref policies, 2);
-
- // Assert
- Assert.NotNull(parameterPart.ParameterPolicies);
- Assert.Equal(3, parameterPart.ParameterPolicies.Count);
- Assert.NotNull(parameterPart.ParameterPolicies[0]);
- Assert.NotNull(parameterPart.ParameterPolicies[1]);
- Assert.NotNull(parameterPart.ParameterPolicies[2]);
- }
-
- [Fact]
- public void ParameterPart_ParameterNameAndDefaultAndParameterKindAndEnumerableOfParameterPolicies_ShouldMakeCopyOfParameterPolicies()
- {
- // Arrange (going through hoops to get an enumerable of RoutePatternParameterPolicyReference)
- const string name = "Id";
- var defaults = new { a = "13", };
- var x = new InlineConstraint("x");
- var y = new InlineConstraint("y");
- var z = new InlineConstraint("z");
- var constraints = new[] { x, y, z };
- var templatePart = TemplatePart.CreateParameter("t", false, false, null, constraints);
- var routePatternParameterPart = (RoutePatternParameterPart)templatePart.ToRoutePatternPart();
- var policies = routePatternParameterPart.ParameterPolicies.ToList();
-
- // Act
- var parameterPart = RoutePatternFactory.ParameterPart(name, defaults, RoutePatternParameterKind.Standard, policies);
- policies[0] = null;
- policies.RemoveAt(1);
-
- // Assert
- Assert.NotNull(parameterPart.ParameterPolicies);
- Assert.Equal(3, parameterPart.ParameterPolicies.Count);
- Assert.NotNull(parameterPart.ParameterPolicies[0]);
- Assert.NotNull(parameterPart.ParameterPolicies[1]);
- Assert.NotNull(parameterPart.ParameterPolicies[2]);
- }
-
- [Fact]
- public void Segment_EnumerableOfParts()
- {
- // Arrange
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var parts = new[] { paramPartB, paramPartC, paramPartD };
-
- // Act
- var actual = RoutePatternFactory.Segment((IEnumerable<RoutePatternParameterPart>) parts);
- parts[1] = RoutePatternFactory.ParameterPart("E");
- Array.Resize(ref parts, 2);
-
- // Assert
- Assert.Equal(3, actual.Parts.Count);
- Assert.Same(paramPartB, actual.Parts[0]);
- Assert.Same(paramPartC, actual.Parts[1]);
- Assert.Same(paramPartD, actual.Parts[2]);
- }
-
- [Fact]
- public void Segment_ArrayOfParts()
- {
- // Arrange
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var parts = new[] { paramPartB, paramPartC, paramPartD };
-
- // Act
- var actual = RoutePatternFactory.Segment(parts);
- parts[1] = RoutePatternFactory.ParameterPart("E");
- Array.Resize(ref parts, 2);
-
- // Assert
- Assert.Equal(3, actual.Parts.Count);
- Assert.Same(paramPartB, actual.Parts[0]);
- Assert.Same(paramPartC, actual.Parts[1]);
- Assert.Same(paramPartD, actual.Parts[2]);
- }
+ [Fact]
+ public void Segment_ArrayOfParts()
+ {
+ // Arrange
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var parts = new[] { paramPartB, paramPartC, paramPartD };
+
+ // Act
+ var actual = RoutePatternFactory.Segment(parts);
+ parts[1] = RoutePatternFactory.ParameterPart("E");
+ Array.Resize(ref parts, 2);
+
+ // Assert
+ Assert.Equal(3, actual.Parts.Count);
+ Assert.Same(paramPartB, actual.Parts[0]);
+ Assert.Same(paramPartC, actual.Parts[1]);
+ Assert.Same(paramPartD, actual.Parts[2]);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternMatcherTest.cs b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternMatcherTest.cs
index f6e4940b1b..e035ea7e68 100644
--- a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternMatcherTest.cs
+++ b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternMatcherTest.cs
@@ -2,1129 +2,1128 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
-using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RoutePatternMatcherTest
{
- public class RoutePatternMatcherTest
+ [Fact]
+ public void TryMatch_Success()
{
- [Fact]
- public void TryMatch_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}");
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}");
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank/DoAction/123", values);
+ // Act
+ var match = matcher.TryMatch("/Bank/DoAction/123", values);
- // Assert
- Assert.True(match);
- Assert.Equal("Bank", values["controller"]);
- Assert.Equal("DoAction", values["action"]);
- Assert.Equal("123", values["id"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("Bank", values["controller"]);
+ Assert.Equal("DoAction", values["action"]);
+ Assert.Equal("123", values["id"]);
+ }
- [Fact]
- public void TryMatch_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}");
+ [Fact]
+ public void TryMatch_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}");
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank/DoAction", values);
+ // Act
+ var match = matcher.TryMatch("/Bank/DoAction", values);
- // Assert
- Assert.False(match);
- }
+ // Assert
+ Assert.False(match);
+ }
- [Fact]
- public void TryMatch_WithDefaults_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
+ [Fact]
+ public void TryMatch_WithDefaults_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank/DoAction", values);
+ // Act
+ var match = matcher.TryMatch("/Bank/DoAction", values);
- // Assert
- Assert.True(match);
- Assert.Equal("Bank", values["controller"]);
- Assert.Equal("DoAction", values["action"]);
- Assert.Equal("default id", values["id"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("Bank", values["controller"]);
+ Assert.Equal("DoAction", values["action"]);
+ Assert.Equal("default id", values["id"]);
+ }
- [Fact]
- public void TryMatch_WithDefaults_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
+ [Fact]
+ public void TryMatch_WithDefaults_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank", values);
+ // Act
+ var match = matcher.TryMatch("/Bank", values);
- // Assert
- Assert.False(match);
- }
+ // Assert
+ Assert.False(match);
+ }
- [Fact]
- public void TryMatch_WithLiterals_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
+ [Fact]
+ public void TryMatch_WithLiterals_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/moo/111/bar/222", values);
+ // Act
+ var match = matcher.TryMatch("/moo/111/bar/222", values);
- // Assert
- Assert.True(match);
- Assert.Equal("111", values["p1"]);
- Assert.Equal("222", values["p2"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("111", values["p1"]);
+ Assert.Equal("222", values["p2"]);
+ }
- [Fact]
- public void TryMatch_RouteWithLiteralsAndDefaults_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
+ [Fact]
+ public void TryMatch_RouteWithLiteralsAndDefaults_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/moo/111/bar/", values);
+ // Act
+ var match = matcher.TryMatch("/moo/111/bar/", values);
- // Assert
- Assert.True(match);
- Assert.Equal("111", values["p1"]);
- Assert.Equal("default p2", values["p2"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("111", values["p1"]);
+ Assert.Equal("default p2", values["p2"]);
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "/123-456-7890")] // ssn
- [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "/asd@assds.com")] // email
- [InlineData(@"{p1:regex(([}}])\w+)}", "/}sda")] // Not balanced }
- [InlineData(@"{p1:regex(([{{)])\w+)}", "/})sda")] // Not balanced {
- public void TryMatch_RegularExpressionConstraint_Valid(
- string template,
- string path)
- {
- // Arrange
- var matcher = CreateMatcher(template);
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "/123-456-7890")] // ssn
+ [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "/asd@assds.com")] // email
+ [InlineData(@"{p1:regex(([}}])\w+)}", "/}sda")] // Not balanced }
+ [InlineData(@"{p1:regex(([{{)])\w+)}", "/})sda")] // Not balanced {
+ public void TryMatch_RegularExpressionConstraint_Valid(
+ string template,
+ string path)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch(path, values);
+ // Act
+ var match = matcher.TryMatch(path, values);
- // Assert
- Assert.True(match);
- }
+ // Assert
+ Assert.True(match);
+ }
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", true, "foo", "bar")]
- [InlineData("moo/{p1?}", "/moo/foo", true, "foo", null)]
- [InlineData("moo/{p1?}", "/moo", true, null, null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo", true, "foo", null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", true, "foo.", "bar")]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", true, "foo.moo", "bar")]
- [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", true, "foo", "bar")]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", true, "moo", "bar")]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", true, "moo", null)]
- [InlineData("moo/.{p2?}", "/moo/.foo", true, null, "foo")]
- [InlineData("moo/.{p2?}", "/moo", false, null, null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/....", true, "..", ".")]
- [InlineData("moo/{p1}.{p2?}", "/moo/.bar", true, ".bar", null)]
- public void TryMatch_OptionalParameter_FollowedByPeriod_Valid(
- string template,
- string path,
- bool expectedMatch,
- string p1,
- string p2)
- {
- // Arrange
- var matcher = CreateMatcher(template);
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", true, "foo", "bar")]
+ [InlineData("moo/{p1?}", "/moo/foo", true, "foo", null)]
+ [InlineData("moo/{p1?}", "/moo", true, null, null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo", true, "foo", null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", true, "foo.", "bar")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", true, "foo.moo", "bar")]
+ [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", true, "foo", "bar")]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", true, "moo", "bar")]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", true, "moo", null)]
+ [InlineData("moo/.{p2?}", "/moo/.foo", true, null, "foo")]
+ [InlineData("moo/.{p2?}", "/moo", false, null, null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/....", true, "..", ".")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.bar", true, ".bar", null)]
+ public void TryMatch_OptionalParameter_FollowedByPeriod_Valid(
+ string template,
+ string path,
+ bool expectedMatch,
+ string p1,
+ string p2)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch(path, values);
+ // Act
+ var match = matcher.TryMatch(path, values);
- // Assert
- Assert.Equal(expectedMatch, match);
- if (p1 != null)
- {
- Assert.Equal(p1, values["p1"]);
- }
- if (p2 != null)
- {
- Assert.Equal(p2, values["p2"]);
- }
+ // Assert
+ Assert.Equal(expectedMatch, match);
+ if (p1 != null)
+ {
+ Assert.Equal(p1, values["p1"]);
}
-
- [Theory]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
- [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
- [InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", "foo", "bar", "baz")]
- public void TryMatch_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
- string template,
- string path,
- string p1,
- string p2,
- string p3)
+ if (p2 != null)
{
- // Arrange
- var matcher = CreateMatcher(template);
+ Assert.Equal(p2, values["p2"]);
+ }
+ }
- var values = new RouteValueDictionary();
+ [Theory]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
+ [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
+ [InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", "foo", "bar", "baz")]
+ public void TryMatch_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
+ string template,
+ string path,
+ string p1,
+ string p2,
+ string p3)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- // Act
- var match = matcher.TryMatch(path, values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Equal(p1, values["p1"]);
+ // Act
+ var match = matcher.TryMatch(path, values);
- if (p2 != null)
- {
- Assert.Equal(p2, values["p2"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal(p1, values["p1"]);
- if (p3 != null)
- {
- Assert.Equal(p3, values["p3"]);
- }
+ if (p2 != null)
+ {
+ Assert.Equal(p2, values["p2"]);
}
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
- [InlineData("moo/{p1}.{p2?}", "/moo/.")]
- [InlineData("moo/{p1}.{p2}", "/foo.")]
- [InlineData("moo/{p1}.{p2}", "/foo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
- [InlineData("moo/.{p2?}", "/moo/.")]
- [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
- public void TryMatch_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
+ if (p3 != null)
{
- // Arrange
- var matcher = CreateMatcher(template);
+ Assert.Equal(p3, values["p3"]);
+ }
+ }
- var values = new RouteValueDictionary();
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.")]
+ [InlineData("moo/{p1}.{p2}", "/foo.")]
+ [InlineData("moo/{p1}.{p2}", "/foo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
+ [InlineData("moo/.{p2?}", "/moo/.")]
+ [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
+ public void TryMatch_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- // Act
- var match = matcher.TryMatch(path, values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch(path, values);
- [Fact]
- public void TryMatch_RouteWithOnlyLiterals_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bar");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithOnlyLiterals_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bar");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithOnlyLiterals_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bars");
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithOnlyLiterals_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bars");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithExtraSeparators_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bar");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithExtraSeparators_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bar");
- // Act
- var match = matcher.TryMatch("/moo/bar/", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar/", values);
- [Fact]
- public void TryMatch_UrlWithExtraSeparators_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bar/");
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_UrlWithExtraSeparators_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bar/");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithParametersAndExtraSeparators_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{p2}/");
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithParametersAndExtraSeparators_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{p2}/");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Equal("moo", values["p1"]);
- Assert.Equal("bar", values["p2"]);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithDifferentLiterals_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{p2}/baz");
+ // Assert
+ Assert.True(match);
+ Assert.Equal("moo", values["p1"]);
+ Assert.Equal("bar", values["p2"]);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithDifferentLiterals_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{p2}/baz");
- // Act
- var match = matcher.TryMatch("/moo/bar/boo", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar/boo", values);
- [Fact]
- public void TryMatch_LongerUrl_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_LongerUrl_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_SimpleFilename_Success()
- {
- // Arrange
- var matcher = CreateMatcher("DEFAULT.ASPX");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_SimpleFilename_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("DEFAULT.ASPX");
- // Act
- var match = matcher.TryMatch("/default.aspx", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- }
+ // Act
+ var match = matcher.TryMatch("/default.aspx", values);
- [Theory]
- [InlineData("{prefix}x{suffix}", "/xxxxxxxxxx")]
- [InlineData("{prefix}xyz{suffix}", "/xxxxyzxyzxxxxxxyz")]
- [InlineData("{prefix}xyz{suffix}", "/abcxxxxyzxyzxxxxxxyzxx")]
- [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz")]
- [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz1")]
- [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyz")]
- [InlineData("{prefix}aa{suffix}", "/aaaaa")]
- [InlineData("{prefix}aaa{suffix}", "/aaaaa")]
- public void TryMatch_RouteWithComplexSegment_Success(string template, string path)
- {
- var matcher = CreateMatcher(template);
+ // Assert
+ Assert.True(match);
+ }
- var values = new RouteValueDictionary();
+ [Theory]
+ [InlineData("{prefix}x{suffix}", "/xxxxxxxxxx")]
+ [InlineData("{prefix}xyz{suffix}", "/xxxxyzxyzxxxxxxyz")]
+ [InlineData("{prefix}xyz{suffix}", "/abcxxxxyzxyzxxxxxxyzxx")]
+ [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz")]
+ [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz1")]
+ [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyz")]
+ [InlineData("{prefix}aa{suffix}", "/aaaaa")]
+ [InlineData("{prefix}aaa{suffix}", "/aaaaa")]
+ public void TryMatch_RouteWithComplexSegment_Success(string template, string path)
+ {
+ var matcher = CreateMatcher(template);
- // Act
- var match = matcher.TryMatch(path, values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- }
+ // Act
+ var match = matcher.TryMatch(path, values);
- [Fact]
- public void TryMatch_RouteWithExtraDefaultValues_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" });
+ // Assert
+ Assert.True(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithExtraDefaultValues_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" });
- // Act
- var match = matcher.TryMatch("/v1", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Equal<int>(3, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Null(values["p2"]);
- Assert.Equal("bar", values["foo"]);
- }
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- [Fact]
- public void TryMatch_PrettyRouteWithExtraDefaultValues_Success()
- {
- // Arrange
- var matcher = CreateMatcher(
- "date/{y}/{m}/{d}",
- new { controller = "blog", action = "showpost", m = (string)null, d = (string)null });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(3, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Null(values["p2"]);
+ Assert.Equal("bar", values["foo"]);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_PrettyRouteWithExtraDefaultValues_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher(
+ "date/{y}/{m}/{d}",
+ new { controller = "blog", action = "showpost", m = (string)null, d = (string)null });
+
+ var values = new RouteValueDictionary();
+
+ // Act
+ var match = matcher.TryMatch("/date/2007/08", values);
+
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(5, values.Count);
+ Assert.Equal("blog", values["controller"]);
+ Assert.Equal("showpost", values["action"]);
+ Assert.Equal("2007", values["y"]);
+ Assert.Equal("08", values["m"]);
+ Assert.Null(values["d"]);
+ }
- // Act
- var match = matcher.TryMatch("/date/2007/08", values);
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnBothEndsMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}",
+ "/language/en-US",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- // Assert
- Assert.True(match);
- Assert.Equal<int>(5, values.Count);
- Assert.Equal("blog", values["controller"]);
- Assert.Equal("showpost", values["action"]);
- Assert.Equal("2007", values["y"]);
- Assert.Equal("08", values["m"]);
- Assert.Null(values["d"]);
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnLeftEndMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}a",
+ "/language/en-USa",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnBothEndsMatches()
- {
- RunTest(
- "language/{lang}-{region}",
- "/language/en-US",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnRightEndMatches()
+ {
+ RunTest(
+ "language/a{lang}-{region}",
+ "/language/aen-US",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnLeftEndMatches()
- {
- RunTest(
- "language/{lang}-{region}a",
- "/language/en-USa",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnNeitherEndMatches()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ "/language/aen-USa",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnRightEndMatches()
- {
- RunTest(
- "language/a{lang}-{region}",
- "/language/aen-US",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ "/language/a-USa",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnNeitherEndMatches()
- {
- RunTest(
- "language/a{lang}-{region}a",
- "/language/aen-USa",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ "/language/aen-a",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch()
- {
- RunTest(
- "language/a{lang}-{region}a",
- "/language/a-USa",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsMatches()
+ {
+ RunTest(
+ "language/{lang}",
+ "/language/en",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
- {
- RunTest(
- "language/a{lang}-{region}a",
- "/language/aen-a",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsTrailingSlashDoesNotMatch()
+ {
+ RunTest(
+ "language/{lang}",
+ "/language/",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsMatches()
- {
- RunTest(
- "language/{lang}",
- "/language/en",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsDoesNotMatch()
+ {
+ RunTest(
+ "language/{lang}",
+ "/language",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsTrailingSlashDoesNotMatch()
- {
- RunTest(
- "language/{lang}",
- "/language/",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnLeftEndMatches()
+ {
+ RunTest(
+ "language/{lang}-",
+ "/language/en-",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsDoesNotMatch()
- {
- RunTest(
- "language/{lang}",
- "/language",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnRightEndMatches()
+ {
+ RunTest(
+ "language/a{lang}",
+ "/language/aen",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnLeftEndMatches()
- {
- RunTest(
- "language/{lang}-",
- "/language/en-",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnNeitherEndMatches()
+ {
+ RunTest(
+ "language/a{lang}a",
+ "/language/aena",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnRightEndMatches()
- {
- RunTest(
- "language/a{lang}",
- "/language/aen",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentStandamatchMvcRouteMatches()
+ {
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ "/home.mvc/index",
+ new RouteValueDictionary(new { action = "Index", id = (string)null }),
+ new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnNeitherEndMatches()
- {
- RunTest(
- "language/a{lang}a",
- "/language/aena",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}",
+ "/language/-",
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ null);
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentStandamatchMvcRouteMatches()
- {
- RunTest(
- "{controller}.mvc/{action}/{id}",
- "/home.mvc/index",
- new RouteValueDictionary(new { action = "Index", id = (string)null }),
- new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithMultiSegmentWithRepeatedDots()
+ {
+ RunTest(
+ "{Controller}..mvc/{id}/{Param1}",
+ "/Home..mvc/123/p1",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
- {
- RunTest(
- "language/{lang}-{region}",
- "/language/-",
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- null);
- }
+ [Fact]
+ public void TryMatch_WithUrlWithTwoRepeatedDots()
+ {
+ RunTest(
+ "{Controller}.mvc/../{action}",
+ "/Home.mvc/../index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithMultiSegmentWithRepeatedDots()
- {
- RunTest(
- "{Controller}..mvc/{id}/{Param1}",
- "/Home..mvc/123/p1",
- null,
- new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithThreeRepeatedDots()
+ {
+ RunTest(
+ "{Controller}.mvc/.../{action}",
+ "/Home.mvc/.../index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithTwoRepeatedDots()
- {
- RunTest(
- "{Controller}.mvc/../{action}",
- "/Home.mvc/../index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithManyRepeatedDots()
+ {
+ RunTest(
+ "{Controller}.mvc/../../../{action}",
+ "/Home.mvc/../../../index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithThreeRepeatedDots()
- {
- RunTest(
- "{Controller}.mvc/.../{action}",
- "/Home.mvc/.../index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithExclamationPoint()
+ {
+ RunTest(
+ "{Controller}.mvc!/{action}",
+ "/Home.mvc!/index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithManyRepeatedDots()
- {
- RunTest(
- "{Controller}.mvc/../../../{action}",
- "/Home.mvc/../../../index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithStartingDotDotSlash()
+ {
+ RunTest(
+ "../{Controller}.mvc",
+ "/../Home.mvc",
+ null,
+ new RouteValueDictionary(new { Controller = "Home" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithExclamationPoint()
- {
- RunTest(
- "{Controller}.mvc!/{action}",
- "/Home.mvc!/index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithStartingBackslash()
+ {
+ RunTest(
+ @"\{Controller}.mvc",
+ @"/\Home.mvc",
+ null,
+ new RouteValueDictionary(new { Controller = "Home" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithStartingDotDotSlash()
- {
- RunTest(
- "../{Controller}.mvc",
- "/../Home.mvc",
- null,
- new RouteValueDictionary(new { Controller = "Home" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithBackslashSeparators()
+ {
+ RunTest(
+ @"{Controller}.mvc\{id}\{Param1}",
+ @"/Home.mvc\123\p1",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithStartingBackslash()
- {
- RunTest(
- @"\{Controller}.mvc",
- @"/\Home.mvc",
- null,
- new RouteValueDictionary(new { Controller = "Home" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithParenthesesLiterals()
+ {
+ RunTest(
+ @"(Controller).mvc",
+ @"/(Controller).mvc",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_WithUrlWithBackslashSeparators()
- {
- RunTest(
- @"{Controller}.mvc\{id}\{Param1}",
- @"/Home.mvc\123\p1",
- null,
- new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithTrailingSlashSpace()
+ {
+ RunTest(
+ @"Controller.mvc/ ",
+ @"/Controller.mvc/ ",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_WithUrlWithParenthesesLiterals()
- {
- RunTest(
- @"(Controller).mvc",
- @"/(Controller).mvc",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_WithUrlWithTrailingSpace()
+ {
+ RunTest(
+ @"Controller.mvc ",
+ @"/Controller.mvc ",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_WithUrlWithTrailingSlashSpace()
- {
- RunTest(
- @"Controller.mvc/ ",
- @"/Controller.mvc/ ",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_WithCatchAllCapturesDots()
+ {
+ // DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "."
+ RunTest(
+ "Home/ShowPilot/{missionId}/{*name}",
+ "/Home/ShowPilot/777/12345./foobar",
+ new RouteValueDictionary(new
+ {
+ controller = "Home",
+ action = "ShowPilot",
+ missionId = (string)null,
+ name = (string)null
+ }),
+ new RouteValueDictionary(new { controller = "Home", action = "ShowPilot", missionId = "777", name = "12345./foobar" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithTrailingSpace()
- {
- RunTest(
- @"Controller.mvc ",
- @"/Controller.mvc ",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesMultiplePathSegments()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- [Fact]
- public void TryMatch_WithCatchAllCapturesDots()
- {
- // DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "."
- RunTest(
- "Home/ShowPilot/{missionId}/{*name}",
- "/Home/ShowPilot/777/12345./foobar",
- new RouteValueDictionary(new
- {
- controller = "Home",
- action = "ShowPilot",
- missionId = (string)null,
- name = (string)null
- }),
- new RouteValueDictionary(new { controller = "Home", action = "ShowPilot", missionId = "777", name = "12345./foobar" }));
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesMultiplePathSegments()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1/v2/v3", values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("v2/v3", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1/v2/v3", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesTrailingSlash()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("v2/v3", values["p2"]);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesTrailingSlash()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1/", values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Null(values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1/", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesEmptyContent()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Null(values["p2"]);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesEmptyContent()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Null(values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesEmptyContent_DoesNotReplaceExistingRouteValue()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Null(values["p2"]);
- }
+ var values = new RouteValueDictionary(new { p2 = "hello" });
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesEmptyContent_DoesNotReplaceExistingRouteValue()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- var values = new RouteValueDictionary(new { p2 = "hello" });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("hello", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_UsesDefaultValueForEmptyContent()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("hello", values["p2"]);
- }
+ var values = new RouteValueDictionary(new { p2 = "overridden" });
- [Fact]
- public void TryMatch_RouteWithCatchAll_UsesDefaultValueForEmptyContent()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- var values = new RouteValueDictionary(new { p2 = "overridden" });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("catchall", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_IgnoresDefaultValueForNonEmptyContent()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("catchall", values["p2"]);
- }
+ var values = new RouteValueDictionary(new { p2 = "overridden" });
- [Fact]
- public void TryMatch_RouteWithCatchAll_IgnoresDefaultValueForNonEmptyContent()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
+ // Act
+ var match = matcher.TryMatch("/v1/hello/whatever", values);
- var values = new RouteValueDictionary(new { p2 = "overridden" });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("hello/whatever", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1/hello/whatever", values);
+ [Fact]
+ public void TryMatch_DoesNotMatchOnlyLeftLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/fooBAR",
+ null,
+ null);
+ }
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("hello/whatever", values["p2"]);
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchOnlyRightLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/BARfoo",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchOnlyLeftLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/fooBAR",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchMiddleLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/BARfooBAR",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchOnlyRightLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/BARfoo",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_DoesMatchesExactLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/foo",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_DoesNotMatchMiddleLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/BARfooBAR",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithWeimatchParameterNames()
+ {
+ RunTest(
+ "foo/{ }/{.!$%}/{dynamic.data}/{op.tional}",
+ "/foo/space/weimatch/omatcherid",
+ new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } },
+ new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weimatch" }, { "dynamic.data", "omatcherid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } });
+ }
- [Fact]
- public void TryMatch_DoesMatchesExactLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/foo",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchRouteWithLiteralSeparatomatchefaultsButNoValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ null);
+ }
- [Fact]
- public void TryMatch_WithWeimatchParameterNames()
- {
- RunTest(
- "foo/{ }/{.!$%}/{dynamic.data}/{op.tional}",
- "/foo/space/weimatch/omatcherid",
- new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } },
- new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weimatch" }, { "dynamic.data", "omatcherid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } });
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndLeftValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo/xx-",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchRouteWithLiteralSeparatomatchefaultsButNoValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- null);
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndRightValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo/-yy",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndLeftValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo/xx-",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- null);
- }
+ [Fact]
+ public void TryMatch_MatchesRouteWithLiteralSeparatomatchefaultsAndValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo/xx-yy",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
+ }
- [Fact]
- public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndRightValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo/-yy",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- null);
- }
+ [Fact]
+ public void TryMatch_SetsOptionalParameter()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action?}");
+ var url = "/Home/Index";
- [Fact]
- public void TryMatch_MatchesRouteWithLiteralSeparatomatchefaultsAndValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo/xx-yy",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_SetsOptionalParameter()
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action?}");
- var url = "/Home/Index";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal(2, values.Count);
+ Assert.Equal("Home", values["controller"]);
+ Assert.Equal("Index", values["action"]);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch_DoesNotSetOptionalParameter()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action?}");
+ var url = "/Home";
- // Assert
- Assert.True(match);
- Assert.Equal(2, values.Count);
- Assert.Equal("Home", values["controller"]);
- Assert.Equal("Index", values["action"]);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_DoesNotSetOptionalParameter()
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action?}");
- var url = "/Home";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Single(values);
+ Assert.Equal("Home", values["controller"]);
+ Assert.False(values.ContainsKey("action"));
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch_DoesNotSetOptionalParameter_EmptyString()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller?}");
+ var url = "";
- // Assert
- Assert.True(match);
- Assert.Single(values);
- Assert.Equal("Home", values["controller"]);
- Assert.False(values.ContainsKey("action"));
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_DoesNotSetOptionalParameter_EmptyString()
- {
- // Arrange
- var route = CreateMatcher("{controller?}");
- var url = "";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ Assert.False(values.ContainsKey("controller"));
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch__EmptyRouteWith_EmptyString()
+ {
+ // Arrange
+ var route = CreateMatcher("");
+ var url = "";
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- Assert.False(values.ContainsKey("controller"));
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch__EmptyRouteWith_EmptyString()
- {
- // Arrange
- var route = CreateMatcher("");
- var url = "";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch_MultipleOptionalParameters()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action?}/{id?}");
+ var url = "/Home/Index";
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_MultipleOptionalParameters()
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action?}/{id?}");
- var url = "/Home/Index";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal(2, values.Count);
+ Assert.Equal("Home", values["controller"]);
+ Assert.Equal("Index", values["action"]);
+ Assert.False(values.ContainsKey("id"));
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("///")]
+ [InlineData("/a//")]
+ [InlineData("/a/b//")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public void TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller?}/{action?}/{id?}");
- // Assert
- Assert.True(match);
- Assert.Equal(2, values.Count);
- Assert.Equal("Home", values["controller"]);
- Assert.Equal("Index", values["action"]);
- Assert.False(values.ContainsKey("id"));
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("///")]
- [InlineData("/a//")]
- [InlineData("/a/b//")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public void TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller?}/{action?}/{id?}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.False(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("")]
+ [InlineData("/")]
+ [InlineData("/a")]
+ [InlineData("/a/")]
+ [InlineData("/a/b")]
+ [InlineData("/a/b/")]
+ [InlineData("/a/b/c")]
+ [InlineData("/a/b/c/")]
+ public void TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller?}/{action?}/{id?}");
- // Assert
- Assert.False(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("")]
- [InlineData("/")]
- [InlineData("/a")]
- [InlineData("/a/")]
- [InlineData("/a/b")]
- [InlineData("/a/b/")]
- [InlineData("/a/b/c")]
- [InlineData("/a/b/c/")]
- public void TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller?}/{action?}/{id?}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("///")]
+ [InlineData("////")]
+ [InlineData("/a//")]
+ [InlineData("/a///")]
+ [InlineData("//b/")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public void TryMatch_MultipleParameters_WithEmptyValues(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action}/{id}");
- // Assert
- Assert.True(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("///")]
- [InlineData("////")]
- [InlineData("/a//")]
- [InlineData("/a///")]
- [InlineData("//b/")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public void TryMatch_MultipleParameters_WithEmptyValues(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action}/{id}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.False(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("/a/b/c//")]
+ [InlineData("/a/b/c/////")]
+ public void TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action}/{*id}");
- // Assert
- Assert.False(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("/a/b/c//")]
- [InlineData("/a/b/c/////")]
- public void TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action}/{*id}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("/a/b//")]
+ [InlineData("/a/b///c")]
+ public void TryMatch_CatchAllParameters_WithEmptyValues(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action}/{*id}");
- // Assert
- Assert.True(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("/a/b//")]
- [InlineData("/a/b///c")]
- public void TryMatch_CatchAllParameters_WithEmptyValues(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action}/{*id}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.False(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ private RoutePatternMatcher CreateMatcher(string template, object defaults = null)
+ {
+ return new RoutePatternMatcher(
+ RoutePatternParser.Parse(template),
+ new RouteValueDictionary(defaults));
+ }
- // Assert
- Assert.False(match);
- }
+ private static void RunTest(
+ string template,
+ string path,
+ RouteValueDictionary defaults,
+ IDictionary<string, object> expected)
+ {
+ // Arrange
+ var matcher = new RoutePatternMatcher(
+ RoutePatternParser.Parse(template),
+ defaults ?? new RouteValueDictionary());
- private RoutePatternMatcher CreateMatcher(string template, object defaults = null)
+ var values = new RouteValueDictionary();
+
+ // Act
+ var match = matcher.TryMatch(new PathString(path), values);
+
+ // Assert
+ if (expected == null)
{
- return new RoutePatternMatcher(
- RoutePatternParser.Parse(template),
- new RouteValueDictionary(defaults));
+ Assert.False(match);
}
-
- private static void RunTest(
- string template,
- string path,
- RouteValueDictionary defaults,
- IDictionary<string, object> expected)
+ else
{
- // Arrange
- var matcher = new RoutePatternMatcher(
- RoutePatternParser.Parse(template),
- defaults ?? new RouteValueDictionary());
-
- var values = new RouteValueDictionary();
-
- // Act
- var match = matcher.TryMatch(new PathString(path), values);
-
- // Assert
- if (expected == null)
- {
- Assert.False(match);
- }
- else
+ Assert.True(match);
+ Assert.Equal(expected.Count, values.Count);
+ foreach (string key in values.Keys)
{
- Assert.True(match);
- Assert.Equal(expected.Count, values.Count);
- foreach (string key in values.Keys)
- {
- Assert.Equal(expected[key], values[key]);
- }
+ Assert.Equal(expected[key], values[key]);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternParserTest.cs b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternParserTest.cs
index 42b0310eda..900492fa76 100644
--- a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternParserTest.cs
+++ b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternParserTest.cs
@@ -9,755 +9,754 @@ using Microsoft.AspNetCore.Testing;
using Xunit;
using static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory;
-namespace Microsoft.AspNetCore.Routing.Patterns
+namespace Microsoft.AspNetCore.Routing.Patterns;
+
+public class RoutePatternParameterParserTest
{
- public class RoutePatternParameterParserTest
+ [Fact]
+ public void Parse_SingleLiteral()
{
- [Fact]
- public void Parse_SingleLiteral()
- {
- // Arrange
- var template = "cool";
+ // Arrange
+ var template = "cool";
- var expected = Pattern(
- template,
- Segment(LiteralPart("cool")));
+ var expected = Pattern(
+ template,
+ Segment(LiteralPart("cool")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_SingleParameter()
- {
- // Arrange
- var template = "{p}";
+ [Fact]
+ public void Parse_SingleParameter()
+ {
+ // Arrange
+ var template = "{p}";
- var expected = Pattern(template, Segment(ParameterPart("p")));
+ var expected = Pattern(template, Segment(ParameterPart("p")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_OptionalParameter()
- {
- // Arrange
- var template = "{p?}";
+ [Fact]
+ public void Parse_OptionalParameter()
+ {
+ // Arrange
+ var template = "{p?}";
- var expected = Pattern(template, Segment(ParameterPart("p", null, RoutePatternParameterKind.Optional)));
+ var expected = Pattern(template, Segment(ParameterPart("p", null, RoutePatternParameterKind.Optional)));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_MultipleLiterals()
- {
- // Arrange
- var template = "cool/awesome/super";
+ [Fact]
+ public void Parse_MultipleLiterals()
+ {
+ // Arrange
+ var template = "cool/awesome/super";
- var expected = Pattern(
- template,
- Segment(LiteralPart("cool")),
- Segment(LiteralPart("awesome")),
- Segment(LiteralPart("super")));
+ var expected = Pattern(
+ template,
+ Segment(LiteralPart("cool")),
+ Segment(LiteralPart("awesome")),
+ Segment(LiteralPart("super")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_MultipleParameters()
- {
- // Arrange
- var template = "{p1}/{p2}/{*p3}";
+ [Fact]
+ public void Parse_MultipleParameters()
+ {
+ // Arrange
+ var template = "{p1}/{p2}/{*p3}";
- var expected = Pattern(
- template,
- Segment(ParameterPart("p1")),
- Segment(ParameterPart("p2")),
- Segment(ParameterPart("p3", null, RoutePatternParameterKind.CatchAll)));
+ var expected = Pattern(
+ template,
+ Segment(ParameterPart("p1")),
+ Segment(ParameterPart("p2")),
+ Segment(ParameterPart("p3", null, RoutePatternParameterKind.CatchAll)));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_LP()
- {
- // Arrange
- var template = "cool-{p1}";
+ [Fact]
+ public void Parse_ComplexSegment_LP()
+ {
+ // Arrange
+ var template = "cool-{p1}";
- var expected = Pattern(
- template,
- Segment(
- LiteralPart("cool-"),
- ParameterPart("p1")));
+ var expected = Pattern(
+ template,
+ Segment(
+ LiteralPart("cool-"),
+ ParameterPart("p1")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_PL()
- {
- // Arrange
- var template = "{p1}-cool";
+ [Fact]
+ public void Parse_ComplexSegment_PL()
+ {
+ // Arrange
+ var template = "{p1}-cool";
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- LiteralPart("-cool")));
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ LiteralPart("-cool")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_PLP()
- {
- // Arrange
- var template = "{p1}-cool-{p2}";
+ [Fact]
+ public void Parse_ComplexSegment_PLP()
+ {
+ // Arrange
+ var template = "{p1}-cool-{p2}";
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- LiteralPart("-cool-"),
- ParameterPart("p2")));
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ LiteralPart("-cool-"),
+ ParameterPart("p2")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_LPL()
- {
- // Arrange
- var template = "cool-{p1}-awesome";
+ [Fact]
+ public void Parse_ComplexSegment_LPL()
+ {
+ // Arrange
+ var template = "cool-{p1}-awesome";
- var expected = Pattern(
- template,
- Segment(
- LiteralPart("cool-"),
- ParameterPart("p1"),
- LiteralPart("-awesome")));
+ var expected = Pattern(
+ template,
+ Segment(
+ LiteralPart("cool-"),
+ ParameterPart("p1"),
+ LiteralPart("-awesome")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
- {
- // Arrange
- var template = "{p1}.{p2?}";
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
+ {
+ // Arrange
+ var template = "{p1}.{p2?}";
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- SeparatorPart("."),
- ParameterPart("p2", null, RoutePatternParameterKind.Optional)));
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ SeparatorPart("."),
+ ParameterPart("p2", null, RoutePatternParameterKind.Optional)));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_ParametersFollowingPeriod()
- {
- // Arrange
- var template = "{p1}.{p2}";
+ [Fact]
+ public void Parse_ComplexSegment_ParametersFollowingPeriod()
+ {
+ // Arrange
+ var template = "{p1}.{p2}";
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- LiteralPart("."),
- ParameterPart("p2")));
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ LiteralPart("."),
+ ParameterPart("p2")));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
- {
- // Arrange
- var template = "{p1}.{p2}.{p3?}";
-
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- LiteralPart("."),
- ParameterPart("p2"),
- SeparatorPart("."),
- ParameterPart("p3", null, RoutePatternParameterKind.Optional)));
-
- // Act
- var actual = RoutePatternParser.Parse(template);
-
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
+ {
+ // Arrange
+ var template = "{p1}.{p2}.{p3?}";
+
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ LiteralPart("."),
+ ParameterPart("p2"),
+ SeparatorPart("."),
+ ParameterPart("p3", null, RoutePatternParameterKind.Optional)));
+
+ // Act
+ var actual = RoutePatternParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_ThreeParametersSeparatedByPeriod()
- {
- // Arrange
- var template = "{p1}.{p2}.{p3}";
-
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- LiteralPart("."),
- ParameterPart("p2"),
- LiteralPart("."),
- ParameterPart("p3")));
-
- // Act
- var actual = RoutePatternParser.Parse(template);
-
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_ThreeParametersSeparatedByPeriod()
+ {
+ // Arrange
+ var template = "{p1}.{p2}.{p3}";
+
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ LiteralPart("."),
+ ParameterPart("p2"),
+ LiteralPart("."),
+ ParameterPart("p3")));
+
+ // Act
+ var actual = RoutePatternParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
- {
- // Arrange
- var template = "{p1}.{p2?}/{p3}";
-
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1"),
- SeparatorPart("."),
- ParameterPart("p2", null, RoutePatternParameterKind.Optional)),
- Segment(
- ParameterPart("p3")));
-
- // Act
- var actual = RoutePatternParser.Parse(template);
-
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
+ {
+ // Arrange
+ var template = "{p1}.{p2?}/{p3}";
+
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1"),
+ SeparatorPart("."),
+ ParameterPart("p2", null, RoutePatternParameterKind.Optional)),
+ Segment(
+ ParameterPart("p3")));
+
+ // Act
+ var actual = RoutePatternParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
- {
- // Arrange
- var template = "{p1}/{p2}.{p3?}";
-
- var expected = Pattern(
- template,
- Segment(
- ParameterPart("p1")),
- Segment(
- ParameterPart("p2"),
- SeparatorPart("."),
- ParameterPart("p3", null, RoutePatternParameterKind.Optional)));
-
- // Act
- var actual = RoutePatternParser.Parse(template);
-
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
+ {
+ // Arrange
+ var template = "{p1}/{p2}.{p3?}";
+
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart("p1")),
+ Segment(
+ ParameterPart("p2"),
+ SeparatorPart("."),
+ ParameterPart("p3", null, RoutePatternParameterKind.Optional)));
+
+ // Act
+ var actual = RoutePatternParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
- {
- // Arrange
- var template = "{p2}/.{p3?}";
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
+ {
+ // Arrange
+ var template = "{p2}/.{p3?}";
- var expected = Pattern(
- template,
- Segment(ParameterPart("p2")),
- Segment(
- SeparatorPart("."),
- ParameterPart("p3", null, RoutePatternParameterKind.Optional)));
+ var expected = Pattern(
+ template,
+ Segment(ParameterPart("p2")),
+ Segment(
+ SeparatorPart("."),
+ ParameterPart("p3", null, RoutePatternParameterKind.Optional)));
- // Act
- var actual = RoutePatternParser.Parse(template);
+ // Act
+ var actual = RoutePatternParser.Parse(template);
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", @"regex(^\d{3}-\d{3}-\d{4}$)")] // ssn
- [InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)}", @"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")] // date
- [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", @"regex(^\w+\@\w+\.\w+)")] // email
- [InlineData(@"{p1:regex(([}}])\w+)}", @"regex(([}])\w+)")] // Not balanced }
- [InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced {
- public void Parse_RegularExpressions(string template, string constraint)
- {
- // Arrange
- var expected = Pattern(
- template,
- Segment(
- ParameterPart(
- "p1",
- null,
- RoutePatternParameterKind.Standard,
- Constraint(constraint))));
-
- // Act
- var actual = RoutePatternParser.Parse(template);
-
- // Assert
- Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
- }
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", @"regex(^\d{3}-\d{3}-\d{4}$)")] // ssn
+ [InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)}", @"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")] // date
+ [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", @"regex(^\w+\@\w+\.\w+)")] // email
+ [InlineData(@"{p1:regex(([}}])\w+)}", @"regex(([}])\w+)")] // Not balanced }
+ [InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced {
+ public void Parse_RegularExpressions(string template, string constraint)
+ {
+ // Arrange
+ var expected = Pattern(
+ template,
+ Segment(
+ ParameterPart(
+ "p1",
+ null,
+ RoutePatternParameterKind.Standard,
+ Constraint(constraint))));
+
+ // Act
+ var actual = RoutePatternParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RoutePattern>(expected, actual, new RoutePatternEqualityComparer());
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}}$)}")] // extra }
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}}")] // extra } at the end
- [InlineData(@"{{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}")] // extra { at the beginning
- [InlineData(@"{p1:regex(([}])\w+}")] // Not escaped }
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}$)}")] // Not escaped }
- [InlineData(@"{p1:regex(abc)")]
- public void Parse_RegularExpressions_Invalid(string template)
- {
- // Act and Assert
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse(template),
- "There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
- "'}' character.");
- }
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}}$)}")] // extra }
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}}")] // extra } at the end
+ [InlineData(@"{{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}")] // extra { at the beginning
+ [InlineData(@"{p1:regex(([}])\w+}")] // Not escaped }
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}$)}")] // Not escaped }
+ [InlineData(@"{p1:regex(abc)")]
+ public void Parse_RegularExpressions_Invalid(string template)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse(template),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
+ "'}' character.");
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{{4}}$)}")] // extra {
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{4}}$)}")] // Not escaped {
- public void Parse_RegularExpressions_Unescaped(string template)
- {
- // Act and Assert
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse(template),
- "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.");
- }
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{{4}}$)}")] // extra {
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{4}}$)}")] // Not escaped {
+ public void Parse_RegularExpressions_Unescaped(string template)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse(template),
+ "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.");
+ }
- [Theory]
- [InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
- [InlineData("{p1?}{p2}", "p1", "{p2}")]
- [InlineData("{p1?}{p2?}", "p1", "{p2?}")]
- [InlineData("{p1}.{p2?})", "p2", ")")]
- [InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
- public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
- string template,
- string parameter,
- string invalid)
- {
- // Act and Assert
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse(template),
- "An optional parameter must be at the end of the segment. In the segment '" + template +
- "', optional parameter '" + parameter + "' is followed by '" + invalid + "'.");
- }
+ [Theory]
+ [InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
+ [InlineData("{p1?}{p2}", "p1", "{p2}")]
+ [InlineData("{p1?}{p2?}", "p1", "{p2?}")]
+ [InlineData("{p1}.{p2?})", "p2", ")")]
+ [InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
+ public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
+ string template,
+ string parameter,
+ string invalid)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse(template),
+ "An optional parameter must be at the end of the segment. In the segment '" + template +
+ "', optional parameter '" + parameter + "' is followed by '" + invalid + "'.");
+ }
- [Theory]
- [InlineData("{p1}-{p2?}", "-")]
- [InlineData("{p1}..{p2?}", "..")]
- [InlineData("..{p2?}", "..")]
- [InlineData("{p1}.abc.{p2?}", ".abc.")]
- [InlineData("{p1}{p2?}", "{p1}")]
- public void Parse_ComplexSegment_OptionalParametersSeparatedByPeriod_Invalid(string template, string parameter)
- {
- // Act and Assert
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse(template),
- "In the segment '" + template + "', the optional parameter 'p2' is preceded by an invalid " +
- "segment '" + parameter + "'. Only a period (.) can precede an optional parameter.");
- }
+ [Theory]
+ [InlineData("{p1}-{p2?}", "-")]
+ [InlineData("{p1}..{p2?}", "..")]
+ [InlineData("..{p2?}", "..")]
+ [InlineData("{p1}.abc.{p2?}", ".abc.")]
+ [InlineData("{p1}{p2?}", "{p1}")]
+ public void Parse_ComplexSegment_OptionalParametersSeparatedByPeriod_Invalid(string template, string parameter)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse(template),
+ "In the segment '" + template + "', the optional parameter 'p2' is preceded by an invalid " +
+ "segment '" + parameter + "'. Only a period (.) can precede an optional parameter.");
+ }
- [Fact]
- public void InvalidTemplate_WithRepeatedParameter()
- {
- var ex = ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{Controller}.mvc/{id}/{controller}"),
- "The route parameter name 'controller' appears more than one time in the route template.");
- }
+ [Fact]
+ public void InvalidTemplate_WithRepeatedParameter()
+ {
+ var ex = ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{Controller}.mvc/{id}/{controller}"),
+ "The route parameter name 'controller' appears more than one time in the route template.");
+ }
- [Theory]
- [InlineData("123{a}abc{")]
- [InlineData("123{a}abc}")]
- [InlineData("xyz}123{a}abc}")]
- [InlineData("{{p1}")]
- [InlineData("{p1}}")]
- [InlineData("p1}}p2{")]
- public void InvalidTemplate_WithMismatchedBraces(string template)
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse(template),
- @"There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character.");
- }
+ [Theory]
+ [InlineData("123{a}abc{")]
+ [InlineData("123{a}abc}")]
+ [InlineData("xyz}123{a}abc}")]
+ [InlineData("{{p1}")]
+ [InlineData("{p1}}")]
+ [InlineData("p1}}p2{")]
+ public void InvalidTemplate_WithMismatchedBraces(string template)
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse(template),
+ @"There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character.");
+ }
- [Fact]
- public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("123{a}abc{*moo}"),
- "A path segment that contains more than one section, such as a literal section or a parameter, " +
- "cannot contain a catch-all parameter.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("123{a}abc{*moo}"),
+ "A path segment that contains more than one section, such as a literal section or a parameter, " +
+ "cannot contain a catch-all parameter.");
+ }
- [Fact]
- public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{*p1}/{*p2}"),
- "A catch-all parameter can only appear as the last segment of the route template.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{*p1}/{*p2}"),
+ "A catch-all parameter can only appear as the last segment of the route template.");
+ }
- [Fact]
- public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{*p1}abc{*p2}"),
- "A path segment that contains more than one section, such as a literal section or a parameter, " +
- "cannot contain a catch-all parameter.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{*p1}abc{*p2}"),
+ "A path segment that contains more than one section, such as a literal section or a parameter, " +
+ "cannot contain a catch-all parameter.");
+ }
- [Fact]
- public void InvalidTemplate_CannotHaveCatchAllWithNoName()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("foo/{*}"),
- "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional," +
- " and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotHaveCatchAllWithNoName()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("foo/{*}"),
+ "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional," +
+ " and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter.");
+ }
- [Theory]
- [InlineData("{a*}", "a*")]
- [InlineData("{*a*}", "a*")]
- [InlineData("{*a*:int}", "a*")]
- [InlineData("{*a*=5}", "a*")]
- [InlineData("{*a*b=5}", "a*b")]
- [InlineData("{p1?}.{p2/}/{p3}", "p2/")]
- [InlineData("{p{{}", "p{")]
- [InlineData("{p}}}", "p}")]
- [InlineData("{p/}", "p/")]
- public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(
- string template,
- string parameterName)
- {
- // Arrange
- var expectedMessage = "The route parameter name '" + parameterName + "' is invalid. Route parameter " +
- "names must be non-empty and cannot contain these characters: '{', '}', '/'. The '?' character " +
- "marks a parameter as optional, and can occur only at the end of the parameter. The '*' character " +
- "marks a parameter as catch-all, and can occur only at the start of the parameter.";
-
- // Act & Assert
- ExceptionAssert.Throws<RoutePatternException>(() => RoutePatternParser.Parse(template), expectedMessage);
- }
+ [Theory]
+ [InlineData("{a*}", "a*")]
+ [InlineData("{*a*}", "a*")]
+ [InlineData("{*a*:int}", "a*")]
+ [InlineData("{*a*=5}", "a*")]
+ [InlineData("{*a*b=5}", "a*b")]
+ [InlineData("{p1?}.{p2/}/{p3}", "p2/")]
+ [InlineData("{p{{}", "p{")]
+ [InlineData("{p}}}", "p}")]
+ [InlineData("{p/}", "p/")]
+ public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(
+ string template,
+ string parameterName)
+ {
+ // Arrange
+ var expectedMessage = "The route parameter name '" + parameterName + "' is invalid. Route parameter " +
+ "names must be non-empty and cannot contain these characters: '{', '}', '/'. The '?' character " +
+ "marks a parameter as optional, and can occur only at the end of the parameter. The '*' character " +
+ "marks a parameter as catch-all, and can occur only at the start of the parameter.";
+
+ // Act & Assert
+ ExceptionAssert.Throws<RoutePatternException>(() => RoutePatternParser.Parse(template), expectedMessage);
+ }
- [Fact]
- public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("foo/{{p1}"),
- "There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("foo/{{p1}"),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character.");
+ }
- [Fact]
- public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("foo/{p1}}"),
- "There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("foo/{p1}}"),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character.");
+ }
- [Fact]
- public void InvalidTemplate_SameParameterTwiceThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{aaa}/{AAA}"),
- "The route parameter name 'AAA' appears more than one time in the route template.");
- }
+ [Fact]
+ public void InvalidTemplate_SameParameterTwiceThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{aaa}/{AAA}"),
+ "The route parameter name 'AAA' appears more than one time in the route template.");
+ }
- [Fact]
- public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{aaa}/{*AAA}"),
- "The route parameter name 'AAA' appears more than one time in the route template.");
- }
+ [Fact]
+ public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{aaa}/{*AAA}"),
+ "The route parameter name 'AAA' appears more than one time in the route template.");
+ }
- [Fact]
- public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{a}/{aa}a}/{z}"),
- "There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character.");
- }
+ [Fact]
+ public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{a}/{aa}a}/{z}"),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character.");
+ }
- [Fact]
- public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{a}/{a{aa}/{z}"),
- "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.");
- }
+ [Fact]
+ public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{a}/{a{aa}/{z}"),
+ "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'.");
+ }
- [Fact]
- public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{a}/{}/{z}"),
- "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
- " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter.");
- }
+ [Fact]
+ public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{a}/{}/{z}"),
+ "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
+ " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter.");
+ }
- [Fact]
- public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{Controller}.mvc/{?}"),
- "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
- " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter.");
- }
+ [Fact]
+ public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{Controller}.mvc/{?}"),
+ "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
+ " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter.");
+ }
- [Fact]
- public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{a}//{z}"),
- "The route template separator character '/' cannot appear consecutively. It must be separated by " +
- "either a parameter or a literal value.");
- }
+ [Fact]
+ public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{a}//{z}"),
+ "The route template separator character '/' cannot appear consecutively. It must be separated by " +
+ "either a parameter or a literal value.");
+ }
- [Fact]
- public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("foo/{p1}/{*p2}/{p3}"),
- "A catch-all parameter can only appear as the last segment of the route template.");
- }
+ [Fact]
+ public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("foo/{p1}/{*p2}/{p3}"),
+ "A catch-all parameter can only appear as the last segment of the route template.");
+ }
- [Fact]
- public void InvalidTemplate_RepeatedParametersThrows()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("foo/aa{p1}{p2}"),
- "A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
- "a literal string.");
- }
+ [Fact]
+ public void InvalidTemplate_RepeatedParametersThrows()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("foo/aa{p1}{p2}"),
+ "A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
+ "a literal string.");
+ }
- [Theory]
- [InlineData("/foo")]
- [InlineData("~/foo")]
- public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routePattern)
- {
- // Arrange & Act
- var pattern = RoutePatternParser.Parse(routePattern);
+ [Theory]
+ [InlineData("/foo")]
+ [InlineData("~/foo")]
+ public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routePattern)
+ {
+ // Arrange & Act
+ var pattern = RoutePatternParser.Parse(routePattern);
- // Assert
- Assert.Equal(routePattern, pattern.RawText);
- }
+ // Assert
+ Assert.Equal(routePattern, pattern.RawText);
+ }
- [Fact]
- public void InvalidTemplate_CannotStartWithTilde()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("~foo"),
- "The route template cannot start with a '~' character unless followed by a '/'.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotStartWithTilde()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("~foo"),
+ "The route template cannot start with a '~' character unless followed by a '/'.");
+ }
- [Fact]
- public void InvalidTemplate_CannotContainQuestionMark()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("foor?bar"),
- "The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character.");
- }
+ [Fact]
+ public void InvalidTemplate_CannotContainQuestionMark()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("foor?bar"),
+ "The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character.");
+ }
- [Fact]
- public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{foor?b}"),
- "The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
- " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter.");
- }
+ [Fact]
+ public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{foor?b}"),
+ "The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
+ " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter.");
+ }
- [Fact]
- public void InvalidTemplate_CatchAllMarkedOptional()
- {
- ExceptionAssert.Throws<RoutePatternException>(
- () => RoutePatternParser.Parse("{a}/{*b?}"),
- "A catch-all parameter cannot be marked optional.");
- }
+ [Fact]
+ public void InvalidTemplate_CatchAllMarkedOptional()
+ {
+ ExceptionAssert.Throws<RoutePatternException>(
+ () => RoutePatternParser.Parse("{a}/{*b?}"),
+ "A catch-all parameter cannot be marked optional.");
+ }
- private class RoutePatternEqualityComparer :
- IEqualityComparer<RoutePattern>,
- IEqualityComparer<RoutePatternParameterPolicyReference>
+ private class RoutePatternEqualityComparer :
+ IEqualityComparer<RoutePattern>,
+ IEqualityComparer<RoutePatternParameterPolicyReference>
+ {
+ public bool Equals(RoutePattern x, RoutePattern y)
{
- public bool Equals(RoutePattern x, RoutePattern y)
+ if (x == null && y == null)
+ {
+ return true;
+ }
+ else if (x == null || y == null)
+ {
+ return false;
+ }
+ else
{
- if (x == null && y == null)
+ if (!string.Equals(x.RawText, y.RawText, StringComparison.Ordinal))
{
- return true;
+ return false;
}
- else if (x == null || y == null)
+
+ if (x.PathSegments.Count != y.PathSegments.Count)
{
return false;
}
- else
- {
- if (!string.Equals(x.RawText, y.RawText, StringComparison.Ordinal))
- {
- return false;
- }
-
- if (x.PathSegments.Count != y.PathSegments.Count)
- {
- return false;
- }
-
- for (var i = 0; i < x.PathSegments.Count; i++)
- {
- if (x.PathSegments[i].Parts.Count != y.PathSegments[i].Parts.Count)
- {
- return false;
- }
-
- for (int j = 0; j < x.PathSegments[i].Parts.Count; j++)
- {
- if (!Equals(x.PathSegments[i].Parts[j], y.PathSegments[i].Parts[j]))
- {
- return false;
- }
- }
- }
- if (x.Parameters.Count != y.Parameters.Count)
+ for (var i = 0; i < x.PathSegments.Count; i++)
+ {
+ if (x.PathSegments[i].Parts.Count != y.PathSegments[i].Parts.Count)
{
return false;
}
- for (var i = 0; i < x.Parameters.Count; i++)
+ for (int j = 0; j < x.PathSegments[i].Parts.Count; j++)
{
- if (!Equals(x.Parameters[i], y.Parameters[i]))
+ if (!Equals(x.PathSegments[i].Parts[j], y.PathSegments[i].Parts[j]))
{
return false;
}
}
-
- return true;
}
- }
- private bool Equals(RoutePatternPart x, RoutePatternPart y)
- {
- if (x.GetType() != y.GetType())
+ if (x.Parameters.Count != y.Parameters.Count)
{
return false;
}
- if (x.IsLiteral && y.IsLiteral)
- {
- return Equals((RoutePatternLiteralPart)x, (RoutePatternLiteralPart)y);
- }
- else if (x.IsParameter && y.IsParameter)
+ for (var i = 0; i < x.Parameters.Count; i++)
{
- return Equals((RoutePatternParameterPart)x, (RoutePatternParameterPart)y);
- }
- else if (x.IsSeparator && y.IsSeparator)
- {
- return Equals((RoutePatternSeparatorPart)x, (RoutePatternSeparatorPart)y);
+ if (!Equals(x.Parameters[i], y.Parameters[i]))
+ {
+ return false;
+ }
}
- Debug.Fail("This should not be reachable. Do you need to update the comparison logic?");
- return false;
+ return true;
}
+ }
- private bool Equals(RoutePatternLiteralPart x, RoutePatternLiteralPart y)
+ private bool Equals(RoutePatternPart x, RoutePatternPart y)
+ {
+ if (x.GetType() != y.GetType())
{
- return x.Content == y.Content;
+ return false;
}
- private bool Equals(RoutePatternParameterPart x, RoutePatternParameterPart y)
+ if (x.IsLiteral && y.IsLiteral)
{
- return
- x.Name == y.Name &&
- x.Default == y.Default &&
- x.ParameterKind == y.ParameterKind &&
- Enumerable.SequenceEqual(x.ParameterPolicies, y.ParameterPolicies, this);
-
+ return Equals((RoutePatternLiteralPart)x, (RoutePatternLiteralPart)y);
}
-
- public bool Equals(RoutePatternParameterPolicyReference x, RoutePatternParameterPolicyReference y)
+ else if (x.IsParameter && y.IsParameter)
{
- return
- x.Content == y.Content &&
- x.ParameterPolicy == y.ParameterPolicy;
+ return Equals((RoutePatternParameterPart)x, (RoutePatternParameterPart)y);
}
-
- private bool Equals(RoutePatternSeparatorPart x, RoutePatternSeparatorPart y)
+ else if (x.IsSeparator && y.IsSeparator)
{
- return x.Content == y.Content;
+ return Equals((RoutePatternSeparatorPart)x, (RoutePatternSeparatorPart)y);
}
- public int GetHashCode(RoutePattern obj)
- {
- throw new NotImplementedException();
- }
+ Debug.Fail("This should not be reachable. Do you need to update the comparison logic?");
+ return false;
+ }
- public int GetHashCode(RoutePatternParameterPolicyReference obj)
- {
- throw new NotImplementedException();
- }
+ private bool Equals(RoutePatternLiteralPart x, RoutePatternLiteralPart y)
+ {
+ return x.Content == y.Content;
+ }
+
+ private bool Equals(RoutePatternParameterPart x, RoutePatternParameterPart y)
+ {
+ return
+ x.Name == y.Name &&
+ x.Default == y.Default &&
+ x.ParameterKind == y.ParameterKind &&
+ Enumerable.SequenceEqual(x.ParameterPolicies, y.ParameterPolicies, this);
+
+ }
+
+ public bool Equals(RoutePatternParameterPolicyReference x, RoutePatternParameterPolicyReference y)
+ {
+ return
+ x.Content == y.Content &&
+ x.ParameterPolicy == y.ParameterPolicy;
+ }
+
+ private bool Equals(RoutePatternSeparatorPart x, RoutePatternSeparatorPart y)
+ {
+ return x.Content == y.Content;
+ }
+
+ public int GetHashCode(RoutePattern obj)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int GetHashCode(RoutePatternParameterPolicyReference obj)
+ {
+ throw new NotImplementedException();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/RequestDelegateRouteBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/RequestDelegateRouteBuilderExtensionsTest.cs
index ee1034d0cc..5470d1490e 100644
--- a/src/Http/Routing/test/UnitTests/RequestDelegateRouteBuilderExtensionsTest.cs
+++ b/src/Http/Routing/test/UnitTests/RequestDelegateRouteBuilderExtensionsTest.cs
@@ -10,19 +10,19 @@ using Microsoft.Extensions.ObjectPool;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+// These are really more like integration tests. They verify that these extensions
+// add routes that behave as advertised.
+public class RequestDelegateRouteBuilderExtensionsTest
{
- // These are really more like integration tests. They verify that these extensions
- // add routes that behave as advertised.
- public class RequestDelegateRouteBuilderExtensionsTest
- {
- private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
+ private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
- public static TheoryData<Action<IRouteBuilder>, Action<HttpContext>> MatchingActions
+ public static TheoryData<Action<IRouteBuilder>, Action<HttpContext>> MatchingActions
+ {
+ get
{
- get
- {
- return new TheoryData<Action<IRouteBuilder>, Action<HttpContext>>()
+ return new TheoryData<Action<IRouteBuilder>, Action<HttpContext>>()
{
{ b => { b.MapRoute("api/{id}", NullHandler); }, null },
{ b => { b.MapMiddlewareRoute("api/{id}", app => { }); }, null },
@@ -39,38 +39,38 @@ namespace Microsoft.AspNetCore.Routing
{ b => { b.MapVerb("PUT", "api/{id}", NullHandler); }, c => { c.Request.Method = "PUT"; } },
{ b => { b.MapMiddlewareVerb("PUT", "api/{id}", app => { }); }, c => { c.Request.Method = "PUT"; } },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(MatchingActions))]
- public async Task Map_MatchesRequest(
- Action<IRouteBuilder> routeSetup,
- Action<HttpContext> requestSetup)
- {
- // Arrange
- var services = CreateServices();
+ [Theory]
+ [MemberData(nameof(MatchingActions))]
+ public async Task Map_MatchesRequest(
+ Action<IRouteBuilder> routeSetup,
+ Action<HttpContext> requestSetup)
+ {
+ // Arrange
+ var services = CreateServices();
- var context = CreateRouteContext(services);
- context.HttpContext.Request.Path = new PathString("/api/5");
- requestSetup?.Invoke(context.HttpContext);
+ var context = CreateRouteContext(services);
+ context.HttpContext.Request.Path = new PathString("/api/5");
+ requestSetup?.Invoke(context.HttpContext);
- var builder = CreateRouteBuilder(services);
- routeSetup(builder);
- var route = builder.Build();
+ var builder = CreateRouteBuilder(services);
+ routeSetup(builder);
+ var route = builder.Build();
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Same(NullHandler, context.Handler);
- }
+ // Assert
+ Assert.Same(NullHandler, context.Handler);
+ }
- public static TheoryData<Action<IRouteBuilder>, Action<HttpContext>> NonmatchingActions
+ public static TheoryData<Action<IRouteBuilder>, Action<HttpContext>> NonmatchingActions
+ {
+ get
{
- get
- {
- return new TheoryData<Action<IRouteBuilder>, Action<HttpContext>>()
+ return new TheoryData<Action<IRouteBuilder>, Action<HttpContext>>()
{
{ b => { b.MapRoute("api/{id}/extra", NullHandler); }, null },
{ b => { b.MapMiddlewareRoute("api/{id}/extra", app => { }); }, null },
@@ -97,62 +97,61 @@ namespace Microsoft.AspNetCore.Routing
{ b => { b.MapVerb("PUT", "api/{id}/extra", NullHandler); }, c => { c.Request.Method = "PUT"; } },
{ b => { b.MapMiddlewareVerb("PUT", "api/{id}/extra", app => { }); }, c => { c.Request.Method = "PUT"; } },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(NonmatchingActions))]
- public async Task Map_DoesNotMatchRequest(
- Action<IRouteBuilder> routeSetup,
- Action<HttpContext> requestSetup)
- {
- // Arrange
- var services = CreateServices();
+ [Theory]
+ [MemberData(nameof(NonmatchingActions))]
+ public async Task Map_DoesNotMatchRequest(
+ Action<IRouteBuilder> routeSetup,
+ Action<HttpContext> requestSetup)
+ {
+ // Arrange
+ var services = CreateServices();
- var context = CreateRouteContext(services);
- context.HttpContext.Request.Path = new PathString("/api/5");
- requestSetup?.Invoke(context.HttpContext);
+ var context = CreateRouteContext(services);
+ context.HttpContext.Request.Path = new PathString("/api/5");
+ requestSetup?.Invoke(context.HttpContext);
- var builder = CreateRouteBuilder(services);
- routeSetup(builder);
- var route = builder.Build();
+ var builder = CreateRouteBuilder(services);
+ routeSetup(builder);
+ var route = builder.Build();
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- private static IServiceProvider CreateServices()
- {
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- services.AddLogging();
- return services.BuildServiceProvider();
- }
+ private static IServiceProvider CreateServices()
+ {
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ services.AddLogging();
+ return services.BuildServiceProvider();
+ }
- private static RouteContext CreateRouteContext(IServiceProvider services)
- {
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = services;
- return new RouteContext(httpContext);
- }
+ private static RouteContext CreateRouteContext(IServiceProvider services)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = services;
+ return new RouteContext(httpContext);
+ }
- private static IRouteBuilder CreateRouteBuilder(IServiceProvider services)
- {
- var applicationBuilder = new Mock<IApplicationBuilder>();
- applicationBuilder.SetupAllProperties();
+ private static IRouteBuilder CreateRouteBuilder(IServiceProvider services)
+ {
+ var applicationBuilder = new Mock<IApplicationBuilder>();
+ applicationBuilder.SetupAllProperties();
- applicationBuilder
- .Setup(b => b.New().Build())
- .Returns(NullHandler);
+ applicationBuilder
+ .Setup(b => b.New().Build())
+ .Returns(NullHandler);
- applicationBuilder.Object.ApplicationServices = services;
+ applicationBuilder.Object.ApplicationServices = services;
- var routeBuilder = new RouteBuilder(applicationBuilder.Object);
- return routeBuilder;
- }
+ var routeBuilder = new RouteBuilder(applicationBuilder.Object);
+ return routeBuilder;
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteBuilderTest.cs b/src/Http/Routing/test/UnitTests/RouteBuilderTest.cs
index 381aa1d865..c671247a31 100644
--- a/src/Http/Routing/test/UnitTests/RouteBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteBuilderTest.cs
@@ -7,48 +7,47 @@ using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteBuilderTest
{
- public class RouteBuilderTest
+ [Fact]
+ public void Ctor_SetsPropertyValues()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddSingleton(typeof(RoutingMarkerService));
+ var applicationServices = services.BuildServiceProvider();
+ var applicationBuilderMock = new Mock<IApplicationBuilder>();
+ applicationBuilderMock.Setup(a => a.ApplicationServices).Returns(applicationServices);
+ var applicationBuilder = applicationBuilderMock.Object;
+ var defaultHandler = Mock.Of<IRouter>();
+
+ // Act
+ var builder = new RouteBuilder(applicationBuilder, defaultHandler);
+
+ // Assert
+ Assert.Same(applicationBuilder, builder.ApplicationBuilder);
+ Assert.Same(defaultHandler, builder.DefaultHandler);
+ Assert.Same(applicationServices, builder.ServiceProvider);
+ }
+
+ [Fact]
+ public void Ctor_ThrowsInvalidOperationException_IfRoutingMarkerServiceIsNotRegistered()
{
- [Fact]
- public void Ctor_SetsPropertyValues()
- {
- // Arrange
- var services = new ServiceCollection();
- services.AddSingleton(typeof(RoutingMarkerService));
- var applicationServices = services.BuildServiceProvider();
- var applicationBuilderMock = new Mock<IApplicationBuilder>();
- applicationBuilderMock.Setup(a => a.ApplicationServices).Returns(applicationServices);
- var applicationBuilder = applicationBuilderMock.Object;
- var defaultHandler = Mock.Of<IRouter>();
-
- // Act
- var builder = new RouteBuilder(applicationBuilder, defaultHandler);
-
- // Assert
- Assert.Same(applicationBuilder, builder.ApplicationBuilder);
- Assert.Same(defaultHandler, builder.DefaultHandler);
- Assert.Same(applicationServices, builder.ServiceProvider);
- }
-
- [Fact]
- public void Ctor_ThrowsInvalidOperationException_IfRoutingMarkerServiceIsNotRegistered()
- {
- // Arrange
- var applicationBuilderMock = new Mock<IApplicationBuilder>();
- applicationBuilderMock
- .Setup(s => s.ApplicationServices)
- .Returns(Mock.Of<IServiceProvider>());
-
- // Act & Assert
- var exception = Assert.Throws<InvalidOperationException>(() => new RouteBuilder(applicationBuilderMock.Object));
-
- Assert.Equal(
- "Unable to find the required services. Please add all the required services by calling " +
- "'IServiceCollection.AddRouting' inside the call to 'ConfigureServices(...)'" +
- " in the application startup code.",
- exception.Message);
- }
+ // Arrange
+ var applicationBuilderMock = new Mock<IApplicationBuilder>();
+ applicationBuilderMock
+ .Setup(s => s.ApplicationServices)
+ .Returns(Mock.Of<IServiceProvider>());
+
+ // Act & Assert
+ var exception = Assert.Throws<InvalidOperationException>(() => new RouteBuilder(applicationBuilderMock.Object));
+
+ Assert.Equal(
+ "Unable to find the required services. Please add all the required services by calling " +
+ "'IServiceCollection.AddRouting' inside the call to 'ConfigureServices(...)'" +
+ " in the application startup code.",
+ exception.Message);
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteCollectionTest.cs b/src/Http/Routing/test/UnitTests/RouteCollectionTest.cs
index 8082af30f0..62346d6af9 100644
--- a/src/Http/Routing/test/UnitTests/RouteCollectionTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteCollectionTest.cs
@@ -15,349 +15,349 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteCollectionTest
{
- public class RouteCollectionTest
+ private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
+
+ [Theory]
+ [InlineData(@"Home/Index/23", "/home/index/23", true, false)]
+ [InlineData(@"Home/Index/23", "/Home/Index/23", false, false)]
+ [InlineData(@"Home/Index/23", "/home/index/23/", true, true)]
+ [InlineData(@"Home/Index/23", "/Home/Index/23/", false, true)]
+ [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23/?Param1=ABC&Param2=Xyz", false, true)]
+ [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, false)]
+ [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/home/index/23/?Param1=ABC&Param2=Xyz", true, true)]
+ [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/Home/Index/23/#Param1=ABC&Param2=Xyz", false, true)]
+ [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/home/index/23#Param1=ABC&Param2=Xyz", true, false)]
+ [InlineData(@"Home/Index/23/?Param1=ABC&Param2=Xyz", "/home/index/23/?Param1=ABC&Param2=Xyz", true, true)]
+ [InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#Param1=ABC&Param2=Xyz", true, false)]
+ public void GetVirtualPath_CanLowerCaseUrls_And_AppendTrailingSlash_BasedOnOptions(
+ string returnUrl,
+ string expectedUrl,
+ bool lowercaseUrls,
+ bool appendTrailingSlash)
{
- private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
-
- [Theory]
- [InlineData(@"Home/Index/23", "/home/index/23", true, false)]
- [InlineData(@"Home/Index/23", "/Home/Index/23", false, false)]
- [InlineData(@"Home/Index/23", "/home/index/23/", true, true)]
- [InlineData(@"Home/Index/23", "/Home/Index/23/", false, true)]
- [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23/?Param1=ABC&Param2=Xyz", false, true)]
- [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, false)]
- [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/home/index/23/?Param1=ABC&Param2=Xyz", true, true)]
- [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/Home/Index/23/#Param1=ABC&Param2=Xyz", false, true)]
- [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/home/index/23#Param1=ABC&Param2=Xyz", true, false)]
- [InlineData(@"Home/Index/23/?Param1=ABC&Param2=Xyz", "/home/index/23/?Param1=ABC&Param2=Xyz", true, true)]
- [InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#Param1=ABC&Param2=Xyz", true, false)]
- public void GetVirtualPath_CanLowerCaseUrls_And_AppendTrailingSlash_BasedOnOptions(
- string returnUrl,
- string expectedUrl,
- bool lowercaseUrls,
- bool appendTrailingSlash)
- {
- // Arrange
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns(new VirtualPathData(target.Object, returnUrl));
-
- var routeCollection = new RouteCollection();
- routeCollection.Add(target.Object);
- var virtualPathContext = CreateVirtualPathContext(
- options: GetRouteOptions(
- lowerCaseUrls: lowercaseUrls,
- appendTrailingSlash: appendTrailingSlash));
-
- // Act
- var pathData = routeCollection.GetVirtualPath(virtualPathContext);
-
- // Assert
- Assert.Equal(expectedUrl, pathData.VirtualPath);
- Assert.Same(target.Object, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Arrange
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns(new VirtualPathData(target.Object, returnUrl));
+
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(target.Object);
+ var virtualPathContext = CreateVirtualPathContext(
+ options: GetRouteOptions(
+ lowerCaseUrls: lowercaseUrls,
+ appendTrailingSlash: appendTrailingSlash));
+
+ // Act
+ var pathData = routeCollection.GetVirtualPath(virtualPathContext);
+
+ // Assert
+ Assert.Equal(expectedUrl, pathData.VirtualPath);
+ Assert.Same(target.Object, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Theory]
- [InlineData(@"\u0130", @"/\u0130", true)]
- [InlineData(@"\u0049", @"/\u0049", true)]
- [InlineData(@"�ino", @"/�ino", true)]
- public void GetVirtualPath_DoesntLowerCaseUrls_Invariant(
- string returnUrl,
- string lowercaseUrl,
- bool lowercaseUrls)
- {
- // Arrange
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns(new VirtualPathData(target.Object, returnUrl));
-
- var routeCollection = new RouteCollection();
- routeCollection.Add(target.Object);
- var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls));
-
- // Act
- var pathData = routeCollection.GetVirtualPath(virtualPathContext);
-
- // Assert
- Assert.Equal(lowercaseUrl, pathData.VirtualPath);
- Assert.Same(target.Object, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Theory]
+ [InlineData(@"\u0130", @"/\u0130", true)]
+ [InlineData(@"\u0049", @"/\u0049", true)]
+ [InlineData(@"�ino", @"/�ino", true)]
+ public void GetVirtualPath_DoesntLowerCaseUrls_Invariant(
+ string returnUrl,
+ string lowercaseUrl,
+ bool lowercaseUrls)
+ {
+ // Arrange
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns(new VirtualPathData(target.Object, returnUrl));
+
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(target.Object);
+ var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls));
+
+ // Act
+ var pathData = routeCollection.GetVirtualPath(virtualPathContext);
+
+ // Assert
+ Assert.Equal(lowercaseUrl, pathData.VirtualPath);
+ Assert.Same(target.Object, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Theory]
- [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, true, false)]
- [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, false, false)]
- [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/home/index/23/?param1=abc&param2=xyz", true, true, true)]
- [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/Home/Index/23/#Param1=ABC&Param2=Xyz", false, true, true)]
- [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/home/index/23#Param1=ABC&Param2=Xyz", true, false, false)]
- [InlineData(@"Home/Index/23/?Param1=ABC&Param2=Xyz", "/home/index/23/?param1=abc&param2=xyz", true, true, true)]
- [InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#Param1=ABC&Param2=Xyz", true, false, true)]
- [InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#param1=abc&param2=xyz", true, true, true)]
- public void GetVirtualPath_CanLowerCaseUrls_QueryStrings_BasedOnOptions(
- string returnUrl,
- string expectedUrl,
- bool lowercaseUrls,
- bool lowercaseQueryStrings, bool appendTrailingSlash)
- {
- // Arrange
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns(new VirtualPathData(target.Object, returnUrl));
-
- var routeCollection = new RouteCollection();
- routeCollection.Add(target.Object);
- var virtualPathContext = CreateVirtualPathContext(
- options: GetRouteOptions(
- lowerCaseUrls: lowercaseUrls,
- lowercaseQueryStrings: lowercaseQueryStrings,
- appendTrailingSlash: appendTrailingSlash));
-
- // Act
- var pathData = routeCollection.GetVirtualPath(virtualPathContext);
-
- // Assert
- Assert.Equal(expectedUrl, pathData.VirtualPath);
- Assert.Same(target.Object, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Theory]
+ [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, true, false)]
+ [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, false, false)]
+ [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/home/index/23/?param1=abc&param2=xyz", true, true, true)]
+ [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/Home/Index/23/#Param1=ABC&Param2=Xyz", false, true, true)]
+ [InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/home/index/23#Param1=ABC&Param2=Xyz", true, false, false)]
+ [InlineData(@"Home/Index/23/?Param1=ABC&Param2=Xyz", "/home/index/23/?param1=abc&param2=xyz", true, true, true)]
+ [InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#Param1=ABC&Param2=Xyz", true, false, true)]
+ [InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#param1=abc&param2=xyz", true, true, true)]
+ public void GetVirtualPath_CanLowerCaseUrls_QueryStrings_BasedOnOptions(
+ string returnUrl,
+ string expectedUrl,
+ bool lowercaseUrls,
+ bool lowercaseQueryStrings, bool appendTrailingSlash)
+ {
+ // Arrange
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns(new VirtualPathData(target.Object, returnUrl));
+
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(target.Object);
+ var virtualPathContext = CreateVirtualPathContext(
+ options: GetRouteOptions(
+ lowerCaseUrls: lowercaseUrls,
+ lowercaseQueryStrings: lowercaseQueryStrings,
+ appendTrailingSlash: appendTrailingSlash));
+
+ // Act
+ var pathData = routeCollection.GetVirtualPath(virtualPathContext);
+
+ // Assert
+ Assert.Equal(expectedUrl, pathData.VirtualPath);
+ Assert.Same(target.Object, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Theory]
- [MemberData(nameof(DataTokensTestData))]
- public void GetVirtualPath_ReturnsDataTokens(RouteValueDictionary dataTokens, string routerName)
- {
- // Arrange
- var virtualPath = "/TestVirtualPath";
+ [Theory]
+ [MemberData(nameof(DataTokensTestData))]
+ public void GetVirtualPath_ReturnsDataTokens(RouteValueDictionary dataTokens, string routerName)
+ {
+ // Arrange
+ var virtualPath = "/TestVirtualPath";
- var pathContextValues = new RouteValueDictionary { { "controller", virtualPath } };
+ var pathContextValues = new RouteValueDictionary { { "controller", virtualPath } };
- var pathContext = CreateVirtualPathContext(
- pathContextValues,
- GetRouteOptions(),
- routerName);
+ var pathContext = CreateVirtualPathContext(
+ pathContextValues,
+ GetRouteOptions(),
+ routerName);
- var route = CreateTemplateRoute("{controller}", routerName, dataTokens);
- var routeCollection = new RouteCollection();
- routeCollection.Add(route);
+ var route = CreateTemplateRoute("{controller}", routerName, dataTokens);
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(route);
- var expectedDataTokens = dataTokens ?? new RouteValueDictionary();
+ var expectedDataTokens = dataTokens ?? new RouteValueDictionary();
- // Act
- var pathData = routeCollection.GetVirtualPath(pathContext);
+ // Act
+ var pathData = routeCollection.GetVirtualPath(pathContext);
- // Assert
- Assert.NotNull(pathData);
- Assert.Same(route, pathData.Router);
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Same(route, pathData.Router);
- Assert.Equal(virtualPath, pathData.VirtualPath);
+ Assert.Equal(virtualPath, pathData.VirtualPath);
- Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count);
- foreach (var dataToken in expectedDataTokens)
- {
- Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key));
- Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
- }
+ Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count);
+ foreach (var dataToken in expectedDataTokens)
+ {
+ Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key));
+ Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
}
+ }
- [Fact]
- public async Task RouteAsync_FirstMatches()
- {
- // Arrange
- var routes = new RouteCollection();
+ [Fact]
+ public async Task RouteAsync_FirstMatches()
+ {
+ // Arrange
+ var routes = new RouteCollection();
- var route1 = CreateRoute(accept: true);
- routes.Add(route1.Object);
+ var route1 = CreateRoute(accept: true);
+ routes.Add(route1.Object);
- var route2 = CreateRoute(accept: false);
- routes.Add(route2.Object);
+ var route2 = CreateRoute(accept: false);
+ routes.Add(route2.Object);
- var context = CreateRouteContext("/Cool");
+ var context = CreateRouteContext("/Cool");
- // Act
- await routes.RouteAsync(context);
+ // Act
+ await routes.RouteAsync(context);
- // Assert
- route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
- route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(0));
- Assert.NotNull(context.Handler);
+ // Assert
+ route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
+ route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(0));
+ Assert.NotNull(context.Handler);
- Assert.Equal(1, context.RouteData.Routers.Count);
- Assert.Same(route1.Object, context.RouteData.Routers[0]);
- }
+ Assert.Equal(1, context.RouteData.Routers.Count);
+ Assert.Same(route1.Object, context.RouteData.Routers[0]);
+ }
- [Fact]
- public async Task RouteAsync_SecondMatches()
- {
- // Arrange
+ [Fact]
+ public async Task RouteAsync_SecondMatches()
+ {
+ // Arrange
- var routes = new RouteCollection();
- var route1 = CreateRoute(accept: false);
- routes.Add(route1.Object);
+ var routes = new RouteCollection();
+ var route1 = CreateRoute(accept: false);
+ routes.Add(route1.Object);
- var route2 = CreateRoute(accept: true);
- routes.Add(route2.Object);
+ var route2 = CreateRoute(accept: true);
+ routes.Add(route2.Object);
- var context = CreateRouteContext("/Cool");
+ var context = CreateRouteContext("/Cool");
- // Act
- await routes.RouteAsync(context);
+ // Act
+ await routes.RouteAsync(context);
- // Assert
- route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
- route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
- Assert.NotNull(context.Handler);
+ // Assert
+ route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
+ route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
+ Assert.NotNull(context.Handler);
- Assert.Equal(1, context.RouteData.Routers.Count);
- Assert.Same(route2.Object, context.RouteData.Routers[0]);
- }
+ Assert.Equal(1, context.RouteData.Routers.Count);
+ Assert.Same(route2.Object, context.RouteData.Routers[0]);
+ }
- [Fact]
- public async Task RouteAsync_NoMatch()
- {
- // Arrange
- var routes = new RouteCollection();
- var route1 = CreateRoute(accept: false);
- routes.Add(route1.Object);
+ [Fact]
+ public async Task RouteAsync_NoMatch()
+ {
+ // Arrange
+ var routes = new RouteCollection();
+ var route1 = CreateRoute(accept: false);
+ routes.Add(route1.Object);
- var route2 = CreateRoute(accept: false);
- routes.Add(route2.Object);
+ var route2 = CreateRoute(accept: false);
+ routes.Add(route2.Object);
- var context = CreateRouteContext("/Cool");
+ var context = CreateRouteContext("/Cool");
- // Act
- await routes.RouteAsync(context);
+ // Act
+ await routes.RouteAsync(context);
- // Assert
- route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
- route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
- Assert.Null(context.Handler);
+ // Assert
+ route1.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
+ route2.Verify(e => e.RouteAsync(It.IsAny<RouteContext>()), Times.Exactly(1));
+ Assert.Null(context.Handler);
- Assert.Empty(context.RouteData.Routers);
- }
+ Assert.Empty(context.RouteData.Routers);
+ }
- [Theory]
- [InlineData(false, "/RouteName")]
- [InlineData(true, "/routename")]
- public void NamedRouteTests_GetNamedRoute_ReturnsValue(bool lowercaseUrls, string expectedUrl)
- {
- // Arrange
- var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "RouteName", "Route3" });
- var virtualPathContext = CreateVirtualPathContext(
- routeName: "RouteName",
- options: GetRouteOptions(lowercaseUrls));
-
- // Act
- var pathData = routeCollection.GetVirtualPath(virtualPathContext);
-
- // Assert
- Assert.Equal(expectedUrl, pathData.VirtualPath);
- var namedRouter = Assert.IsAssignableFrom<INamedRouter>(pathData.Router);
- Assert.Equal(virtualPathContext.RouteName, namedRouter.Name);
- Assert.Empty(pathData.DataTokens);
- }
+ [Theory]
+ [InlineData(false, "/RouteName")]
+ [InlineData(true, "/routename")]
+ public void NamedRouteTests_GetNamedRoute_ReturnsValue(bool lowercaseUrls, string expectedUrl)
+ {
+ // Arrange
+ var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "RouteName", "Route3" });
+ var virtualPathContext = CreateVirtualPathContext(
+ routeName: "RouteName",
+ options: GetRouteOptions(lowercaseUrls));
+
+ // Act
+ var pathData = routeCollection.GetVirtualPath(virtualPathContext);
+
+ // Assert
+ Assert.Equal(expectedUrl, pathData.VirtualPath);
+ var namedRouter = Assert.IsAssignableFrom<INamedRouter>(pathData.Router);
+ Assert.Equal(virtualPathContext.RouteName, namedRouter.Name);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void NamedRouteTests_GetNamedRoute_RouteNotFound()
- {
- // Arrange
- var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "Route3" });
- var virtualPathContext = CreateVirtualPathContext("NonExistantRoute");
+ [Fact]
+ public void NamedRouteTests_GetNamedRoute_RouteNotFound()
+ {
+ // Arrange
+ var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "Route3" });
+ var virtualPathContext = CreateVirtualPathContext("NonExistantRoute");
- // Act
- var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext);
+ // Act
+ var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext);
- // Assert
- Assert.Null(stringVirtualPath);
- }
+ // Assert
+ Assert.Null(stringVirtualPath);
+ }
- [Fact]
- public void NamedRouteTests_GetNamedRoute_AmbiguousRoutesInCollection_DoesNotThrowForUnambiguousRoute()
- {
- // Arrange
- var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "Route3", "Route4" });
+ [Fact]
+ public void NamedRouteTests_GetNamedRoute_AmbiguousRoutesInCollection_DoesNotThrowForUnambiguousRoute()
+ {
+ // Arrange
+ var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "Route3", "Route4" });
- // Add Duplicate route.
- routeCollection.Add(CreateNamedRoute("Route3"));
- var virtualPathContext = CreateVirtualPathContext(routeName: "Route1", options: GetRouteOptions(true));
+ // Add Duplicate route.
+ routeCollection.Add(CreateNamedRoute("Route3"));
+ var virtualPathContext = CreateVirtualPathContext(routeName: "Route1", options: GetRouteOptions(true));
- // Act
- var pathData = routeCollection.GetVirtualPath(virtualPathContext);
+ // Act
+ var pathData = routeCollection.GetVirtualPath(virtualPathContext);
- // Assert
- Assert.Equal("/route1", pathData.VirtualPath);
- var namedRouter = Assert.IsAssignableFrom<INamedRouter>(pathData.Router);
- Assert.Equal("Route1", namedRouter.Name);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.Equal("/route1", pathData.VirtualPath);
+ var namedRouter = Assert.IsAssignableFrom<INamedRouter>(pathData.Router);
+ Assert.Equal("Route1", namedRouter.Name);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void NamedRouteTests_GetNamedRoute_AmbiguousRoutesInCollection_ThrowsForAmbiguousRoute()
- {
- // Arrange
- var ambiguousRoute = "ambiguousRoute";
- var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", ambiguousRoute, "Route4" });
-
- // Add Duplicate route.
- routeCollection.Add(CreateNamedRoute(ambiguousRoute));
- var virtualPathContext = CreateVirtualPathContext(routeName: ambiguousRoute, options: GetRouteOptions());
-
- // Act & Assert
- var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
- Assert.Equal(
- "The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.",
- ex.Message);
- }
+ [Fact]
+ public void NamedRouteTests_GetNamedRoute_AmbiguousRoutesInCollection_ThrowsForAmbiguousRoute()
+ {
+ // Arrange
+ var ambiguousRoute = "ambiguousRoute";
+ var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", ambiguousRoute, "Route4" });
+
+ // Add Duplicate route.
+ routeCollection.Add(CreateNamedRoute(ambiguousRoute));
+ var virtualPathContext = CreateVirtualPathContext(routeName: ambiguousRoute, options: GetRouteOptions());
+
+ // Act & Assert
+ var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
+ Assert.Equal(
+ "The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.",
+ ex.Message);
+ }
- [Fact]
- public void GetVirtualPath_AmbiguousRoutes_RequiresRouteValueValidation_Error()
- {
- // Arrange
- var namedRoute = CreateNamedRoute("Ambiguous", accept: false);
+ [Fact]
+ public void GetVirtualPath_AmbiguousRoutes_RequiresRouteValueValidation_Error()
+ {
+ // Arrange
+ var namedRoute = CreateNamedRoute("Ambiguous", accept: false);
- var routeCollection = new RouteCollection();
- routeCollection.Add(namedRoute);
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(namedRoute);
- var innerRouteCollection = new RouteCollection();
- innerRouteCollection.Add(namedRoute);
- routeCollection.Add(innerRouteCollection);
+ var innerRouteCollection = new RouteCollection();
+ innerRouteCollection.Add(namedRoute);
+ routeCollection.Add(innerRouteCollection);
- var virtualPathContext = CreateVirtualPathContext("Ambiguous");
+ var virtualPathContext = CreateVirtualPathContext("Ambiguous");
- // Act & Assert
- var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
- Assert.Equal("The supplied route name 'Ambiguous' is ambiguous and matched more than one route.", ex.Message);
- }
+ // Act & Assert
+ var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
+ Assert.Equal("The supplied route name 'Ambiguous' is ambiguous and matched more than one route.", ex.Message);
+ }
- // "Integration" tests for RouteCollection
+ // "Integration" tests for RouteCollection
- public static IEnumerable<object[]> IntegrationTestData
+ public static IEnumerable<object[]> IntegrationTestData
+ {
+ get
{
- get
- {
- yield return new object[] {
+ yield return new object[] {
"{controller}/{action}",
new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } },
"/home/index",
true };
- yield return new object[] {
+ yield return new object[] {
"{controller}/{action}/",
new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } },
"/Home/Index",
false };
- yield return new object[] {
+ yield return new object[] {
"api/{action}/",
new RouteValueDictionary { { "action", "Create" } },
"/api/create",
true };
- yield return new object[] {
+ yield return new object[] {
"api/{action}/{id}",
new RouteValueDictionary {
{ "action", "Create" },
@@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Routing
"/api/create/23?Param1=Value1&Param2=Value2",
true };
- yield return new object[] {
+ yield return new object[] {
"api/{action}/{id}",
new RouteValueDictionary {
{ "action", "Create" },
@@ -376,39 +376,39 @@ namespace Microsoft.AspNetCore.Routing
{ "Param2", "Value2" } },
"/api/Create/23?Param1=Value1&Param2=Value2",
false };
- }
}
+ }
- [Theory]
- [MemberData(nameof(IntegrationTestData))]
- public void GetVirtualPath_Success(
- string template,
- RouteValueDictionary values,
- string expectedUrl,
- bool lowercaseUrls)
- {
- // Arrange
- var routeCollection = new RouteCollection();
- var route = CreateTemplateRoute(template);
- routeCollection.Add(route);
- var context = CreateVirtualPathContext(values, options: GetRouteOptions(lowercaseUrls));
-
- // Act
- var pathData = routeCollection.GetVirtualPath(context);
-
- // Assert
- Assert.Equal(expectedUrl, pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Theory]
+ [MemberData(nameof(IntegrationTestData))]
+ public void GetVirtualPath_Success(
+ string template,
+ RouteValueDictionary values,
+ string expectedUrl,
+ bool lowercaseUrls)
+ {
+ // Arrange
+ var routeCollection = new RouteCollection();
+ var route = CreateTemplateRoute(template);
+ routeCollection.Add(route);
+ var context = CreateVirtualPathContext(values, options: GetRouteOptions(lowercaseUrls));
+
+ // Act
+ var pathData = routeCollection.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal(expectedUrl, pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- public static IEnumerable<object[]> RestoresRouteDataForEachRouterData
+ public static IEnumerable<object[]> RestoresRouteDataForEachRouterData
+ {
+ get
{
- get
- {
- // Here 'area' segment doesn't have a value but the later segments have values. This is an invalid
- // route match and the url generation should look into the next available route in the collection.
- yield return new object[] {
+ // Here 'area' segment doesn't have a value but the later segments have values. This is an invalid
+ // route match and the url generation should look into the next available route in the collection.
+ yield return new object[] {
new Route[]
{
CreateTemplateRoute("{area?}/{controller=Home}/{action=Index}/{id?}", "1"),
@@ -418,9 +418,9 @@ namespace Microsoft.AspNetCore.Routing
"/Test",
"2" };
- // Here the segment 'a' is valid but 'b' is not as it would be empty. This would be an invalid route match, but
- // the route value of 'a' should still be present to be evaluated for the next available route.
- yield return new object[] {
+ // Here the segment 'a' is valid but 'b' is not as it would be empty. This would be an invalid route match, but
+ // the route value of 'a' should still be present to be evaluated for the next available route.
+ yield return new object[] {
new[]
{
CreateTemplateRoute("{a}/{b?}/{c}", "1"),
@@ -429,284 +429,283 @@ namespace Microsoft.AspNetCore.Routing
new RouteValueDictionary(new { a = "Test", c = "Foo" }),
"/Test?c=Foo",
"2" };
- }
}
+ }
- [Theory]
- [MemberData(nameof(RestoresRouteDataForEachRouterData))]
- public void GetVirtualPath_RestoresRouteData_ForEachRouter(
- Route[] routes,
- RouteValueDictionary routeValues,
- string expectedUrl,
- string expectedRouteToMatch)
+ [Theory]
+ [MemberData(nameof(RestoresRouteDataForEachRouterData))]
+ public void GetVirtualPath_RestoresRouteData_ForEachRouter(
+ Route[] routes,
+ RouteValueDictionary routeValues,
+ string expectedUrl,
+ string expectedRouteToMatch)
+ {
+ // Arrange
+ var routeCollection = new RouteCollection();
+ foreach (var route in routes)
{
- // Arrange
- var routeCollection = new RouteCollection();
- foreach (var route in routes)
- {
- routeCollection.Add(route);
- }
- var context = CreateVirtualPathContext(routeValues);
-
- // Act
- var pathData = routeCollection.GetVirtualPath(context);
-
- // Assert
- Assert.Equal(expectedUrl, pathData.VirtualPath);
- Assert.Same(expectedRouteToMatch, ((INamedRouter)pathData.Router).Name);
- Assert.Empty(pathData.DataTokens);
+ routeCollection.Add(route);
}
+ var context = CreateVirtualPathContext(routeValues);
- [Fact]
- public void GetVirtualPath_NoBestEffort_NoMatch()
- {
- // Arrange
- var route1 = CreateRoute(accept: false, match: false, matchValue: "bad");
- var route2 = CreateRoute(accept: false, match: false, matchValue: "bad");
- var route3 = CreateRoute(accept: false, match: false, matchValue: "bad");
+ // Act
+ var pathData = routeCollection.GetVirtualPath(context);
- var routeCollection = new RouteCollection();
- routeCollection.Add(route1.Object);
- routeCollection.Add(route2.Object);
- routeCollection.Add(route3.Object);
+ // Assert
+ Assert.Equal(expectedUrl, pathData.VirtualPath);
+ Assert.Same(expectedRouteToMatch, ((INamedRouter)pathData.Router).Name);
+ Assert.Empty(pathData.DataTokens);
+ }
- var virtualPathContext = CreateVirtualPathContext();
+ [Fact]
+ public void GetVirtualPath_NoBestEffort_NoMatch()
+ {
+ // Arrange
+ var route1 = CreateRoute(accept: false, match: false, matchValue: "bad");
+ var route2 = CreateRoute(accept: false, match: false, matchValue: "bad");
+ var route3 = CreateRoute(accept: false, match: false, matchValue: "bad");
- // Act
- var path = routeCollection.GetVirtualPath(virtualPathContext);
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(route1.Object);
+ routeCollection.Add(route2.Object);
+ routeCollection.Add(route3.Object);
- Assert.Null(path);
+ var virtualPathContext = CreateVirtualPathContext();
- // All of these should be called
- route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
- route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
- route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
- }
+ // Act
+ var path = routeCollection.GetVirtualPath(virtualPathContext);
+
+ Assert.Null(path);
- // DataTokens test data for RouterCollection.GetVirtualPath
- public static IEnumerable<object[]> DataTokensTestData
+ // All of these should be called
+ route1.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
+ route2.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
+ route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
+ }
+
+ // DataTokens test data for RouterCollection.GetVirtualPath
+ public static IEnumerable<object[]> DataTokensTestData
+ {
+ get
{
- get
- {
- yield return new object[] { null, null };
- yield return new object[] { new RouteValueDictionary(), null };
- yield return new object[] { new RouteValueDictionary() { { "tokenKey", "tokenValue" } }, null };
-
- yield return new object[] { null, "routerA" };
- yield return new object[] { new RouteValueDictionary(), "routerA" };
- yield return new object[] { new RouteValueDictionary() { { "tokenKey", "tokenValue" } }, "routerA" };
- }
+ yield return new object[] { null, null };
+ yield return new object[] { new RouteValueDictionary(), null };
+ yield return new object[] { new RouteValueDictionary() { { "tokenKey", "tokenValue" } }, null };
+
+ yield return new object[] { null, "routerA" };
+ yield return new object[] { new RouteValueDictionary(), "routerA" };
+ yield return new object[] { new RouteValueDictionary() { { "tokenKey", "tokenValue" } }, "routerA" };
}
+ }
- private static RouteCollection GetRouteCollectionWithNamedRoutes(IEnumerable<string> routeNames)
+ private static RouteCollection GetRouteCollectionWithNamedRoutes(IEnumerable<string> routeNames)
+ {
+ var routes = new RouteCollection();
+ foreach (var routeName in routeNames)
{
- var routes = new RouteCollection();
- foreach (var routeName in routeNames)
- {
- var route1 = CreateNamedRoute(routeName, accept: true);
- routes.Add(route1);
- }
-
- return routes;
+ var route1 = CreateNamedRoute(routeName, accept: true);
+ routes.Add(route1);
}
- private static RouteCollection GetNestedRouteCollection(string[] routeNames)
+ return routes;
+ }
+
+ private static RouteCollection GetNestedRouteCollection(string[] routeNames)
+ {
+ int index = Random.Shared.Next(0, routeNames.Length - 1);
+ var first = routeNames.Take(index).ToArray();
+ var second = routeNames.Skip(index).ToArray();
+
+ var rc1 = GetRouteCollectionWithNamedRoutes(first);
+ var rc2 = GetRouteCollectionWithNamedRoutes(second);
+ var rc3 = new RouteCollection();
+ var rc4 = new RouteCollection();
+
+ rc1.Add(rc3);
+ rc4.Add(rc2);
+
+ // Add a few unnamedRoutes.
+ rc1.Add(CreateRoute(accept: false).Object);
+ rc2.Add(CreateRoute(accept: false).Object);
+ rc3.Add(CreateRoute(accept: false).Object);
+ rc3.Add(CreateRoute(accept: false).Object);
+ rc4.Add(CreateRoute(accept: false).Object);
+ rc4.Add(CreateRoute(accept: false).Object);
+
+ var routeCollection = new RouteCollection();
+ routeCollection.Add(rc1);
+ routeCollection.Add(rc4);
+
+ return routeCollection;
+ }
+
+ private static INamedRouter CreateNamedRoute(string name, bool accept = false, string matchValue = null)
+ {
+ if (matchValue == null)
{
- int index = Random.Shared.Next(0, routeNames.Length - 1);
- var first = routeNames.Take(index).ToArray();
- var second = routeNames.Skip(index).ToArray();
-
- var rc1 = GetRouteCollectionWithNamedRoutes(first);
- var rc2 = GetRouteCollectionWithNamedRoutes(second);
- var rc3 = new RouteCollection();
- var rc4 = new RouteCollection();
-
- rc1.Add(rc3);
- rc4.Add(rc2);
-
- // Add a few unnamedRoutes.
- rc1.Add(CreateRoute(accept: false).Object);
- rc2.Add(CreateRoute(accept: false).Object);
- rc3.Add(CreateRoute(accept: false).Object);
- rc3.Add(CreateRoute(accept: false).Object);
- rc4.Add(CreateRoute(accept: false).Object);
- rc4.Add(CreateRoute(accept: false).Object);
-
- var routeCollection = new RouteCollection();
- routeCollection.Add(rc1);
- routeCollection.Add(rc4);
-
- return routeCollection;
+ matchValue = name;
}
- private static INamedRouter CreateNamedRoute(string name, bool accept = false, string matchValue = null)
+ var target = new Mock<INamedRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns<VirtualPathContext>(c =>
+ c.RouteName == name ? new VirtualPathData(target.Object, matchValue) : null)
+ .Verifiable();
+
+ target
+ .SetupGet(e => e.Name)
+ .Returns(name);
+
+ target
+ .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>((c) => c.Handler = accept ? NullHandler : null)
+ .Returns(Task.FromResult<object>(null))
+ .Verifiable();
+
+ return target.Object;
+ }
+
+ private static Route CreateTemplateRoute(
+ string template,
+ string routerName = null,
+ RouteValueDictionary dataTokens = null,
+ IInlineConstraintResolver constraintResolver = null)
+ {
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns<VirtualPathContext>(rc => null);
+
+ if (constraintResolver == null)
{
- if (matchValue == null)
- {
- matchValue = name;
- }
-
- var target = new Mock<INamedRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns<VirtualPathContext>(c =>
- c.RouteName == name ? new VirtualPathData(target.Object, matchValue) : null)
- .Verifiable();
-
- target
- .SetupGet(e => e.Name)
- .Returns(name);
-
- target
- .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>((c) => c.Handler = accept ? NullHandler : null)
- .Returns(Task.FromResult<object>(null))
- .Verifiable();
-
- return target.Object;
+ constraintResolver = new Mock<IInlineConstraintResolver>().Object;
}
- private static Route CreateTemplateRoute(
- string template,
- string routerName = null,
- RouteValueDictionary dataTokens = null,
- IInlineConstraintResolver constraintResolver = null)
+ return new Route(
+ target.Object,
+ routerName,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: dataTokens,
+ inlineConstraintResolver: constraintResolver);
+ }
+
+ private static VirtualPathContext CreateVirtualPathContext(
+ string routeName = null,
+ ILoggerFactory loggerFactory = null,
+ Action<RouteOptions> options = null)
+ {
+ if (loggerFactory == null)
{
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns<VirtualPathContext>(rc => null);
-
- if (constraintResolver == null)
- {
- constraintResolver = new Mock<IInlineConstraintResolver>().Object;
- }
-
- return new Route(
- target.Object,
- routerName,
- template,
- defaults: null,
- constraints: null,
- dataTokens: dataTokens,
- inlineConstraintResolver: constraintResolver);
+ loggerFactory = NullLoggerFactory.Instance;
}
- private static VirtualPathContext CreateVirtualPathContext(
- string routeName = null,
- ILoggerFactory loggerFactory = null,
- Action<RouteOptions> options = null)
+ var request = new Mock<HttpRequest>(MockBehavior.Strict);
+
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ if (options != null)
{
- if (loggerFactory == null)
- {
- loggerFactory = NullLoggerFactory.Instance;
- }
-
- var request = new Mock<HttpRequest>(MockBehavior.Strict);
-
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- if (options != null)
- {
- services.Configure(options);
- }
-
- var context = new Mock<HttpContext>(MockBehavior.Strict);
- context.SetupGet(m => m.RequestServices).Returns(services.BuildServiceProvider());
- context.SetupGet(c => c.Request).Returns(request.Object);
-
- return new VirtualPathContext(context.Object, null, null, routeName);
+ services.Configure(options);
}
- private static VirtualPathContext CreateVirtualPathContext(
- RouteValueDictionary values,
- Action<RouteOptions> options = null,
- string routeName = null)
+ var context = new Mock<HttpContext>(MockBehavior.Strict);
+ context.SetupGet(m => m.RequestServices).Returns(services.BuildServiceProvider());
+ context.SetupGet(c => c.Request).Returns(request.Object);
+
+ return new VirtualPathContext(context.Object, null, null, routeName);
+ }
+
+ private static VirtualPathContext CreateVirtualPathContext(
+ RouteValueDictionary values,
+ Action<RouteOptions> options = null,
+ string routeName = null)
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+ services.AddOptions();
+ services.AddRouting();
+ if (options != null)
{
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
- services.AddOptions();
- services.AddRouting();
- if (options != null)
- {
- services.Configure<RouteOptions>(options);
- }
-
- var context = new DefaultHttpContext
- {
- RequestServices = services.BuildServiceProvider(),
- };
-
- return new VirtualPathContext(
- context,
- ambientValues: null,
- values: values,
- routeName: routeName);
+ services.Configure<RouteOptions>(options);
}
- private static RouteContext CreateRouteContext(
- string requestPath,
- ILoggerFactory loggerFactory = null,
- RouteOptions options = null)
+ var context = new DefaultHttpContext
{
- if (loggerFactory == null)
- {
- loggerFactory = NullLoggerFactory.Instance;
- }
-
- if (options == null)
- {
- options = new RouteOptions();
- }
-
- var request = new Mock<HttpRequest>(MockBehavior.Strict);
- request.SetupGet(r => r.Path).Returns(requestPath);
-
- var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
- optionsAccessor.SetupGet(o => o.Value).Returns(options);
-
- var context = new Mock<HttpContext>(MockBehavior.Strict);
- context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
- .Returns(loggerFactory);
- context.Setup(m => m.RequestServices.GetService(typeof(IOptions<RouteOptions>)))
- .Returns(optionsAccessor.Object);
- context.SetupGet(c => c.Request).Returns(request.Object);
-
- return new RouteContext(context.Object);
- }
+ RequestServices = services.BuildServiceProvider(),
+ };
+
+ return new VirtualPathContext(
+ context,
+ ambientValues: null,
+ values: values,
+ routeName: routeName);
+ }
- private static Mock<IRouter> CreateRoute(
- bool accept = true,
- bool match = false,
- string matchValue = "value")
+ private static RouteContext CreateRouteContext(
+ string requestPath,
+ ILoggerFactory loggerFactory = null,
+ RouteOptions options = null)
+ {
+ if (loggerFactory == null)
{
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns(accept || match ? new VirtualPathData(target.Object, matchValue) : null)
- .Verifiable();
-
- target
- .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>((c) => c.Handler = accept ? NullHandler : null)
- .Returns(Task.FromResult<object>(null))
- .Verifiable();
-
- return target;
+ loggerFactory = NullLoggerFactory.Instance;
}
- private static Action<RouteOptions> GetRouteOptions(
- bool lowerCaseUrls = false,
- bool appendTrailingSlash = false,
- bool lowercaseQueryStrings = false)
+ if (options == null)
{
- return (options) =>
- {
- options.LowercaseUrls = lowerCaseUrls;
- options.AppendTrailingSlash = appendTrailingSlash;
- options.LowercaseQueryStrings = lowercaseQueryStrings;
- };
+ options = new RouteOptions();
}
+
+ var request = new Mock<HttpRequest>(MockBehavior.Strict);
+ request.SetupGet(r => r.Path).Returns(requestPath);
+
+ var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
+ optionsAccessor.SetupGet(o => o.Value).Returns(options);
+
+ var context = new Mock<HttpContext>(MockBehavior.Strict);
+ context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
+ .Returns(loggerFactory);
+ context.Setup(m => m.RequestServices.GetService(typeof(IOptions<RouteOptions>)))
+ .Returns(optionsAccessor.Object);
+ context.SetupGet(c => c.Request).Returns(request.Object);
+
+ return new RouteContext(context.Object);
+ }
+
+ private static Mock<IRouter> CreateRoute(
+ bool accept = true,
+ bool match = false,
+ string matchValue = "value")
+ {
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns(accept || match ? new VirtualPathData(target.Object, matchValue) : null)
+ .Verifiable();
+
+ target
+ .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>((c) => c.Handler = accept ? NullHandler : null)
+ .Returns(Task.FromResult<object>(null))
+ .Verifiable();
+
+ return target;
+ }
+
+ private static Action<RouteOptions> GetRouteOptions(
+ bool lowerCaseUrls = false,
+ bool appendTrailingSlash = false,
+ bool lowercaseQueryStrings = false)
+ {
+ return (options) =>
+ {
+ options.LowercaseUrls = lowerCaseUrls;
+ options.AppendTrailingSlash = appendTrailingSlash;
+ options.LowercaseQueryStrings = lowercaseQueryStrings;
+ };
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteConstraintBuilderTest.cs b/src/Http/Routing/test/UnitTests/RouteConstraintBuilderTest.cs
index 66eb7ada18..6188ce2694 100644
--- a/src/Http/Routing/test/UnitTests/RouteConstraintBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteConstraintBuilderTest.cs
@@ -11,181 +11,180 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteConstraintBuilderTest
{
- public class RouteConstraintBuilderTest
+ [Fact]
+ public void AddConstraint_String_CreatesARegex()
+ {
+ // Arrange
+ var builder = CreateBuilder("{controller}/{action}");
+ builder.AddConstraint("controller", "abc");
+
+ // Act
+ var result = builder.Build();
+
+ // Assert
+ Assert.Equal(1, result.Count);
+ Assert.Equal("controller", result.First().Key);
+
+ Assert.IsType<RegexRouteConstraint>(Assert.Single(result).Value);
+ }
+
+ [Fact]
+ public void AddConstraint_IRouteConstraint()
+ {
+ // Arrange
+ var originalConstraint = Mock.Of<IRouteConstraint>();
+
+ var builder = CreateBuilder("{controller}/{action}");
+ builder.AddConstraint("controller", originalConstraint);
+
+ // Act
+ var result = builder.Build();
+
+ // Assert
+ Assert.Equal(1, result.Count);
+
+ var kvp = Assert.Single(result);
+ Assert.Equal("controller", kvp.Key);
+
+ Assert.Same(originalConstraint, kvp.Value);
+ }
+
+ [Fact]
+ public void AddResolvedConstraint_IRouteConstraint()
+ {
+ // Arrange
+ var builder = CreateBuilder("{controller}/{action}");
+ builder.AddResolvedConstraint("controller", "int");
+
+ // Act
+ var result = builder.Build();
+
+ // Assert
+ Assert.Equal(1, result.Count);
+
+ var kvp = Assert.Single(result);
+ Assert.Equal("controller", kvp.Key);
+
+ Assert.IsType<IntRouteConstraint>(kvp.Value);
+ }
+
+ [Fact]
+ public void AddConstraint_InvalidType_Throws()
+ {
+ // Arrange
+ var builder = CreateBuilder("{controller}/{action}");
+
+ // Act & Assert
+ ExceptionAssert.Throws<RouteCreationException>(
+ () => builder.AddConstraint("controller", 5),
+ "The constraint entry 'controller' - '5' on the route " +
+ "'{controller}/{action}' must have a string value or be of a type which implements '" +
+ typeof(IRouteConstraint) + "'.");
+ }
+
+ [Fact]
+ public void AddResolvedConstraint_NotFound_Throws()
+ {
+ // Arrange
+ var unresolvedConstraint = @"test";
+
+ var builder = CreateBuilder("{controller}/{action}");
+
+ // Act & Assert
+ ExceptionAssert.Throws<InvalidOperationException>(
+ () => builder.AddResolvedConstraint("controller", unresolvedConstraint),
+ @"The constraint entry 'controller' - '" + unresolvedConstraint + "' on the route " +
+ "'{controller}/{action}' could not be resolved by the constraint resolver " +
+ "of type 'DefaultInlineConstraintResolver'.");
+ }
+
+ [Fact]
+ public void AddResolvedConstraint_ForOptionalParameter()
+ {
+ var builder = CreateBuilder("{controller}/{action}/{id}");
+ builder.SetOptional("id");
+ builder.AddResolvedConstraint("id", "int");
+
+ var result = builder.Build();
+ Assert.Equal(1, result.Count);
+ Assert.Equal("id", result.First().Key);
+ Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
+ }
+
+ [Fact]
+ public void AddResolvedConstraint_SetOptionalParameter_AfterAddingTheParameter()
{
- [Fact]
- public void AddConstraint_String_CreatesARegex()
- {
- // Arrange
- var builder = CreateBuilder("{controller}/{action}");
- builder.AddConstraint("controller", "abc");
-
- // Act
- var result = builder.Build();
-
- // Assert
- Assert.Equal(1, result.Count);
- Assert.Equal("controller", result.First().Key);
-
- Assert.IsType<RegexRouteConstraint>(Assert.Single(result).Value);
- }
-
- [Fact]
- public void AddConstraint_IRouteConstraint()
- {
- // Arrange
- var originalConstraint = Mock.Of<IRouteConstraint>();
-
- var builder = CreateBuilder("{controller}/{action}");
- builder.AddConstraint("controller", originalConstraint);
-
- // Act
- var result = builder.Build();
-
- // Assert
- Assert.Equal(1, result.Count);
-
- var kvp = Assert.Single(result);
- Assert.Equal("controller", kvp.Key);
-
- Assert.Same(originalConstraint, kvp.Value);
- }
-
- [Fact]
- public void AddResolvedConstraint_IRouteConstraint()
- {
- // Arrange
- var builder = CreateBuilder("{controller}/{action}");
- builder.AddResolvedConstraint("controller", "int");
-
- // Act
- var result = builder.Build();
-
- // Assert
- Assert.Equal(1, result.Count);
-
- var kvp = Assert.Single(result);
- Assert.Equal("controller", kvp.Key);
-
- Assert.IsType<IntRouteConstraint>(kvp.Value);
- }
-
- [Fact]
- public void AddConstraint_InvalidType_Throws()
- {
- // Arrange
- var builder = CreateBuilder("{controller}/{action}");
-
- // Act & Assert
- ExceptionAssert.Throws<RouteCreationException>(
- () => builder.AddConstraint("controller", 5),
- "The constraint entry 'controller' - '5' on the route " +
- "'{controller}/{action}' must have a string value or be of a type which implements '" +
- typeof(IRouteConstraint) + "'.");
- }
-
- [Fact]
- public void AddResolvedConstraint_NotFound_Throws()
- {
- // Arrange
- var unresolvedConstraint = @"test";
-
- var builder = CreateBuilder("{controller}/{action}");
-
- // Act & Assert
- ExceptionAssert.Throws<InvalidOperationException>(
- () => builder.AddResolvedConstraint("controller", unresolvedConstraint),
- @"The constraint entry 'controller' - '" + unresolvedConstraint + "' on the route " +
- "'{controller}/{action}' could not be resolved by the constraint resolver " +
- "of type 'DefaultInlineConstraintResolver'.");
- }
-
- [Fact]
- public void AddResolvedConstraint_ForOptionalParameter()
- {
- var builder = CreateBuilder("{controller}/{action}/{id}");
- builder.SetOptional("id");
- builder.AddResolvedConstraint("id", "int");
-
- var result = builder.Build();
- Assert.Equal(1, result.Count);
- Assert.Equal("id", result.First().Key);
- Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
- }
-
- [Fact]
- public void AddResolvedConstraint_SetOptionalParameter_AfterAddingTheParameter()
- {
- var builder = CreateBuilder("{controller}/{action}/{id}");
- builder.AddResolvedConstraint("id", "int");
- builder.SetOptional("id");
-
- var result = builder.Build();
- Assert.Equal(1, result.Count);
- Assert.Equal("id", result.First().Key);
- Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
- }
-
- [Fact]
- public void AddResolvedConstraint_And_AddConstraint_ForOptionalParameter()
- {
- var builder = CreateBuilder("{controller}/{action}/{name}");
- builder.SetOptional("name");
- builder.AddResolvedConstraint("name", "alpha");
- var minLenConstraint = new MinLengthRouteConstraint(10);
- builder.AddConstraint("name", minLenConstraint);
-
- var result = builder.Build();
- Assert.Equal(1, result.Count);
- Assert.Equal("name", result.First().Key);
- Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
- var optionalConstraint = (OptionalRouteConstraint)result.First().Value;
- var compositeConstraint = Assert.IsType<CompositeRouteConstraint>(optionalConstraint.InnerConstraint); ;
- Assert.Equal(2, compositeConstraint.Constraints.Count());
-
- Assert.Single(compositeConstraint.Constraints, c => c is MinLengthRouteConstraint);
- Assert.Single(compositeConstraint.Constraints, c => c is AlphaRouteConstraint);
- }
-
- [Theory]
- [InlineData("abc", "abc", true)] // simple case
- [InlineData("abc", "bbb|abc", true)] // Regex or
- [InlineData("Abc", "abc", true)] // Case insensitive
- [InlineData("Abc ", "abc", false)] // Matches whole (but no trimming)
- [InlineData("Abcd", "abc", false)] // Matches whole (additional non whitespace char)
- [InlineData("Abc", " abc", false)] // Matches whole (less one char)
- public void StringConstraintsMatchingScenarios(string routeValue,
- string constraintValue,
- bool shouldMatch)
- {
- // Arrange
- var routeValues = new RouteValueDictionary(new { controller = routeValue });
-
- var builder = CreateBuilder("{controller}/{action}");
- builder.AddConstraint("controller", constraintValue);
-
- var constraint = Assert.Single(builder.Build()).Value;
-
- Assert.Equal(shouldMatch,
- constraint.Match(
- httpContext: new Mock<HttpContext>().Object,
- route: new Mock<IRouter>().Object,
- routeKey: "controller",
- values: routeValues,
- routeDirection: RouteDirection.IncomingRequest));
- }
-
- private static RouteConstraintBuilder CreateBuilder(string template)
- {
- var options = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
- options
- .SetupGet(o => o.Value)
- .Returns(new RouteOptions());
-
- var inlineConstraintResolver = new DefaultInlineConstraintResolver(options.Object, new TestServiceProvider());
- return new RouteConstraintBuilder(inlineConstraintResolver, template);
- }
+ var builder = CreateBuilder("{controller}/{action}/{id}");
+ builder.AddResolvedConstraint("id", "int");
+ builder.SetOptional("id");
+
+ var result = builder.Build();
+ Assert.Equal(1, result.Count);
+ Assert.Equal("id", result.First().Key);
+ Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
+ }
+
+ [Fact]
+ public void AddResolvedConstraint_And_AddConstraint_ForOptionalParameter()
+ {
+ var builder = CreateBuilder("{controller}/{action}/{name}");
+ builder.SetOptional("name");
+ builder.AddResolvedConstraint("name", "alpha");
+ var minLenConstraint = new MinLengthRouteConstraint(10);
+ builder.AddConstraint("name", minLenConstraint);
+
+ var result = builder.Build();
+ Assert.Equal(1, result.Count);
+ Assert.Equal("name", result.First().Key);
+ Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
+ var optionalConstraint = (OptionalRouteConstraint)result.First().Value;
+ var compositeConstraint = Assert.IsType<CompositeRouteConstraint>(optionalConstraint.InnerConstraint); ;
+ Assert.Equal(2, compositeConstraint.Constraints.Count());
+
+ Assert.Single(compositeConstraint.Constraints, c => c is MinLengthRouteConstraint);
+ Assert.Single(compositeConstraint.Constraints, c => c is AlphaRouteConstraint);
+ }
+
+ [Theory]
+ [InlineData("abc", "abc", true)] // simple case
+ [InlineData("abc", "bbb|abc", true)] // Regex or
+ [InlineData("Abc", "abc", true)] // Case insensitive
+ [InlineData("Abc ", "abc", false)] // Matches whole (but no trimming)
+ [InlineData("Abcd", "abc", false)] // Matches whole (additional non whitespace char)
+ [InlineData("Abc", " abc", false)] // Matches whole (less one char)
+ public void StringConstraintsMatchingScenarios(string routeValue,
+ string constraintValue,
+ bool shouldMatch)
+ {
+ // Arrange
+ var routeValues = new RouteValueDictionary(new { controller = routeValue });
+
+ var builder = CreateBuilder("{controller}/{action}");
+ builder.AddConstraint("controller", constraintValue);
+
+ var constraint = Assert.Single(builder.Build()).Value;
+
+ Assert.Equal(shouldMatch,
+ constraint.Match(
+ httpContext: new Mock<HttpContext>().Object,
+ route: new Mock<IRouter>().Object,
+ routeKey: "controller",
+ values: routeValues,
+ routeDirection: RouteDirection.IncomingRequest));
+ }
+
+ private static RouteConstraintBuilder CreateBuilder(string template)
+ {
+ var options = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
+ options
+ .SetupGet(o => o.Value)
+ .Returns(new RouteOptions());
+
+ var inlineConstraintResolver = new DefaultInlineConstraintResolver(options.Object, new TestServiceProvider());
+ return new RouteConstraintBuilder(inlineConstraintResolver, template);
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs b/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs
index df43e7d4b1..131b72f0d2 100644
--- a/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteEndpointBuilderTest.cs
@@ -8,29 +8,28 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteEndpointBuilderTest
{
- public class RouteEndpointBuilderTest
+ [Fact]
+ public void Build_AllValuesSet_EndpointCreated()
{
- [Fact]
- public void Build_AllValuesSet_EndpointCreated()
- {
- const int defaultOrder = 0;
- var metadata = new object();
- RequestDelegate requestDelegate = (d) => null;
+ const int defaultOrder = 0;
+ var metadata = new object();
+ RequestDelegate requestDelegate = (d) => null;
- var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder)
- {
- DisplayName = "Display name!",
- Metadata = { metadata }
- };
+ var builder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse("/"), defaultOrder)
+ {
+ DisplayName = "Display name!",
+ Metadata = { metadata }
+ };
- var endpoint = Assert.IsType<RouteEndpoint>(builder.Build());
- Assert.Equal("Display name!", endpoint.DisplayName);
- Assert.Equal(defaultOrder, endpoint.Order);
- Assert.Equal(requestDelegate, endpoint.RequestDelegate);
- Assert.Equal("/", endpoint.RoutePattern.RawText);
- Assert.Equal(metadata, Assert.Single(endpoint.Metadata));
- }
+ var endpoint = Assert.IsType<RouteEndpoint>(builder.Build());
+ Assert.Equal("Display name!", endpoint.DisplayName);
+ Assert.Equal(defaultOrder, endpoint.Order);
+ Assert.Equal(requestDelegate, endpoint.RequestDelegate);
+ Assert.Equal("/", endpoint.RoutePattern.RawText);
+ Assert.Equal(metadata, Assert.Single(endpoint.Metadata));
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteHandlerOptionsTests.cs b/src/Http/Routing/test/UnitTests/RouteHandlerOptionsTests.cs
index 3bbad84808..63d72cb6de 100644
--- a/src/Http/Routing/test/UnitTests/RouteHandlerOptionsTests.cs
+++ b/src/Http/Routing/test/UnitTests/RouteHandlerOptionsTests.cs
@@ -11,71 +11,70 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteHandlerOptionsTests
{
- public class RouteHandlerOptionsTests
+ [Theory]
+ [InlineData("Development", true)]
+ [InlineData("DEVELOPMENT", true)]
+ [InlineData("Production", false)]
+ [InlineData("Custom", false)]
+ public void ThrowOnBadRequestIsTrueIfInDevelopmentEnvironmentFalseOtherwise(string environmentName, bool expectedThrowOnBadRequest)
{
- [Theory]
- [InlineData("Development", true)]
- [InlineData("DEVELOPMENT", true)]
- [InlineData("Production", false)]
- [InlineData("Custom", false)]
- public void ThrowOnBadRequestIsTrueIfInDevelopmentEnvironmentFalseOtherwise(string environmentName, bool expectedThrowOnBadRequest)
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ services.AddSingleton<IHostEnvironment>(new HostEnvironment
{
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- services.AddSingleton<IHostEnvironment>(new HostEnvironment
- {
- EnvironmentName = environmentName,
- });
- var serviceProvider = services.BuildServiceProvider();
+ EnvironmentName = environmentName,
+ });
+ var serviceProvider = services.BuildServiceProvider();
- var options = serviceProvider.GetRequiredService<IOptions<RouteHandlerOptions>>().Value;
- Assert.Equal(expectedThrowOnBadRequest, options.ThrowOnBadRequest);
- }
+ var options = serviceProvider.GetRequiredService<IOptions<RouteHandlerOptions>>().Value;
+ Assert.Equal(expectedThrowOnBadRequest, options.ThrowOnBadRequest);
+ }
- [Fact]
- public void ThrowOnBadRequestIsNotOverwrittenIfNotInDevelopmentEnvironment()
- {
- var services = new ServiceCollection();
+ [Fact]
+ public void ThrowOnBadRequestIsNotOverwrittenIfNotInDevelopmentEnvironment()
+ {
+ var services = new ServiceCollection();
- services.Configure<RouteHandlerOptions>(options =>
- {
- options.ThrowOnBadRequest = true;
- });
+ services.Configure<RouteHandlerOptions>(options =>
+ {
+ options.ThrowOnBadRequest = true;
+ });
- services.AddSingleton<IHostEnvironment>(new HostEnvironment
- {
- EnvironmentName = "Production",
- });
+ services.AddSingleton<IHostEnvironment>(new HostEnvironment
+ {
+ EnvironmentName = "Production",
+ });
- services.AddOptions();
- services.AddRouting();
+ services.AddOptions();
+ services.AddRouting();
- var serviceProvider = services.BuildServiceProvider();
+ var serviceProvider = services.BuildServiceProvider();
- var options = serviceProvider.GetRequiredService<IOptions<RouteHandlerOptions>>().Value;
- Assert.True(options.ThrowOnBadRequest);
- }
+ var options = serviceProvider.GetRequiredService<IOptions<RouteHandlerOptions>>().Value;
+ Assert.True(options.ThrowOnBadRequest);
+ }
- [Fact]
- public void RouteHandlerOptionsFailsToResolveWithoutHostEnvironment()
- {
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- var serviceProvider = services.BuildServiceProvider();
+ [Fact]
+ public void RouteHandlerOptionsFailsToResolveWithoutHostEnvironment()
+ {
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ var serviceProvider = services.BuildServiceProvider();
- Assert.Throws<InvalidOperationException>(() => serviceProvider.GetRequiredService<IOptions<RouteHandlerOptions>>());
- }
+ Assert.Throws<InvalidOperationException>(() => serviceProvider.GetRequiredService<IOptions<RouteHandlerOptions>>());
+ }
- private class HostEnvironment : IHostEnvironment
- {
- public string ApplicationName { get; set; }
- public IFileProvider ContentRootFileProvider { get; set; }
- public string ContentRootPath { get; set; }
- public string EnvironmentName { get; set; }
- }
+ private class HostEnvironment : IHostEnvironment
+ {
+ public string ApplicationName { get; set; }
+ public IFileProvider ContentRootFileProvider { get; set; }
+ public string ContentRootPath { get; set; }
+ public string EnvironmentName { get; set; }
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteOptionsTests.cs b/src/Http/Routing/test/UnitTests/RouteOptionsTests.cs
index 0169de5da2..da342ba11c 100644
--- a/src/Http/Routing/test/UnitTests/RouteOptionsTests.cs
+++ b/src/Http/Routing/test/UnitTests/RouteOptionsTests.cs
@@ -8,74 +8,73 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class RouteOptionsTests
{
- public class RouteOptionsTests
+ [Fact]
+ public void ConfigureRouting_ConfiguresOptionsProperly()
{
- [Fact]
- public void ConfigureRouting_ConfiguresOptionsProperly()
- {
- // Arrange
- var services = new ServiceCollection();
- services.AddOptions();
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOptions();
- // Act
- services.AddRouting(options => options.ConstraintMap.Add("foo", typeof(TestRouteConstraint)));
- var serviceProvider = services.BuildServiceProvider();
+ // Act
+ services.AddRouting(options => options.ConstraintMap.Add("foo", typeof(TestRouteConstraint)));
+ var serviceProvider = services.BuildServiceProvider();
- // Assert
- var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
- Assert.Equal("TestRouteConstraint", accessor.Value.ConstraintMap["foo"].Name);
- }
+ // Assert
+ var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
+ Assert.Equal("TestRouteConstraint", accessor.Value.ConstraintMap["foo"].Name);
+ }
- [Fact]
- public void EndpointDataSources_WithDependencyInjection_AddedDataSourcesAddedToEndpointDataSource()
- {
- // Arrange
- var services = new ServiceCollection();
- services.AddOptions();
- services.AddRouting();
- var serviceProvider = services.BuildServiceProvider();
+ [Fact]
+ public void EndpointDataSources_WithDependencyInjection_AddedDataSourcesAddedToEndpointDataSource()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOptions();
+ services.AddRouting();
+ var serviceProvider = services.BuildServiceProvider();
- var endpoint1 = new Endpoint((c) => Task.CompletedTask, EndpointMetadataCollection.Empty, string.Empty);
- var endpoint2 = new Endpoint((c) => Task.CompletedTask, EndpointMetadataCollection.Empty, string.Empty);
+ var endpoint1 = new Endpoint((c) => Task.CompletedTask, EndpointMetadataCollection.Empty, string.Empty);
+ var endpoint2 = new Endpoint((c) => Task.CompletedTask, EndpointMetadataCollection.Empty, string.Empty);
- var options = serviceProvider.GetRequiredService<IOptions<RouteOptions>>().Value;
- var endpointDataSource = serviceProvider.GetRequiredService<EndpointDataSource>();
+ var options = serviceProvider.GetRequiredService<IOptions<RouteOptions>>().Value;
+ var endpointDataSource = serviceProvider.GetRequiredService<EndpointDataSource>();
- // Act 1
- options.EndpointDataSources.Add(new DefaultEndpointDataSource(endpoint1));
+ // Act 1
+ options.EndpointDataSources.Add(new DefaultEndpointDataSource(endpoint1));
- // Assert 1
- var result = Assert.Single(endpointDataSource.Endpoints);
- Assert.Same(endpoint1, result);
+ // Assert 1
+ var result = Assert.Single(endpointDataSource.Endpoints);
+ Assert.Same(endpoint1, result);
- // Act 2
- options.EndpointDataSources.Add(new DefaultEndpointDataSource(endpoint2));
+ // Act 2
+ options.EndpointDataSources.Add(new DefaultEndpointDataSource(endpoint2));
- // Assert 2
- Assert.Collection(endpointDataSource.Endpoints,
- ep => Assert.Same(endpoint1, ep),
- ep => Assert.Same(endpoint2, ep));
- }
+ // Assert 2
+ Assert.Collection(endpointDataSource.Endpoints,
+ ep => Assert.Same(endpoint1, ep),
+ ep => Assert.Same(endpoint2, ep));
+ }
- private class TestRouteConstraint : IRouteConstraint
+ private class TestRouteConstraint : IRouteConstraint
+ {
+ public TestRouteConstraint(string pattern)
{
- public TestRouteConstraint(string pattern)
- {
- Pattern = pattern;
- }
+ Pattern = pattern;
+ }
- public string Pattern { get; private set; }
- public bool Match(
- HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- throw new NotImplementedException();
- }
+ public string Pattern { get; private set; }
+ public bool Match(
+ HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ throw new NotImplementedException();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteTest.cs b/src/Http/Routing/test/UnitTests/RouteTest.cs
index 97af84ed73..26030f262a 100644
--- a/src/Http/Routing/test/UnitTests/RouteTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteTest.cs
@@ -18,1858 +18,1857 @@ using Microsoft.Extensions.WebEncoders.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
-{
- public class RouteTest
- {
- private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
- private static readonly IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
-
- [Fact]
- public void CreateTemplate_InlineConstraint_Regex_Malformed()
- {
- // Arrange
- var template = @"{controller}/{action}/ {p1:regex(abc} ";
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
-
- var exception = Assert.Throws<RouteCreationException>(
- () => new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver));
-
- var expected = "An error occurred while creating the route with name '' and template" +
- $" '{template}'.";
- Assert.Equal(expected, exception.Message);
-
- Assert.NotNull(exception.InnerException);
- expected = "The constraint entry 'p1' - 'regex(abc' on the route " +
- "'{controller}/{action}/ {p1:regex(abc} ' could not be resolved by the constraint resolver of type " +
- $"'{nameof(DefaultInlineConstraintResolver)}'.";
- Assert.Equal(expected, exception.InnerException.Message);
- }
-
- [Fact]
- public async Task RouteAsync_MergesExistingRouteData_IfRouteMatches()
- {
- // Arrange
- var template = "{controller}/{action}/{id:int}";
-
- var context = CreateRouteContext("/Home/Index/5");
-
- var originalRouteDataValues = context.RouteData.Values;
- originalRouteDataValues.Add("country", "USA");
-
- var originalDataTokens = context.RouteData.DataTokens;
- originalDataTokens.Add("company", "Contoso");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var route = new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: null,
- dataTokens: new RouteValueDictionary(new { today = "Friday" }),
- inlineConstraintResolver: _inlineConstraintResolver);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(routeValues);
-
- Assert.True(routeValues.ContainsKey("country"));
- Assert.Equal("USA", routeValues["country"]);
- Assert.True(routeValues.ContainsKey("id"));
- Assert.Equal("5", routeValues["id"]);
-
- Assert.True(context.RouteData.Values.ContainsKey("country"));
- Assert.Equal("USA", context.RouteData.Values["country"]);
- Assert.True(context.RouteData.Values.ContainsKey("id"));
- Assert.Equal("5", context.RouteData.Values["id"]);
- Assert.Same(originalRouteDataValues, context.RouteData.Values);
-
- Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
- Assert.Equal("Friday", context.RouteData.DataTokens["today"]);
- Assert.Same(originalDataTokens, context.RouteData.DataTokens);
- }
-
- [Fact]
- public async Task RouteAsync_MergesExistingRouteData_PassedToConstraint()
- {
- // Arrange
- var template = "{controller}/{action}/{id:int}";
-
- var context = CreateRouteContext("/Home/Index/5");
- var originalRouteDataValues = context.RouteData.Values;
- originalRouteDataValues.Add("country", "USA");
-
- var originalDataTokens = context.RouteData.DataTokens;
- originalDataTokens.Add("company", "Contoso");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var constraint = new CapturingConstraint();
-
- var route = new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: new RouteValueDictionary(new { action = constraint }),
- dataTokens: new RouteValueDictionary(new { today = "Friday" }),
- inlineConstraintResolver: _inlineConstraintResolver);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(routeValues);
-
- Assert.True(routeValues.ContainsKey("country"));
- Assert.Equal("USA", routeValues["country"]);
- Assert.True(routeValues.ContainsKey("id"));
- Assert.Equal("5", routeValues["id"]);
-
- Assert.True(constraint.Values.ContainsKey("country"));
- Assert.Equal("USA", constraint.Values["country"]);
- Assert.True(constraint.Values.ContainsKey("id"));
- Assert.Equal("5", constraint.Values["id"]);
-
- Assert.True(context.RouteData.Values.ContainsKey("country"));
- Assert.Equal("USA", context.RouteData.Values["country"]);
- Assert.True(context.RouteData.Values.ContainsKey("id"));
- Assert.Equal("5", context.RouteData.Values["id"]);
-
- Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
- Assert.Equal("Friday", context.RouteData.DataTokens["today"]);
- }
-
- [Fact]
- public async Task RouteAsync_InlineConstraint_OptionalParameter()
- {
- // Arrange
- var template = "{controller}/{action}/{id:int?}";
-
- var context = CreateRouteContext("/Home/Index/5");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var route = new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
-
- Assert.NotEmpty(route.Constraints);
- Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.True(routeValues.ContainsKey("id"));
- Assert.Equal("5", routeValues["id"]);
-
- Assert.True(context.RouteData.Values.ContainsKey("id"));
- Assert.Equal("5", context.RouteData.Values["id"]);
- }
-
- [Fact]
- public async Task RouteAsync_InlineConstraint_Regex()
- {
- // Arrange
- var template = @"{controller}/{action}/{ssn:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}";
-
- var context = CreateRouteContext("/Home/Index/123-456-7890");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var route = new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
-
- Assert.NotEmpty(route.Constraints);
- Assert.IsType<RegexInlineRouteConstraint>(route.Constraints["ssn"]);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.True(routeValues.ContainsKey("ssn"));
- Assert.Equal("123-456-7890", routeValues["ssn"]);
-
- Assert.True(context.RouteData.Values.ContainsKey("ssn"));
- Assert.Equal("123-456-7890", context.RouteData.Values["ssn"]);
- }
-
- [Fact]
- public async Task RouteAsync_InlineConstraint_OptionalParameter_NotPresent()
- {
- // Arrange
- var template = "{controller}/{action}/{id:int?}";
-
- var context = CreateRouteContext("/Home/Index");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var route = new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
-
- Assert.NotEmpty(route.Constraints);
- Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.NotNull(routeValues);
- Assert.False(routeValues.ContainsKey("id"));
- Assert.False(context.RouteData.Values.ContainsKey("id"));
- }
-
- [Fact]
- public async Task RouteAsync_InlineConstraint_OptionalParameter_WithInConstructorConstraint()
- {
- // Arrange
- var template = "{controller}/{action}/{id:int?}";
-
- var context = CreateRouteContext("/Home/Index/5");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var constraints = new Dictionary<string, object>();
- constraints.Add("id", new RangeRouteConstraint(1, 20));
-
- var route = new Route(
- mockTarget.Object,
- template,
- defaults: null,
- constraints: constraints,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
+namespace Microsoft.AspNetCore.Routing;
- Assert.NotEmpty(route.Constraints);
- Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
- var innerConstraint = ((OptionalRouteConstraint)route.Constraints["id"]).InnerConstraint;
- Assert.IsType<CompositeRouteConstraint>(innerConstraint);
- var compositeConstraint = (CompositeRouteConstraint)innerConstraint;
- Assert.Equal(2, compositeConstraint.Constraints.Count<IRouteConstraint>());
-
- Assert.Single(compositeConstraint.Constraints, c => c is IntRouteConstraint);
- Assert.Single(compositeConstraint.Constraints, c => c is RangeRouteConstraint);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.True(routeValues.ContainsKey("id"));
- Assert.Equal("5", routeValues["id"]);
+public class RouteTest
+{
+ private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
+ private static readonly IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
- Assert.True(context.RouteData.Values.ContainsKey("id"));
- Assert.Equal("5", context.RouteData.Values["id"]);
- }
+ [Fact]
+ public void CreateTemplate_InlineConstraint_Regex_Malformed()
+ {
+ // Arrange
+ var template = @"{controller}/{action}/ {p1:regex(abc} ";
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- [Fact]
- public async Task RouteAsync_InlineConstraint_OptionalParameter_ConstraintFails()
- {
- // Arrange
- var template = "{controller}/{action}/{id:range(1,20)?}";
-
- var context = CreateRouteContext("/Home/Index/100");
-
- IDictionary<string, object> routeValues = null;
- var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
- mockTarget
- .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(ctx =>
- {
- routeValues = ctx.RouteData.Values;
- ctx.Handler = NullHandler;
- })
- .Returns(Task.FromResult(true));
-
- var route = new Route(
+ var exception = Assert.Throws<RouteCreationException>(
+ () => new Route(
mockTarget.Object,
template,
defaults: null,
constraints: null,
dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
-
- Assert.NotEmpty(route.Constraints);
- Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.Null(context.Handler);
- }
-
- // PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases.
- [Fact]
- public async Task Match_Success_LeadingSlash()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateRouteContext("/Home/Index");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(2, context.RouteData.Values.Count);
- Assert.Equal("Home", context.RouteData.Values["controller"]);
- Assert.Equal("Index", context.RouteData.Values["action"]);
- }
-
- [Fact]
- public async Task Match_Success_RootUrl()
- {
- // Arrange
- var route = CreateRoute("");
- var context = CreateRouteContext("/");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Empty(context.RouteData.Values);
- }
-
- [Fact]
- public async Task Match_Success_Defaults()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}", new { action = "Index" });
- var context = CreateRouteContext("/Home");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(2, context.RouteData.Values.Count);
- Assert.Equal("Home", context.RouteData.Values["controller"]);
- Assert.Equal("Index", context.RouteData.Values["action"]);
- }
-
- [Fact]
- public async Task Match_Success_CopiesDataTokens()
- {
- // Arrange
- var route = CreateRoute(
- "{controller}/{action}",
- defaults: new { action = "Index" },
- dataTokens: new { culture = "en-CA" });
-
- var context = CreateRouteContext("/Home");
-
- // Act
- await route.RouteAsync(context);
- Assert.NotNull(context.Handler);
-
- // This should not affect the route - RouteData.DataTokens is a copy
- context.RouteData.DataTokens.Add("company", "contoso");
-
- // Assert
- Assert.Single(route.DataTokens);
- Assert.Single(route.DataTokens, kvp => kvp.Key == "culture" && ((string)kvp.Value) == "en-CA");
- }
+ inlineConstraintResolver: _inlineConstraintResolver));
- [Fact]
- public async Task Match_Fails()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateRouteContext("/Home");
-
- // Act
- await route.RouteAsync(context);
+ var expected = "An error occurred while creating the route with name '' and template" +
+ $" '{template}'.";
+ Assert.Equal(expected, exception.Message);
- // Assert
- Assert.Null(context.Handler);
- }
+ Assert.NotNull(exception.InnerException);
+ expected = "The constraint entry 'p1' - 'regex(abc' on the route " +
+ "'{controller}/{action}/ {p1:regex(abc} ' could not be resolved by the constraint resolver of type " +
+ $"'{nameof(DefaultInlineConstraintResolver)}'.";
+ Assert.Equal(expected, exception.InnerException.Message);
+ }
- [Fact]
- public async Task Match_RejectedByHandler()
- {
- // Arrange
- var route = CreateRoute("{controller}", handleRequest: false);
- var context = CreateRouteContext("/Home");
+ [Fact]
+ public async Task RouteAsync_MergesExistingRouteData_IfRouteMatches()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id:int}";
- // Act
- await route.RouteAsync(context);
+ var context = CreateRouteContext("/Home/Index/5");
- // Assert
- Assert.Null(context.Handler);
+ var originalRouteDataValues = context.RouteData.Values;
+ originalRouteDataValues.Add("country", "USA");
- var value = Assert.Single(context.RouteData.Values);
- Assert.Equal("controller", value.Key);
- Assert.Equal("Home", Assert.IsType<string>(value.Value));
- }
+ var originalDataTokens = context.RouteData.DataTokens;
+ originalDataTokens.Add("company", "Contoso");
- [Fact]
- public async Task Match_SetsRouters()
- {
- // Arrange
- var target = CreateTarget(handleRequest: true);
- var route = CreateRoute(target, "{controller}");
- var context = CreateRouteContext("/Home");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(1, context.RouteData.Routers.Count);
- Assert.Same(target, context.RouteData.Routers[0]);
- }
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
+ {
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: new RouteValueDictionary(new { today = "Friday" }),
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(routeValues);
+
+ Assert.True(routeValues.ContainsKey("country"));
+ Assert.Equal("USA", routeValues["country"]);
+ Assert.True(routeValues.ContainsKey("id"));
+ Assert.Equal("5", routeValues["id"]);
+
+ Assert.True(context.RouteData.Values.ContainsKey("country"));
+ Assert.Equal("USA", context.RouteData.Values["country"]);
+ Assert.True(context.RouteData.Values.ContainsKey("id"));
+ Assert.Equal("5", context.RouteData.Values["id"]);
+ Assert.Same(originalRouteDataValues, context.RouteData.Values);
+
+ Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
+ Assert.Equal("Friday", context.RouteData.DataTokens["today"]);
+ Assert.Same(originalDataTokens, context.RouteData.DataTokens);
+ }
- [Fact]
- public async Task Match_RouteValuesDoesntThrowOnKeyNotFound()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateRouteContext("/Home/Index");
+ [Fact]
+ public async Task RouteAsync_MergesExistingRouteData_PassedToConstraint()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id:int}";
- // Act
- await route.RouteAsync(context);
+ var context = CreateRouteContext("/Home/Index/5");
+ var originalRouteDataValues = context.RouteData.Values;
+ originalRouteDataValues.Add("country", "USA");
- // Assert
- Assert.Null(context.RouteData.Values["1controller"]);
- }
+ var originalDataTokens = context.RouteData.DataTokens;
+ originalDataTokens.Add("company", "Contoso");
- [Fact]
- public async Task Match_Success_OptionalParameter_ValueProvided()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
- var context = CreateRouteContext("/Home/Create.xml");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(3, context.RouteData.Values.Count);
- Assert.Equal("Home", context.RouteData.Values["controller"]);
- Assert.Equal("Create", context.RouteData.Values["action"]);
- Assert.Equal("xml", context.RouteData.Values["format"]);
- }
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
+ {
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var constraint = new CapturingConstraint();
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: new RouteValueDictionary(new { action = constraint }),
+ dataTokens: new RouteValueDictionary(new { today = "Friday" }),
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(routeValues);
+
+ Assert.True(routeValues.ContainsKey("country"));
+ Assert.Equal("USA", routeValues["country"]);
+ Assert.True(routeValues.ContainsKey("id"));
+ Assert.Equal("5", routeValues["id"]);
+
+ Assert.True(constraint.Values.ContainsKey("country"));
+ Assert.Equal("USA", constraint.Values["country"]);
+ Assert.True(constraint.Values.ContainsKey("id"));
+ Assert.Equal("5", constraint.Values["id"]);
+
+ Assert.True(context.RouteData.Values.ContainsKey("country"));
+ Assert.Equal("USA", context.RouteData.Values["country"]);
+ Assert.True(context.RouteData.Values.ContainsKey("id"));
+ Assert.Equal("5", context.RouteData.Values["id"]);
+
+ Assert.Equal("Contoso", context.RouteData.DataTokens["company"]);
+ Assert.Equal("Friday", context.RouteData.DataTokens["today"]);
+ }
- [Fact]
- public async Task Match_Success_OptionalParameter_ValueNotProvided()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
- var context = CreateRouteContext("/Home/Create");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(2, context.RouteData.Values.Count);
- Assert.Equal("Home", context.RouteData.Values["controller"]);
- Assert.Equal("Create", context.RouteData.Values["action"]);
- }
+ [Fact]
+ public async Task RouteAsync_InlineConstraint_OptionalParameter()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id:int?}";
- [Fact]
- public async Task Match_Success_OptionalParameter_DefaultValue()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index", format = "xml" });
- var context = CreateRouteContext("/Home/Create");
-
- // Act
- await route.RouteAsync(context);
-
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(3, context.RouteData.Values.Count);
- Assert.Equal("Home", context.RouteData.Values["controller"]);
- Assert.Equal("Create", context.RouteData.Values["action"]);
- Assert.Equal("xml", context.RouteData.Values["format"]);
- }
+ var context = CreateRouteContext("/Home/Index/5");
- [Fact]
- public async Task Match_Success_OptionalParameter_EndsWithDot()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
- var context = CreateRouteContext("/Home/Create.");
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
+ {
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ Assert.NotEmpty(route.Constraints);
+ Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.True(routeValues.ContainsKey("id"));
+ Assert.Equal("5", routeValues["id"]);
+
+ Assert.True(context.RouteData.Values.ContainsKey("id"));
+ Assert.Equal("5", context.RouteData.Values["id"]);
+ }
- // Act
- await route.RouteAsync(context);
+ [Fact]
+ public async Task RouteAsync_InlineConstraint_Regex()
+ {
+ // Arrange
+ var template = @"{controller}/{action}/{ssn:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}";
- // Assert
- Assert.Null(context.Handler);
- }
+ var context = CreateRouteContext("/Home/Index/123-456-7890");
- private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
- {
- if (factory == null)
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
{
- factory = NullLoggerFactory.Instance;
- }
-
- var request = new Mock<HttpRequest>(MockBehavior.Strict);
- request.SetupGet(r => r.Path).Returns(requestPath);
-
- var context = new Mock<HttpContext>(MockBehavior.Strict);
- context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
- .Returns(factory);
- context.SetupGet(c => c.Request).Returns(request.Object);
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ Assert.NotEmpty(route.Constraints);
+ Assert.IsType<RegexInlineRouteConstraint>(route.Constraints["ssn"]);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.True(routeValues.ContainsKey("ssn"));
+ Assert.Equal("123-456-7890", routeValues["ssn"]);
+
+ Assert.True(context.RouteData.Values.ContainsKey("ssn"));
+ Assert.Equal("123-456-7890", context.RouteData.Values["ssn"]);
+ }
- return new RouteContext(context.Object);
- }
+ [Fact]
+ public async Task RouteAsync_InlineConstraint_OptionalParameter_NotPresent()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id:int?}";
- [Fact]
- public void GetVirtualPath_Success()
- {
- // Arrange
- var route = CreateRoute("{controller}");
- var context = CreateVirtualPathContext(new { controller = "Home" });
+ var context = CreateRouteContext("/Home/Index");
- // Act
- var pathData = route.GetVirtualPath(context);
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
+ {
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ Assert.NotEmpty(route.Constraints);
+ Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.NotNull(routeValues);
+ Assert.False(routeValues.ContainsKey("id"));
+ Assert.False(context.RouteData.Values.ContainsKey("id"));
+ }
- // Assert
- Assert.Equal("/Home", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public async Task RouteAsync_InlineConstraint_OptionalParameter_WithInConstructorConstraint()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id:int?}";
- [Fact]
- public void GetVirtualPath_Fail()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(new { controller = "Home" });
+ var context = CreateRouteContext("/Home/Index/5");
- // Act
- var path = route.GetVirtualPath(context);
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
+ {
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var constraints = new Dictionary<string, object>();
+ constraints.Add("id", new RangeRouteConstraint(1, 20));
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: constraints,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ Assert.NotEmpty(route.Constraints);
+ Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
+ var innerConstraint = ((OptionalRouteConstraint)route.Constraints["id"]).InnerConstraint;
+ Assert.IsType<CompositeRouteConstraint>(innerConstraint);
+ var compositeConstraint = (CompositeRouteConstraint)innerConstraint;
+ Assert.Equal(2, compositeConstraint.Constraints.Count<IRouteConstraint>());
+
+ Assert.Single(compositeConstraint.Constraints, c => c is IntRouteConstraint);
+ Assert.Single(compositeConstraint.Constraints, c => c is RangeRouteConstraint);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.True(routeValues.ContainsKey("id"));
+ Assert.Equal("5", routeValues["id"]);
+
+ Assert.True(context.RouteData.Values.ContainsKey("id"));
+ Assert.Equal("5", context.RouteData.Values["id"]);
+ }
- // Assert
- Assert.Null(path);
- }
+ [Fact]
+ public async Task RouteAsync_InlineConstraint_OptionalParameter_ConstraintFails()
+ {
+ // Arrange
+ var template = "{controller}/{action}/{id:range(1,20)?}";
- [Fact]
- public void GetVirtualPath_EncodesValues()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(
- new { name = "name with %special #characters" },
- new { controller = "Home", action = "Index" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index?name=name%20with%20%25special%20%23characters", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ var context = CreateRouteContext("/Home/Index/100");
- [Fact]
- public void GetVirtualPath_AlwaysUsesDefaultUrlEncoder()
- {
- // Arrange
- var nameRouteValue = "name with %special #characters Jörn";
- var expected = "/Home/Index?name=" + UrlEncoder.Default.Encode(nameRouteValue);
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
- services.AddOptions();
- services.AddRouting();
- // This test encoder should not be used by Routing and should always use the default one.
- services.AddSingleton<UrlEncoder>(new UrlTestEncoder());
- var httpContext = new DefaultHttpContext
+ IDictionary<string, object> routeValues = null;
+ var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
+ mockTarget
+ .Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(ctx =>
{
- RequestServices = services.BuildServiceProvider(),
- };
+ routeValues = ctx.RouteData.Values;
+ ctx.Handler = NullHandler;
+ })
+ .Returns(Task.FromResult(true));
+
+ var route = new Route(
+ mockTarget.Object,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+
+ Assert.NotEmpty(route.Constraints);
+ Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.Null(context.Handler);
+ }
- var context = new VirtualPathContext(
- httpContext,
- values: new RouteValueDictionary(new { name = nameRouteValue }),
- ambientValues: new RouteValueDictionary(new { controller = "Home", action = "Index" }));
+ // PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases.
+ [Fact]
+ public async Task Match_Success_LeadingSlash()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateRouteContext("/Home/Index");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(2, context.RouteData.Values.Count);
+ Assert.Equal("Home", context.RouteData.Values["controller"]);
+ Assert.Equal("Index", context.RouteData.Values["action"]);
+ }
- var route = CreateRoute("{controller}/{action}");
+ [Fact]
+ public async Task Match_Success_RootUrl()
+ {
+ // Arrange
+ var route = CreateRoute("");
+ var context = CreateRouteContext("/");
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expected, pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Empty(context.RouteData.Values);
+ }
- [Fact]
- public void GetVirtualPath_ForListOfStrings()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(
- new { color = new List<string> { "red", "green", "blue" } },
- new { controller = "Home", action = "Index" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index?color=red&color=green&color=blue", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public async Task Match_Success_Defaults()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}", new { action = "Index" });
+ var context = CreateRouteContext("/Home");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(2, context.RouteData.Values.Count);
+ Assert.Equal("Home", context.RouteData.Values["controller"]);
+ Assert.Equal("Index", context.RouteData.Values["action"]);
+ }
- [Fact]
- public void GetVirtualPath_ForListOfInts()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(
- new { items = new List<int> { 10, 20, 30 } },
- new { controller = "Home", action = "Index" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index?items=10&items=20&items=30", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public async Task Match_Success_CopiesDataTokens()
+ {
+ // Arrange
+ var route = CreateRoute(
+ "{controller}/{action}",
+ defaults: new { action = "Index" },
+ dataTokens: new { culture = "en-CA" });
- [Fact]
- public void GetVirtualPath_ForList_Empty()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(
- new { color = new List<string> { } },
- new { controller = "Home", action = "Index" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ var context = CreateRouteContext("/Home");
- [Fact]
- public void GetVirtualPath_ForList_StringWorkaround()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(
- new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" },
- new { controller = "Home", action = "Index" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index?page=1&color=red&color=green&color=blue&message=textfortest", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ await route.RouteAsync(context);
+ Assert.NotNull(context.Handler);
- [Theory]
- [MemberData(nameof(DataTokensTestData))]
- public void GetVirtualPath_ReturnsDataTokens_WhenTargetReturnsVirtualPathData(
- RouteValueDictionary dataTokens)
- {
- // Arrange
- var path = "/TestPath";
+ // This should not affect the route - RouteData.DataTokens is a copy
+ context.RouteData.DataTokens.Add("company", "contoso");
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns(() => new VirtualPathData(target.Object, path, dataTokens));
+ // Assert
+ Assert.Single(route.DataTokens);
+ Assert.Single(route.DataTokens, kvp => kvp.Key == "culture" && ((string)kvp.Value) == "en-CA");
+ }
- var routeDataTokens =
- new RouteValueDictionary() { { "ThisShouldBeIgnored", "" } };
+ [Fact]
+ public async Task Match_Fails()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateRouteContext("/Home");
- var route = CreateRoute(
- target.Object,
- "{controller}",
- defaults: null,
- dataTokens: routeDataTokens);
- var context = CreateVirtualPathContext(new { controller = path });
+ // Act
+ await route.RouteAsync(context);
- var expectedDataTokens = dataTokens ?? new RouteValueDictionary();
+ // Assert
+ Assert.Null(context.Handler);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public async Task Match_RejectedByHandler()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}", handleRequest: false);
+ var context = CreateRouteContext("/Home");
- // Assert
- Assert.NotNull(pathData);
- Assert.Same(target.Object, pathData.Router);
- Assert.Equal(path, pathData.VirtualPath);
- Assert.NotNull(pathData.DataTokens);
+ // Act
+ await route.RouteAsync(context);
- Assert.DoesNotContain(routeDataTokens.First().Key, pathData.DataTokens.Keys);
+ // Assert
+ Assert.Null(context.Handler);
- Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count);
- foreach (var dataToken in expectedDataTokens)
- {
- Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key));
- Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
- }
- }
+ var value = Assert.Single(context.RouteData.Values);
+ Assert.Equal("controller", value.Key);
+ Assert.Equal("Home", Assert.IsType<string>(value.Value));
+ }
- [Theory]
- [MemberData(nameof(DataTokensTestData))]
- public void GetVirtualPath_ReturnsDataTokens_WhenTargetReturnsNullVirtualPathData(
- RouteValueDictionary dataTokens)
- {
- // Arrange
- var path = "/TestPath";
+ [Fact]
+ public async Task Match_SetsRouters()
+ {
+ // Arrange
+ var target = CreateTarget(handleRequest: true);
+ var route = CreateRoute(target, "{controller}");
+ var context = CreateRouteContext("/Home");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(1, context.RouteData.Routers.Count);
+ Assert.Same(target, context.RouteData.Routers[0]);
+ }
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns(() => null);
+ [Fact]
+ public async Task Match_RouteValuesDoesntThrowOnKeyNotFound()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateRouteContext("/Home/Index");
- var route = CreateRoute(
- target.Object,
- "{controller}",
- defaults: null,
- dataTokens: dataTokens);
- var context = CreateVirtualPathContext(new { controller = path });
+ // Act
+ await route.RouteAsync(context);
- var expectedDataTokens = dataTokens ?? new RouteValueDictionary();
+ // Assert
+ Assert.Null(context.RouteData.Values["1controller"]);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public async Task Match_Success_OptionalParameter_ValueProvided()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
+ var context = CreateRouteContext("/Home/Create.xml");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(3, context.RouteData.Values.Count);
+ Assert.Equal("Home", context.RouteData.Values["controller"]);
+ Assert.Equal("Create", context.RouteData.Values["action"]);
+ Assert.Equal("xml", context.RouteData.Values["format"]);
+ }
- // Assert
- Assert.NotNull(pathData);
- Assert.Same(route, pathData.Router);
- Assert.Equal(path, pathData.VirtualPath);
- Assert.NotNull(pathData.DataTokens);
+ [Fact]
+ public async Task Match_Success_OptionalParameter_ValueNotProvided()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
+ var context = CreateRouteContext("/Home/Create");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(2, context.RouteData.Values.Count);
+ Assert.Equal("Home", context.RouteData.Values["controller"]);
+ Assert.Equal("Create", context.RouteData.Values["action"]);
+ }
- Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count);
- foreach (var dataToken in expectedDataTokens)
- {
- Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key));
- Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
- }
- }
+ [Fact]
+ public async Task Match_Success_OptionalParameter_DefaultValue()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index", format = "xml" });
+ var context = CreateRouteContext("/Home/Create");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(3, context.RouteData.Values.Count);
+ Assert.Equal("Home", context.RouteData.Values["controller"]);
+ Assert.Equal("Create", context.RouteData.Values["action"]);
+ Assert.Equal("xml", context.RouteData.Values["format"]);
+ }
- [Fact]
- public void GetVirtualPath_ValuesRejectedByHandler_StillGeneratesPath()
- {
- // Arrange
- var route = CreateRoute("{controller}", handleRequest: false);
- var context = CreateVirtualPathContext(new { controller = "Home" });
+ [Fact]
+ public async Task Match_Success_OptionalParameter_EndsWithDot()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}.{format?}", new { action = "Index" });
+ var context = CreateRouteContext("/Home/Create.");
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal("/Home", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Fact]
- public void GetVirtualPath_Success_AmbientValues()
+ private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
+ {
+ if (factory == null)
{
- // Arrange
- var route = CreateRoute("{controller}/{action}");
- var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Home" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
+ factory = NullLoggerFactory.Instance;
}
- [Fact]
- public void RouteGenerationRejectsConstraints()
- {
- // Arrange
- var context = CreateVirtualPathContext(new { p1 = "abcd" });
+ var request = new Mock<HttpRequest>(MockBehavior.Strict);
+ request.SetupGet(r => r.Path).Returns(requestPath);
- var route = CreateRoute(
- "{p1}/{p2}",
- new { p2 = "catchall" },
- true,
- new RouteValueDictionary(new { p2 = "\\d{4}" }));
+ var context = new Mock<HttpContext>(MockBehavior.Strict);
+ context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
+ .Returns(factory);
+ context.SetupGet(c => c.Request).Returns(request.Object);
- // Act
- var virtualPath = route.GetVirtualPath(context);
+ return new RouteContext(context.Object);
+ }
- // Assert
- Assert.Null(virtualPath);
- }
+ [Fact]
+ public void GetVirtualPath_Success()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}");
+ var context = CreateVirtualPathContext(new { controller = "Home" });
- [Fact]
- public void RouteGenerationAcceptsConstraints()
- {
- // Arrange
- var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
-
- var route = CreateRoute(
- "{p1}/{p2}",
- new { p2 = "catchall" },
- true,
- new RouteValueDictionary(new { p2 = "\\d{4}" }));
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/hello/1234", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void RouteWithCatchAllRejectsConstraints()
- {
- // Arrange
- var context = CreateVirtualPathContext(new { p1 = "abcd" });
+ // Assert
+ Assert.Equal("/Home", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var route = CreateRoute(
- "{p1}/{*p2}",
- new { p2 = "catchall" },
- true,
- new RouteValueDictionary(new { p2 = "\\d{4}" }));
+ [Fact]
+ public void GetVirtualPath_Fail()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(new { controller = "Home" });
- // Act
- var virtualPath = route.GetVirtualPath(context);
+ // Act
+ var path = route.GetVirtualPath(context);
- // Assert
- Assert.Null(virtualPath);
- }
+ // Assert
+ Assert.Null(path);
+ }
- [Fact]
- public void RouteWithCatchAllAcceptsConstraints()
- {
- // Arrange
- var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
-
- var route = CreateRoute(
- "{p1}/{*p2}",
- new { p2 = "catchall" },
- true,
- new RouteValueDictionary(new { p2 = "\\d{4}" }));
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/hello/1234", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_EncodesValues()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(
+ new { name = "name with %special #characters" },
+ new { controller = "Home", action = "Index" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index?name=name%20with%20%25special%20%23characters", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void GetVirtualPathWithNonParameterConstraintReturnsUrlWithoutQueryString()
- {
- // Arrange
- var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
-
- var target = new Mock<IRouteConstraint>();
- target
- .Setup(
- e => e.Match(
- It.IsAny<HttpContext>(),
- It.IsAny<IRouter>(),
- It.IsAny<string>(),
- It.IsAny<RouteValueDictionary>(),
- It.IsAny<RouteDirection>()))
- .Returns(true)
- .Verifiable();
-
- var route = CreateRoute(
- "{p1}/{p2}",
- new { p2 = "catchall" },
- true,
- new RouteValueDictionary(new { p2 = target.Object }));
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/hello/1234", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
-
- target.VerifyAll();
- }
+ [Fact]
+ public void GetVirtualPath_AlwaysUsesDefaultUrlEncoder()
+ {
+ // Arrange
+ var nameRouteValue = "name with %special #characters Jörn";
+ var expected = "/Home/Index?name=" + UrlEncoder.Default.Encode(nameRouteValue);
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+ services.AddOptions();
+ services.AddRouting();
+ // This test encoder should not be used by Routing and should always use the default one.
+ services.AddSingleton<UrlEncoder>(new UrlTestEncoder());
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = services.BuildServiceProvider(),
+ };
+
+ var context = new VirtualPathContext(
+ httpContext,
+ values: new RouteValueDictionary(new { name = nameRouteValue }),
+ ambientValues: new RouteValueDictionary(new { controller = "Home", action = "Index" }));
+
+ var route = CreateRoute("{controller}/{action}");
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal(expected, pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Any ambient values from the current request should be visible to constraint, even
- // if they have nothing to do with the route generating a link
- [Fact]
- public void GetVirtualPath_ConstraintsSeeAmbientValues()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var route = CreateRoute(
- template: "slug/{controller}/{action}",
- defaults: null,
- handleRequest: true,
- constraints: new { c = constraint });
+ [Fact]
+ public void GetVirtualPath_ForListOfStrings()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(
+ new { color = new List<string> { "red", "green", "blue" } },
+ new { controller = "Home", action = "Index" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index?color=red&color=green&color=blue", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(
- values: new { action = "Store" },
- ambientValues: new { Controller = "Home", action = "Blog", extra = "42" });
+ [Fact]
+ public void GetVirtualPath_ForListOfInts()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(
+ new { items = new List<int> { 10, 20, 30 } },
+ new { controller = "Home", action = "Index" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index?items=10&items=20&items=30", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var expectedValues = new RouteValueDictionary(
- new { controller = "Home", action = "Store", extra = "42" });
+ [Fact]
+ public void GetVirtualPath_ForList_Empty()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(
+ new { color = new List<string> { } },
+ new { controller = "Home", action = "Index" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_ForList_StringWorkaround()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(
+ new { page = 1, color = new List<string> { "red", "green", "blue" }, message = "textfortest" },
+ new { controller = "Home", action = "Index" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index?page=1&color=red&color=green&color=blue&message=textfortest", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.Equal("/slug/Home/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
+ [Theory]
+ [MemberData(nameof(DataTokensTestData))]
+ public void GetVirtualPath_ReturnsDataTokens_WhenTargetReturnsVirtualPathData(
+ RouteValueDictionary dataTokens)
+ {
+ // Arrange
+ var path = "/TestPath";
- Assert.Equal(expectedValues, constraint.Values);
- }
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns(() => new VirtualPathData(target.Object, path, dataTokens));
- // Non-parameter default values from the routing generating a link are not in the 'values'
- // collection when constraints are processed.
- [Fact]
- public void GetVirtualPath_ConstraintsDontSeeDefaults_WhenTheyArentParameters()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var route = CreateRoute(
- template: "slug/{controller}/{action}",
- defaults: new { otherthing = "17" },
- handleRequest: true,
- constraints: new { c = constraint });
-
- var context = CreateVirtualPathContext(
- values: new { action = "Store" },
- ambientValues: new { Controller = "Home", action = "Blog" });
-
- var expectedValues = new RouteValueDictionary(
- new { controller = "Home", action = "Store" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/slug/Home/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
-
- Assert.Equal(expectedValues, constraint.Values);
- }
+ var routeDataTokens =
+ new RouteValueDictionary() { { "ThisShouldBeIgnored", "" } };
- // Default values are visible to the constraint when they are used to fill a parameter.
- [Fact]
- public void GetVirtualPath_ConstraintsSeesDefault_WhenThereItsAParameter()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var route = CreateRoute(
- template: "slug/{controller}/{action}",
- defaults: new { action = "Index" },
- handleRequest: true,
- constraints: new { c = constraint });
-
- var context = CreateVirtualPathContext(
- values: new { controller = "Shopping" },
- ambientValues: new { Controller = "Home", action = "Blog" });
-
- var expectedValues = new RouteValueDictionary(
- new { controller = "Shopping", action = "Index" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/slug/Shopping", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
-
- Assert.Equal(expectedValues, constraint.Values);
- }
+ var route = CreateRoute(
+ target.Object,
+ "{controller}",
+ defaults: null,
+ dataTokens: routeDataTokens);
+ var context = CreateVirtualPathContext(new { controller = path });
- // Default values from the routing generating a link are in the 'values' collection when
- // constraints are processed - IFF they are specified as values or ambient values.
- [Fact]
- public void GetVirtualPath_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient()
- {
- // Arrange
- var constraint = new CapturingConstraint();
- var route = CreateRoute(
- template: "slug/{controller}/{action}",
- defaults: new { otherthing = "17", thirdthing = "13" },
- handleRequest: true,
- constraints: new { c = constraint });
-
- var context = CreateVirtualPathContext(
- values: new { action = "Store", thirdthing = "13" },
- ambientValues: new { Controller = "Home", action = "Blog", otherthing = "17" });
-
- var expectedValues = new RouteValueDictionary(
- new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/slug/Home/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
-
- Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
- }
+ var expectedDataTokens = dataTokens ?? new RouteValueDictionary();
- [Fact]
- public void GetVirtualPath_InlineConstraints_Success()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}/{id:int}");
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", id = 4 });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index/4", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void GetVirtualPath_InlineConstraints_NonMatchingvalue()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}/{id:int}");
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", id = "asf" });
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Same(target.Object, pathData.Router);
+ Assert.Equal(path, pathData.VirtualPath);
+ Assert.NotNull(pathData.DataTokens);
- // Act
- var path = route.GetVirtualPath(context);
+ Assert.DoesNotContain(routeDataTokens.First().Key, pathData.DataTokens.Keys);
- // Assert
- Assert.Null(path);
- }
-
- [Fact]
- public void GetVirtualPath_InlineConstraints_OptionalParameter_ValuePresent()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}/{id:int?}");
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", id = 98 });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index/98", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
-
- [Fact]
- public void GetVirtualPath_InlineConstraints_OptionalParameter_ValueNotPresent()
+ Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count);
+ foreach (var dataToken in expectedDataTokens)
{
- // Arrange
- var route = CreateRoute("{controller}/{action}/{id:int?}");
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
+ Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key));
+ Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
}
+ }
- [Fact]
- public void GetVirtualPath_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}/{id:int?}");
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", id = "sdfd" });
-
- // Act
- var path = route.GetVirtualPath(context);
-
- // Assert
- Assert.Null(path);
- }
+ [Theory]
+ [MemberData(nameof(DataTokensTestData))]
+ public void GetVirtualPath_ReturnsDataTokens_WhenTargetReturnsNullVirtualPathData(
+ RouteValueDictionary dataTokens)
+ {
+ // Arrange
+ var path = "/TestPath";
- [Fact]
- public void GetVirtualPath_InlineConstraints_CompositeInlineConstraint()
- {
- // Arrange
- var route = CreateRoute("{controller}/{action}/{id:int:range(1,20)}");
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", id = 14 });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index/14", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns(() => null);
- [Fact]
- public void GetVirtualPath_InlineConstraints_CompositeConstraint_FromConstructor()
- {
- // Arrange
- var constraint = new MaxLengthRouteConstraint(20);
- var route = CreateRoute(
- template: "{controller}/{action}/{name:alpha}",
- defaults: null,
- handleRequest: true,
- constraints: new { name = constraint });
+ var route = CreateRoute(
+ target.Object,
+ "{controller}",
+ defaults: null,
+ dataTokens: dataTokens);
+ var context = CreateVirtualPathContext(new { controller = path });
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products" });
+ var expectedDataTokens = dataTokens ?? new RouteValueDictionary();
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Act
+ var pathData = route.GetVirtualPath(context);
- // Assert
- Assert.Equal("/Home/Index/products", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Same(route, pathData.Router);
+ Assert.Equal(path, pathData.VirtualPath);
+ Assert.NotNull(pathData.DataTokens);
- [Fact]
- public void GetVirtualPath_OptionalParameter_ParameterPresentInValues()
+ Assert.Equal(expectedDataTokens.Count, pathData.DataTokens.Count);
+ foreach (var dataToken in expectedDataTokens)
{
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/{name}.{format?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
-
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products", format = "xml" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.Equal("/Home/Index/products.xml", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
+ Assert.True(pathData.DataTokens.ContainsKey(dataToken.Key));
+ Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
}
+ }
- [Fact]
- public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/{name}.{format?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ [Fact]
+ public void GetVirtualPath_ValuesRejectedByHandler_StillGeneratesPath()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}", handleRequest: false);
+ var context = CreateVirtualPathContext(new { controller = "Home" });
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products" });
+ // Act
+ var pathData = route.GetVirtualPath(context);
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Assert
+ Assert.Equal("/Home", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.Equal("/Home/Index/products", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_Success_AmbientValues()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}");
+ var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Home" });
- [Fact]
- public void GetVirtualPath_OptionalParameter_ParameterPresentInValuesAndDefaults()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/{name}.{format?}",
- defaults: new { format = "json" },
- handleRequest: true,
- constraints: null);
+ // Act
+ var pathData = route.GetVirtualPath(context);
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products", format = "xml" });
+ // Assert
+ Assert.Equal("/Home/Index", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void RouteGenerationRejectsConstraints()
+ {
+ // Arrange
+ var context = CreateVirtualPathContext(new { p1 = "abcd" });
- // Assert
- Assert.Equal("/Home/Index/products.xml", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ var route = CreateRoute(
+ "{p1}/{p2}",
+ new { p2 = "catchall" },
+ true,
+ new RouteValueDictionary(new { p2 = "\\d{4}" }));
- [Fact]
- public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/{name}.{format?}",
- defaults: new { format = "json" },
- handleRequest: true,
- constraints: null);
+ // Act
+ var virtualPath = route.GetVirtualPath(context);
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products" });
+ // Assert
+ Assert.Null(virtualPath);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void RouteGenerationAcceptsConstraints()
+ {
+ // Arrange
+ var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
+
+ var route = CreateRoute(
+ "{p1}/{p2}",
+ new { p2 = "catchall" },
+ true,
+ new RouteValueDictionary(new { p2 = "\\d{4}" }));
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/hello/1234", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.Equal("/Home/Index/products", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void RouteWithCatchAllRejectsConstraints()
+ {
+ // Arrange
+ var context = CreateVirtualPathContext(new { p1 = "abcd" });
- [Fact]
- public void GetVirtualPath_OptionalParameter_ParameterNotPresentInTemplate_PresentInValues()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/{name}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ var route = CreateRoute(
+ "{p1}/{*p2}",
+ new { p2 = "catchall" },
+ true,
+ new RouteValueDictionary(new { p2 = "\\d{4}" }));
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products", format = "json" });
+ // Act
+ var virtualPath = route.GetVirtualPath(context);
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Assert
+ Assert.Null(virtualPath);
+ }
- // Assert
- Assert.Equal("/Home/Index/products?format=json", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void RouteWithCatchAllAcceptsConstraints()
+ {
+ // Arrange
+ var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
+
+ var route = CreateRoute(
+ "{p1}/{*p2}",
+ new { p2 = "catchall" },
+ true,
+ new RouteValueDictionary(new { p2 = "\\d{4}" }));
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/hello/1234", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/.{name?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ [Fact]
+ public void GetVirtualPathWithNonParameterConstraintReturnsUrlWithoutQueryString()
+ {
+ // Arrange
+ var context = CreateVirtualPathContext(new { p1 = "hello", p2 = "1234" });
+
+ var target = new Mock<IRouteConstraint>();
+ target
+ .Setup(
+ e => e.Match(
+ It.IsAny<HttpContext>(),
+ It.IsAny<IRouter>(),
+ It.IsAny<string>(),
+ It.IsAny<RouteValueDictionary>(),
+ It.IsAny<RouteDirection>()))
+ .Returns(true)
+ .Verifiable();
+
+ var route = CreateRoute(
+ "{p1}/{p2}",
+ new { p2 = "catchall" },
+ true,
+ new RouteValueDictionary(new { p2 = target.Object }));
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/hello/1234", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+
+ target.VerifyAll();
+ }
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home", name = "products" });
+ // Any ambient values from the current request should be visible to constraint, even
+ // if they have nothing to do with the route generating a link
+ [Fact]
+ public void GetVirtualPath_ConstraintsSeeAmbientValues()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var route = CreateRoute(
+ template: "slug/{controller}/{action}",
+ defaults: null,
+ handleRequest: true,
+ constraints: new { c = constraint });
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Store" },
+ ambientValues: new { Controller = "Home", action = "Blog", extra = "42" });
+
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Home", action = "Store", extra = "42" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/slug/Home/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+
+ Assert.Equal(expectedValues, constraint.Values);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Non-parameter default values from the routing generating a link are not in the 'values'
+ // collection when constraints are processed.
+ [Fact]
+ public void GetVirtualPath_ConstraintsDontSeeDefaults_WhenTheyArentParameters()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var route = CreateRoute(
+ template: "slug/{controller}/{action}",
+ defaults: new { otherthing = "17" },
+ handleRequest: true,
+ constraints: new { c = constraint });
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Store" },
+ ambientValues: new { Controller = "Home", action = "Blog" });
+
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Home", action = "Store" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/slug/Home/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+
+ Assert.Equal(expectedValues, constraint.Values);
+ }
- // Assert
- Assert.Equal("/Home/Index/.products", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Default values are visible to the constraint when they are used to fill a parameter.
+ [Fact]
+ public void GetVirtualPath_ConstraintsSeesDefault_WhenThereItsAParameter()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var route = CreateRoute(
+ template: "slug/{controller}/{action}",
+ defaults: new { action = "Index" },
+ handleRequest: true,
+ constraints: new { c = constraint });
+
+ var context = CreateVirtualPathContext(
+ values: new { controller = "Shopping" },
+ ambientValues: new { Controller = "Home", action = "Blog" });
+
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Shopping", action = "Index" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/slug/Shopping", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+
+ Assert.Equal(expectedValues, constraint.Values);
+ }
- [Fact]
- public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/.{name?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ // Default values from the routing generating a link are in the 'values' collection when
+ // constraints are processed - IFF they are specified as values or ambient values.
+ [Fact]
+ public void GetVirtualPath_ConstraintsSeeDefaults_IfTheyAreSpecifiedOrAmbient()
+ {
+ // Arrange
+ var constraint = new CapturingConstraint();
+ var route = CreateRoute(
+ template: "slug/{controller}/{action}",
+ defaults: new { otherthing = "17", thirdthing = "13" },
+ handleRequest: true,
+ constraints: new { c = constraint });
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Store", thirdthing = "13" },
+ ambientValues: new { Controller = "Home", action = "Blog", otherthing = "17" });
+
+ var expectedValues = new RouteValueDictionary(
+ new { controller = "Home", action = "Store", otherthing = "17", thirdthing = "13" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/slug/Home/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+
+ Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
+ }
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home" });
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_Success()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}/{id:int}");
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", id = 4 });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/4", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_NonMatchingvalue()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}/{id:int}");
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", id = "asf" });
- // Assert
- Assert.Equal("/Home/Index/", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var path = route.GetVirtualPath(context);
- [Fact]
- public void GetVirtualPath_OptionalParameter_InSimpleSegment()
- {
- // Arrange
- var route = CreateRoute(
- template: "{controller}/{action}/{name?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ // Assert
+ Assert.Null(path);
+ }
- var context = CreateVirtualPathContext(
- values: new { action = "Index", controller = "Home" });
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_OptionalParameter_ValuePresent()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}/{id:int?}");
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", id = 98 });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/98", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_OptionalParameter_ValueNotPresent()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}/{id:int?}");
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.Equal("/Home/Index", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}/{id:int?}");
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", id = "sdfd" });
- [Fact]
- public void GetVirtualPath_TwoOptionalParameters_OneValueFromAmbientValues()
- {
- // Arrange
- var route = CreateRoute(
- template: "a/{b=15}/{c?}/{d?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ // Act
+ var path = route.GetVirtualPath(context);
- var context = CreateVirtualPathContext(
- values: new { },
- ambientValues: new { c = "17" });
+ // Assert
+ Assert.Null(path);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_CompositeInlineConstraint()
+ {
+ // Arrange
+ var route = CreateRoute("{controller}/{action}/{id:int:range(1,20)}");
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", id = 14 });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/14", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/a/15/17", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_InlineConstraints_CompositeConstraint_FromConstructor()
+ {
+ // Arrange
+ var constraint = new MaxLengthRouteConstraint(20);
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name:alpha}",
+ defaults: null,
+ handleRequest: true,
+ constraints: new { name = constraint });
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/products", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_ParameterPresentInValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name}.{format?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products", format = "xml" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/products.xml", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void GetVirtualPath_OptionalParameterAfterDefault_OneValueFromAmbientValues()
- {
- // Arrange
- var route = CreateRoute(
- template: "a/{b=15}/{c?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name}.{format?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/products", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(
- values: new { },
- ambientValues: new { c = "17" });
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_ParameterPresentInValuesAndDefaults()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name}.{format?}",
+ defaults: new { format = "json" },
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products", format = "xml" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/products.xml", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_ParameterNotPresentInValues_PresentInDefaults()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name}.{format?}",
+ defaults: new { format = "json" },
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/products", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/a/15/17", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_ParameterNotPresentInTemplate_PresentInValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products", format = "json" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/products?format=json", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void GetVirtualPath_TwoOptionalParametersAfterDefault_OneValueFromAmbientValues()
- {
- // Arrange
- var route = CreateRoute(
- template: "a/{b=15}/{c?}/{d?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterPresent()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/.{name?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home", name = "products" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/.products", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(
- values: new { },
- ambientValues: new { c = "17" });
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_FollowedByDotAfterSlash_ParameterNotPresent()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/.{name?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index/", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_OptionalParameter_InSimpleSegment()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "{controller}/{action}/{name?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { action = "Index", controller = "Home" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.Equal("/Home/Index", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/a/15/17", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_TwoOptionalParameters_OneValueFromAmbientValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "a/{b=15}/{c?}/{d?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { },
+ ambientValues: new { c = "17" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/a/15/17", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void GetVirtualPath_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
- {
- // Arrange
- var route = CreateRoute(
- template: "a/{b=15}/{c?}/{d?}",
- defaults: null,
- handleRequest: true,
- constraints: null);
- var context = CreateVirtualPathContext(
- values: new { },
- ambientValues: new { d = "17" });
+ [Fact]
+ public void GetVirtualPath_OptionalParameterAfterDefault_OneValueFromAmbientValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "a/{b=15}/{c?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { },
+ ambientValues: new { c = "17" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/a/15/17", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- var pathData = route.GetVirtualPath(context);
+ [Fact]
+ public void GetVirtualPath_TwoOptionalParametersAfterDefault_OneValueFromAmbientValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "a/{b=15}/{c?}/{d?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { },
+ ambientValues: new { c = "17" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/a/15/17", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/a", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ [Fact]
+ public void GetVirtualPath_TwoOptionalParametersAfterDefault_LastValueFromAmbientValues()
+ {
+ // Arrange
+ var route = CreateRoute(
+ template: "a/{b=15}/{c?}/{d?}",
+ defaults: null,
+ handleRequest: true,
+ constraints: null);
+
+ var context = CreateVirtualPathContext(
+ values: new { },
+ ambientValues: new { d = "17" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/a", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- private static VirtualPathContext CreateVirtualPathContext(object values)
- {
- return CreateVirtualPathContext(new RouteValueDictionary(values), null);
- }
+ private static VirtualPathContext CreateVirtualPathContext(object values)
+ {
+ return CreateVirtualPathContext(new RouteValueDictionary(values), null);
+ }
- private static VirtualPathContext CreateVirtualPathContext(object values, object ambientValues)
- {
- return CreateVirtualPathContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues));
- }
+ private static VirtualPathContext CreateVirtualPathContext(object values, object ambientValues)
+ {
+ return CreateVirtualPathContext(new RouteValueDictionary(values), new RouteValueDictionary(ambientValues));
+ }
- private static VirtualPathContext CreateVirtualPathContext(
- RouteValueDictionary values,
- RouteValueDictionary ambientValues)
- {
- var services = new ServiceCollection();
- services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
- services.AddOptions();
- services.AddRouting();
+ private static VirtualPathContext CreateVirtualPathContext(
+ RouteValueDictionary values,
+ RouteValueDictionary ambientValues)
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
+ services.AddOptions();
+ services.AddRouting();
- var context = new DefaultHttpContext
- {
- RequestServices = services.BuildServiceProvider(),
- };
+ var context = new DefaultHttpContext
+ {
+ RequestServices = services.BuildServiceProvider(),
+ };
- return new VirtualPathContext(context, ambientValues, values);
- }
+ return new VirtualPathContext(context, ambientValues, values);
+ }
- private static VirtualPathContext CreateVirtualPathContext(string routeName)
- {
- return new VirtualPathContext(null, null, null, routeName);
- }
+ private static VirtualPathContext CreateVirtualPathContext(string routeName)
+ {
+ return new VirtualPathContext(null, null, null, routeName);
+ }
- public static IEnumerable<object[]> DataTokens
+ public static IEnumerable<object[]> DataTokens
+ {
+ get
{
- get
- {
- yield return new object[] {
+ yield return new object[] {
new Dictionary<string, object> { { "key1", "data1" }, { "key2", 13 } },
new Dictionary<string, object> { { "key1", "data1" }, { "key2", 13 } },
};
- yield return new object[] {
+ yield return new object[] {
new RouteValueDictionary { { "key1", "data1" }, { "key2", 13 } },
new Dictionary<string, object> { { "key1", "data1" }, { "key2", 13 } },
};
- yield return new object[] {
+ yield return new object[] {
new object(),
new Dictionary<string,object>(),
};
- yield return new object[] {
+ yield return new object[] {
null,
new Dictionary<string, object>()
};
- yield return new object[] {
+ yield return new object[] {
new { key1 = "data1", key2 = 13 },
new Dictionary<string, object> { { "key1", "data1" }, { "key2", 13 } },
};
- }
}
+ }
- [Theory]
- [MemberData(nameof(DataTokens))]
- public void RegisteringRoute_WithDataTokens_AbleToAddTheRoute(object dataToken,
- IDictionary<string, object> expectedDictionary)
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ [Theory]
+ [MemberData(nameof(DataTokens))]
+ public void RegisteringRoute_WithDataTokens_AbleToAddTheRoute(object dataToken,
+ IDictionary<string, object> expectedDictionary)
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- // Act
- routeBuilder.MapRoute("mockName",
- "{controller}/{action}",
- defaults: null,
- constraints: null,
- dataTokens: dataToken);
+ // Act
+ routeBuilder.MapRoute("mockName",
+ "{controller}/{action}",
+ defaults: null,
+ constraints: null,
+ dataTokens: dataToken);
- // Assert
- var templateRoute = (Route)routeBuilder.Routes[0];
+ // Assert
+ var templateRoute = (Route)routeBuilder.Routes[0];
- Assert.Equal(expectedDictionary.Count, templateRoute.DataTokens.Count);
- foreach (var expectedKey in expectedDictionary.Keys)
- {
- Assert.True(templateRoute.DataTokens.ContainsKey(expectedKey));
- Assert.Equal(expectedDictionary[expectedKey], templateRoute.DataTokens[expectedKey]);
- }
- }
-
- [Fact]
- public void RegisteringRoute_WithParameterPolicy_AbleToAddTheRoute()
+ Assert.Equal(expectedDictionary.Count, templateRoute.DataTokens.Count);
+ foreach (var expectedKey in expectedDictionary.Keys)
{
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ Assert.True(templateRoute.DataTokens.ContainsKey(expectedKey));
+ Assert.Equal(expectedDictionary[expectedKey], templateRoute.DataTokens[expectedKey]);
+ }
+ }
- // Act
- routeBuilder.MapRoute("mockName",
- "{controller:test-policy}/{action}");
+ [Fact]
+ public void RegisteringRoute_WithParameterPolicy_AbleToAddTheRoute()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- // Assert
- var templateRoute = (Route)routeBuilder.Routes[0];
+ // Act
+ routeBuilder.MapRoute("mockName",
+ "{controller:test-policy}/{action}");
- Assert.Empty(templateRoute.Constraints);
- }
+ // Assert
+ var templateRoute = (Route)routeBuilder.Routes[0];
- [Fact]
- public void RegisteringRouteWithInvalidConstraints_Throws()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
-
- // Assert
- var expectedMessage = "An error occurred while creating the route with name 'mockName' and template" +
- " '{controller}/{action}'.";
-
- var exception = ExceptionAssert.Throws<RouteCreationException>(
- () => routeBuilder.MapRoute("mockName",
- "{controller}/{action}",
- defaults: null,
- constraints: new { controller = "a.*", action = 17 }),
- expectedMessage);
-
- expectedMessage = "The constraint entry 'action' - '17' on the route '{controller}/{action}' " +
- "must have a string value or be of a type which implements '" +
- typeof(IRouteConstraint) + "'.";
- Assert.NotNull(exception.InnerException);
- Assert.Equal(expectedMessage, exception.InnerException.Message);
- }
+ Assert.Empty(templateRoute.Constraints);
+ }
- [Fact]
- public void RegisteringRouteWithTwoConstraints()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ [Fact]
+ public void RegisteringRouteWithInvalidConstraints_Throws()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- var mockConstraint = new Mock<IRouteConstraint>().Object;
+ // Assert
+ var expectedMessage = "An error occurred while creating the route with name 'mockName' and template" +
+ " '{controller}/{action}'.";
- routeBuilder.MapRoute("mockName",
+ var exception = ExceptionAssert.Throws<RouteCreationException>(
+ () => routeBuilder.MapRoute("mockName",
"{controller}/{action}",
defaults: null,
- constraints: new { controller = "a.*", action = mockConstraint });
+ constraints: new { controller = "a.*", action = 17 }),
+ expectedMessage);
+
+ expectedMessage = "The constraint entry 'action' - '17' on the route '{controller}/{action}' " +
+ "must have a string value or be of a type which implements '" +
+ typeof(IRouteConstraint) + "'.";
+ Assert.NotNull(exception.InnerException);
+ Assert.Equal(expectedMessage, exception.InnerException.Message);
+ }
- var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
+ [Fact]
+ public void RegisteringRouteWithTwoConstraints()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- // Assert
- Assert.Equal(2, constraints.Count);
- Assert.IsType<RegexRouteConstraint>(constraints["controller"]);
- Assert.Equal(mockConstraint, constraints["action"]);
- }
+ var mockConstraint = new Mock<IRouteConstraint>().Object;
- [Fact]
- public void RegisteringRouteWithOneInlineConstraintAndOneUsingConstraintArgument()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ routeBuilder.MapRoute("mockName",
+ "{controller}/{action}",
+ defaults: null,
+ constraints: new { controller = "a.*", action = mockConstraint });
- // Act
- routeBuilder.MapRoute("mockName",
- "{controller}/{action}/{id:int}",
- defaults: null,
- constraints: new { id = "1*" });
-
- // Assert
- var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
- Assert.Equal(1, constraints.Count);
- var constraint = (CompositeRouteConstraint)constraints["id"];
- Assert.IsType<CompositeRouteConstraint>(constraint);
- Assert.IsType<RegexRouteConstraint>(constraint.Constraints.ElementAt(0));
- Assert.IsType<IntRouteConstraint>(constraint.Constraints.ElementAt(1));
- }
+ var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
- [Fact]
- public void RegisteringRoute_WithOneInlineConstraint_AddsItToConstraintCollection()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ // Assert
+ Assert.Equal(2, constraints.Count);
+ Assert.IsType<RegexRouteConstraint>(constraints["controller"]);
+ Assert.Equal(mockConstraint, constraints["action"]);
+ }
- // Act
- routeBuilder.MapRoute("mockName",
- "{controller}/{action}/{id:int}",
- defaults: null,
- constraints: null);
+ [Fact]
+ public void RegisteringRouteWithOneInlineConstraintAndOneUsingConstraintArgument()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
+
+ // Act
+ routeBuilder.MapRoute("mockName",
+ "{controller}/{action}/{id:int}",
+ defaults: null,
+ constraints: new { id = "1*" });
+
+ // Assert
+ var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
+ Assert.Equal(1, constraints.Count);
+ var constraint = (CompositeRouteConstraint)constraints["id"];
+ Assert.IsType<CompositeRouteConstraint>(constraint);
+ Assert.IsType<RegexRouteConstraint>(constraint.Constraints.ElementAt(0));
+ Assert.IsType<IntRouteConstraint>(constraint.Constraints.ElementAt(1));
+ }
- // Assert
- var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
- Assert.Equal(1, constraints.Count);
- Assert.IsType<IntRouteConstraint>(constraints["id"]);
- }
+ [Fact]
+ public void RegisteringRoute_WithOneInlineConstraint_AddsItToConstraintCollection()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
+
+ // Act
+ routeBuilder.MapRoute("mockName",
+ "{controller}/{action}/{id:int}",
+ defaults: null,
+ constraints: null);
+
+ // Assert
+ var constraints = ((Route)routeBuilder.Routes[0]).Constraints;
+ Assert.Equal(1, constraints.Count);
+ Assert.IsType<IntRouteConstraint>(constraints["id"]);
+ }
- [Fact]
- public void RegisteringRouteWithRouteName_WithNullDefaults_AddsTheRoute()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ [Fact]
+ public void RegisteringRouteWithRouteName_WithNullDefaults_AddsTheRoute()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- routeBuilder.MapRoute(name: "RouteName", template: "{controller}/{action}", defaults: null);
+ routeBuilder.MapRoute(name: "RouteName", template: "{controller}/{action}", defaults: null);
- // Act
- var name = ((Route)routeBuilder.Routes[0]).Name;
+ // Act
+ var name = ((Route)routeBuilder.Routes[0]).Name;
- // Assert
- Assert.Equal("RouteName", name);
- }
+ // Assert
+ Assert.Equal("RouteName", name);
+ }
- [Fact]
- public void RegisteringRouteWithRouteName_WithNullDefaultsAndConstraints_AddsTheRoute()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
+ [Fact]
+ public void RegisteringRouteWithRouteName_WithNullDefaultsAndConstraints_AddsTheRoute()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- routeBuilder.MapRoute(name: "RouteName",
- template: "{controller}/{action}",
- defaults: null,
- constraints: null);
+ routeBuilder.MapRoute(name: "RouteName",
+ template: "{controller}/{action}",
+ defaults: null,
+ constraints: null);
- // Act
- var name = ((Route)routeBuilder.Routes[0]).Name;
+ // Act
+ var name = ((Route)routeBuilder.Routes[0]).Name;
- // Assert
- Assert.Equal("RouteName", name);
- }
+ // Assert
+ Assert.Equal("RouteName", name);
+ }
- [Theory]
- [InlineData("///")]
- [InlineData("/a//")]
- [InlineData("/a/b//")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public async Task RouteAsync_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
- {
- // Arrange
- var builder = CreateRouteBuilder();
+ [Theory]
+ [InlineData("///")]
+ [InlineData("/a//")]
+ [InlineData("/a/b//")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public async Task RouteAsync_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
+ {
+ // Arrange
+ var builder = CreateRouteBuilder();
- builder.MapRoute(name: null,
- template: "{controller?}/{action?}/{id?}",
- defaults: null,
- constraints: null);
+ builder.MapRoute(name: null,
+ template: "{controller?}/{action?}/{id?}",
+ defaults: null,
+ constraints: null);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- // DataTokens test data for TemplateRoute.GetVirtualPath
- public static IEnumerable<object[]> DataTokensTestData
+ // DataTokens test data for TemplateRoute.GetVirtualPath
+ public static IEnumerable<object[]> DataTokensTestData
+ {
+ get
{
- get
- {
- yield return new object[] { null };
- yield return new object[] { new RouteValueDictionary() };
- yield return new object[] { new RouteValueDictionary() { { "tokenKeyA", "tokenValueA" } } };
- }
+ yield return new object[] { null };
+ yield return new object[] { new RouteValueDictionary() };
+ yield return new object[] { new RouteValueDictionary() { { "tokenKeyA", "tokenValueA" } } };
}
+ }
- private static IRouteBuilder CreateRouteBuilder()
- {
- var services = new ServiceCollection();
- services.AddSingleton<IInlineConstraintResolver>(_inlineConstraintResolver);
- services.AddSingleton<RoutingMarkerService>();
- services.AddSingleton<ParameterPolicyFactory, DefaultParameterPolicyFactory>();
- services.Configure<RouteOptions>(ConfigureRouteOptions);
-
- var applicationBuilder = Mock.Of<IApplicationBuilder>();
- applicationBuilder.ApplicationServices = services.BuildServiceProvider();
-
- var routeBuilder = new RouteBuilder(applicationBuilder);
- routeBuilder.DefaultHandler = new RouteHandler(NullHandler);
- return routeBuilder;
- }
+ private static IRouteBuilder CreateRouteBuilder()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<IInlineConstraintResolver>(_inlineConstraintResolver);
+ services.AddSingleton<RoutingMarkerService>();
+ services.AddSingleton<ParameterPolicyFactory, DefaultParameterPolicyFactory>();
+ services.Configure<RouteOptions>(ConfigureRouteOptions);
+
+ var applicationBuilder = Mock.Of<IApplicationBuilder>();
+ applicationBuilder.ApplicationServices = services.BuildServiceProvider();
+
+ var routeBuilder = new RouteBuilder(applicationBuilder);
+ routeBuilder.DefaultHandler = new RouteHandler(NullHandler);
+ return routeBuilder;
+ }
- private static Route CreateRoute(string routeName, string template, bool handleRequest = true)
- {
- return new Route(
- CreateTarget(handleRequest),
- routeName,
- template,
- defaults: null,
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
- }
+ private static Route CreateRoute(string routeName, string template, bool handleRequest = true)
+ {
+ return new Route(
+ CreateTarget(handleRequest),
+ routeName,
+ template,
+ defaults: null,
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+ }
- private static Route CreateRoute(string template, bool handleRequest = true)
- {
- return new Route(CreateTarget(handleRequest), template, _inlineConstraintResolver);
- }
+ private static Route CreateRoute(string template, bool handleRequest = true)
+ {
+ return new Route(CreateTarget(handleRequest), template, _inlineConstraintResolver);
+ }
- private static Route CreateRoute(
- string template,
- object defaults,
- bool handleRequest = true,
- object constraints = null,
- object dataTokens = null)
- {
- return new Route(
- CreateTarget(handleRequest),
- template,
- new RouteValueDictionary(defaults),
- new RouteValueDictionary(constraints),
- new RouteValueDictionary(dataTokens),
- _inlineConstraintResolver);
- }
+ private static Route CreateRoute(
+ string template,
+ object defaults,
+ bool handleRequest = true,
+ object constraints = null,
+ object dataTokens = null)
+ {
+ return new Route(
+ CreateTarget(handleRequest),
+ template,
+ new RouteValueDictionary(defaults),
+ new RouteValueDictionary(constraints),
+ new RouteValueDictionary(dataTokens),
+ _inlineConstraintResolver);
+ }
- private static Route CreateRoute(IRouter target, string template)
- {
- return new Route(
- target,
- template,
- new RouteValueDictionary(),
- constraints: null,
- dataTokens: null,
- inlineConstraintResolver: _inlineConstraintResolver);
- }
+ private static Route CreateRoute(IRouter target, string template)
+ {
+ return new Route(
+ target,
+ template,
+ new RouteValueDictionary(),
+ constraints: null,
+ dataTokens: null,
+ inlineConstraintResolver: _inlineConstraintResolver);
+ }
- private static Route CreateRoute(
- IRouter target,
- string template,
- object defaults,
- RouteValueDictionary dataTokens = null)
- {
- return new Route(
- target,
- template,
- new RouteValueDictionary(defaults),
- constraints: null,
- dataTokens: dataTokens,
- inlineConstraintResolver: _inlineConstraintResolver);
- }
+ private static Route CreateRoute(
+ IRouter target,
+ string template,
+ object defaults,
+ RouteValueDictionary dataTokens = null)
+ {
+ return new Route(
+ target,
+ template,
+ new RouteValueDictionary(defaults),
+ constraints: null,
+ dataTokens: dataTokens,
+ inlineConstraintResolver: _inlineConstraintResolver);
+ }
- private static IRouter CreateTarget(bool handleRequest = true)
- {
- var target = new Mock<IRouter>(MockBehavior.Strict);
- target
- .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
- .Returns<VirtualPathContext>(rc => null);
+ private static IRouter CreateTarget(bool handleRequest = true)
+ {
+ var target = new Mock<IRouter>(MockBehavior.Strict);
+ target
+ .Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
+ .Returns<VirtualPathContext>(rc => null);
- target
- .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>((c) => c.Handler = handleRequest ? NullHandler : null)
- .Returns(Task.FromResult<object>(null));
+ target
+ .Setup(e => e.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>((c) => c.Handler = handleRequest ? NullHandler : null)
+ .Returns(Task.FromResult<object>(null));
- return target.Object;
- }
+ return target.Object;
+ }
- private static IInlineConstraintResolver GetInlineConstraintResolver()
- {
- var routeOptions = new RouteOptions();
- ConfigureRouteOptions(routeOptions);
+ private static IInlineConstraintResolver GetInlineConstraintResolver()
+ {
+ var routeOptions = new RouteOptions();
+ ConfigureRouteOptions(routeOptions);
- var routeOptionsMock = new Mock<IOptions<RouteOptions>>();
- routeOptionsMock
- .SetupGet(o => o.Value)
- .Returns(routeOptions);
+ var routeOptionsMock = new Mock<IOptions<RouteOptions>>();
+ routeOptionsMock
+ .SetupGet(o => o.Value)
+ .Returns(routeOptions);
- return new DefaultInlineConstraintResolver(routeOptionsMock.Object, new TestServiceProvider());
- }
+ return new DefaultInlineConstraintResolver(routeOptionsMock.Object, new TestServiceProvider());
+ }
- private static void ConfigureRouteOptions(RouteOptions options)
- {
- options.ConstraintMap["test-policy"] = typeof(TestPolicy);
- }
+ private static void ConfigureRouteOptions(RouteOptions options)
+ {
+ options.ConstraintMap["test-policy"] = typeof(TestPolicy);
+ }
- private class TestPolicy : IParameterPolicy
- {
- }
+ private class TestPolicy : IParameterPolicy
+ {
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteValueEqualityComparerTest.cs b/src/Http/Routing/test/UnitTests/RouteValueEqualityComparerTest.cs
index b00e812340..91573ed5f0 100644
--- a/src/Http/Routing/test/UnitTests/RouteValueEqualityComparerTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteValueEqualityComparerTest.cs
@@ -3,39 +3,38 @@
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteValueEqualityComparerTest
{
- public class RouteValueEqualityComparerTest
- {
- private readonly RouteValueEqualityComparer _comparer;
+ private readonly RouteValueEqualityComparer _comparer;
- public RouteValueEqualityComparerTest()
- {
- _comparer = new RouteValueEqualityComparer();
- }
+ public RouteValueEqualityComparerTest()
+ {
+ _comparer = new RouteValueEqualityComparer();
+ }
- [Theory]
- [InlineData(5, 7, false)]
- [InlineData("foo", "foo", true)]
- [InlineData("foo", "FoO", true)]
- [InlineData("foo", "boo", false)]
- [InlineData("7", 7, true)]
- [InlineData(7, "7", true)]
- [InlineData(5.7d, 5.7d, true)]
- [InlineData(null, null, true)]
- [InlineData(null, "foo", false)]
- [InlineData("foo", null, false)]
- [InlineData(null, "", true)]
- [InlineData("", null, true)]
- [InlineData("", "", true)]
- [InlineData("", "foo", false)]
- [InlineData("foo", "", false)]
- [InlineData(true, true, true)]
- [InlineData(true, false, false)]
- public void EqualsTest(object x, object y, bool expected)
- {
- var actual = _comparer.Equals(x, y);
- Assert.Equal(expected, actual);
- }
+ [Theory]
+ [InlineData(5, 7, false)]
+ [InlineData("foo", "foo", true)]
+ [InlineData("foo", "FoO", true)]
+ [InlineData("foo", "boo", false)]
+ [InlineData("7", 7, true)]
+ [InlineData(7, "7", true)]
+ [InlineData(5.7d, 5.7d, true)]
+ [InlineData(null, null, true)]
+ [InlineData(null, "foo", false)]
+ [InlineData("foo", null, false)]
+ [InlineData(null, "", true)]
+ [InlineData("", null, true)]
+ [InlineData("", "", true)]
+ [InlineData("", "foo", false)]
+ [InlineData("foo", "", false)]
+ [InlineData(true, true, true)]
+ [InlineData(true, false, false)]
+ public void EqualsTest(object x, object y, bool expected)
+ {
+ var actual = _comparer.Equals(x, y);
+ Assert.Equal(expected, actual);
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs b/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs
index cf1f1ad259..1584eff8ef 100644
--- a/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs
@@ -5,464 +5,463 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouteValuesAddressSchemeTest
{
- public class RouteValuesAddressSchemeTest
+ [Fact]
+ public void GetOutboundMatches_GetsNamedMatchesFor_EndpointsHaving_IRouteNameMetadata()
{
- [Fact]
- public void GetOutboundMatches_GetsNamedMatchesFor_EndpointsHaving_IRouteNameMetadata()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/a", routeName: "other");
- var endpoint2 = CreateEndpoint("/a", routeName: "named");
-
- // Act
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
-
- // Assert
- var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- Assert.Equal(2, allMatches.Count);
- Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches));
- var namedMatch = Assert.Single(namedMatches);
- var actual = Assert.IsType<RouteEndpoint>(namedMatch.Match.Entry.Data);
- Assert.Same(endpoint2, actual);
- }
+ // Arrange
+ var endpoint1 = CreateEndpoint("/a", routeName: "other");
+ var endpoint2 = CreateEndpoint("/a", routeName: "named");
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
+
+ // Assert
+ var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
+ Assert.Equal(2, allMatches.Count);
+ Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches));
+ var namedMatch = Assert.Single(namedMatches);
+ var actual = Assert.IsType<RouteEndpoint>(namedMatch.Match.Entry.Data);
+ Assert.Same(endpoint2, actual);
+ }
- [Fact]
- public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/a", routeName: "other");
- var endpoint2 = CreateEndpoint("/a", routeName: "named");
- var endpoint3 = CreateEndpoint("/b", routeName: "named");
-
- // Act
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
-
- // Assert
- var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- Assert.Equal(3, allMatches.Count);
- Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches));
- Assert.Equal(2, namedMatches.Count);
- Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data));
- Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data));
- }
+ [Fact]
+ public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/a", routeName: "other");
+ var endpoint2 = CreateEndpoint("/a", routeName: "named");
+ var endpoint3 = CreateEndpoint("/b", routeName: "named");
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
+
+ // Assert
+ var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
+ Assert.Equal(3, allMatches.Count);
+ Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches));
+ Assert.Equal(2, namedMatches.Count);
+ Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data));
+ Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data));
+ }
- [Fact]
- public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName_IgnoringCase()
- {
- // Arrange
- var endpoint1 = CreateEndpoint("/a", routeName: "other");
- var endpoint2 = CreateEndpoint("/a", routeName: "named");
- var endpoint3 = CreateEndpoint("/b", routeName: "NaMed");
-
- // Act
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
-
- // Assert
- var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- Assert.Equal(3, allMatches.Count);
- Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches));
- Assert.Equal(2, namedMatches.Count);
- Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data));
- Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data));
- }
+ [Fact]
+ public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName_IgnoringCase()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint("/a", routeName: "other");
+ var endpoint2 = CreateEndpoint("/a", routeName: "named");
+ var endpoint3 = CreateEndpoint("/b", routeName: "NaMed");
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
+
+ // Assert
+ var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
+ Assert.Equal(3, allMatches.Count);
+ Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches));
+ Assert.Equal(2, namedMatches.Count);
+ Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data));
+ Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data));
+ }
- [Fact]
- public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches()
- {
- // Arrange 1
- var endpoint1 = CreateEndpoint("/a", routeName: "a");
- var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
-
- // Act 1
- var addressScheme = new RouteValuesAddressScheme(new CompositeEndpointDataSource(new[] { dynamicDataSource }));
-
- // Assert 1
- var state = addressScheme.State;
- var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
-
- Assert.NotEmpty(allMatches);
-
- var match = Assert.Single(allMatches);
- var actual = Assert.IsType<RouteEndpoint>(match.Entry.Data);
- Assert.Same(endpoint1, actual);
-
- // Arrange 2
- var endpoint2 = CreateEndpoint("/b", routeName: "b");
-
- // Act 2
- // Trigger change
- dynamicDataSource.AddEndpoint(endpoint2);
-
- // Assert 2
- Assert.NotSame(state, addressScheme.State);
- state = addressScheme.State;
-
- // Arrange 3
- var endpoint3 = CreateEndpoint("/c", routeName: "c");
-
- // Act 3
- // Trigger change
- dynamicDataSource.AddEndpoint(endpoint3);
-
- // Assert 3
- Assert.NotSame(state, addressScheme.State);
- state = addressScheme.State;
-
- // Arrange 4
- var endpoint4 = CreateEndpoint("/d", routeName: "d");
-
- // Act 4
- // Trigger change
- dynamicDataSource.AddEndpoint(endpoint4);
-
- // Assert 4
- Assert.NotSame(state, addressScheme.State);
- state = addressScheme.State;
-
- allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
-
- Assert.NotEmpty(allMatches);
- Assert.Collection(
- allMatches,
- (m) =>
- {
- actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
- Assert.Same(endpoint1, actual);
- },
- (m) =>
- {
- actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
- Assert.Same(endpoint2, actual);
- },
- (m) =>
- {
- actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
- Assert.Same(endpoint3, actual);
- },
- (m) =>
- {
- actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
- Assert.Same(endpoint4, actual);
- });
- }
+ [Fact]
+ public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches()
+ {
+ // Arrange 1
+ var endpoint1 = CreateEndpoint("/a", routeName: "a");
+ var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 });
- [Fact]
- public void FindEndpoints_LookedUpByCriteria_NoMatch()
- {
- // Arrange
- var endpoint1 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { zipCode = 3510 },
- metadataRequiredValues: new { id = 7 });
- var endpoint2 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { id = 12 },
- metadataRequiredValues: new { zipCode = 3510 });
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(new { id = 8 }),
- AmbientValues = new RouteValueDictionary(new { urgent = false }),
- });
-
- // Assert
- Assert.Empty(foundEndpoints);
- }
+ // Act 1
+ var addressScheme = new RouteValuesAddressScheme(new CompositeEndpointDataSource(new[] { dynamicDataSource }));
- [Fact]
- public void FindEndpoints_LookedUpByCriteria_OneMatch()
- {
- // Arrange
- var endpoint1 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { zipCode = 3510 },
- metadataRequiredValues: new { id = 7 });
- var endpoint2 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { id = 12 });
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(new { id = 7 }),
- AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
- });
-
- // Assert
- var actual = Assert.Single(foundEndpoints);
- Assert.Same(endpoint1, actual);
- }
+ // Assert 1
+ var state = addressScheme.State;
+ var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- [Fact]
- public void FindEndpoints_LookedUpByCriteria_MultipleMatches()
- {
- // Arrange
- var endpoint1 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { zipCode = 3510 },
- metadataRequiredValues: new { id = 7 });
- var endpoint2 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent}/{zipCode}",
- defaults: new { id = 12 },
- metadataRequiredValues: new { id = 12 });
- var endpoint3 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { id = 12 },
- metadataRequiredValues: new { id = 12 });
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(new { id = 12 }),
- AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
- });
-
- // Assert
- Assert.Collection(foundEndpoints,
- e => Assert.Equal(endpoint3, e),
- e => Assert.Equal(endpoint2, e));
- }
+ Assert.NotEmpty(allMatches);
- [Fact]
- public void FindEndpoints_LookedUpByCriteria_ExcludeEndpointWithoutRouteValuesAddressMetadata()
- {
- // Arrange
- var endpoint1 = CreateEndpoint(
- "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
- defaults: new { zipCode = 3510 },
- metadataRequiredValues: new { id = 7 });
- var endpoint2 = CreateEndpoint("test");
-
- var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(new { id = 7 }),
- AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
- }).ToList();
-
- // Assert
- Assert.DoesNotContain(endpoint2, foundEndpoints);
- Assert.Contains(endpoint1, foundEndpoints);
- }
+ var match = Assert.Single(allMatches);
+ var actual = Assert.IsType<RouteEndpoint>(match.Entry.Data);
+ Assert.Same(endpoint1, actual);
- [Fact]
- public void FindEndpoints_ReturnsEndpoint_WhenLookedUpByRouteName()
- {
- // Arrange
- var expected = CreateEndpoint(
- "api/orders/{id}",
- defaults: new { controller = "Orders", action = "GetById" },
- metadataRequiredValues: new { controller = "Orders", action = "GetById" },
- routeName: "OrdersApi");
- var addressScheme = CreateAddressScheme(expected);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(new { id = 10 }),
- AmbientValues = new RouteValueDictionary(new { controller = "Home", action = "Index" }),
- RouteName = "OrdersApi"
- });
-
- // Assert
- var actual = Assert.Single(foundEndpoints);
- Assert.Same(expected, actual);
- }
+ // Arrange 2
+ var endpoint2 = CreateEndpoint("/b", routeName: "b");
- [Fact]
- public void FindEndpoints_ReturnsEndpoint_UsingRoutePatternRequiredValues()
- {
- // Arrange
- var expected = CreateEndpoint(
- "api/orders/{id}",
- defaults: new { controller = "Orders", action = "GetById" },
- metadataRequiredValues: new { controller = "Orders", action = "GetById" });
- var addressScheme = CreateAddressScheme(expected);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(new { id = 10 }),
- AmbientValues = new RouteValueDictionary(new { controller = "Orders", action = "GetById" }),
- });
-
- // Assert
- var actual = Assert.Single(foundEndpoints);
- Assert.Same(expected, actual);
- }
+ // Act 2
+ // Trigger change
+ dynamicDataSource.AddEndpoint(endpoint2);
- [Fact]
- public void FindEndpoints_AlwaysReturnsEndpointsByRouteName_IgnoringMissingRequiredParameterValues()
- {
- // Here 'id' is the required value. The endpoint addressScheme would always return an endpoint by looking up
- // name only. Its the link generator which uses these endpoints finally to generate a link or not
- // based on the required parameter values being present or not.
-
- // Arrange
- var expected = CreateEndpoint(
- "api/orders/{id}",
- defaults: new { controller = "Orders", action = "GetById" },
- metadataRequiredValues: new { controller = "Orders", action = "GetById" },
- routeName: "OrdersApi");
- var addressScheme = CreateAddressScheme(expected);
-
- // Act
- var foundEndpoints = addressScheme.FindEndpoints(
- new RouteValuesAddress
- {
- ExplicitValues = new RouteValueDictionary(),
- AmbientValues = new RouteValueDictionary(),
- RouteName = "OrdersApi"
- });
-
- // Assert
- var actual = Assert.Single(foundEndpoints);
- Assert.Same(expected, actual);
- }
+ // Assert 2
+ Assert.NotSame(state, addressScheme.State);
+ state = addressScheme.State;
- [Fact]
- public void GetOutboundMatches_Includes_SameEndpointInNamedMatchesAndMatchesWithRequiredValues()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "api/orders/{id}",
- defaults: new { controller = "Orders", action = "GetById" },
- metadataRequiredValues: new { controller = "Orders", action = "GetById" },
- routeName: "a");
-
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
-
- // Assert
- var matchWithRequiredValue = Assert.Single(addressScheme.State.MatchesWithRequiredValues);
- var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value;
- var namedMatch = Assert.Single(namedMatches).Match;
-
- Assert.Same(endpoint, matchWithRequiredValue.Entry.Data);
- Assert.Same(endpoint, namedMatch.Entry.Data);
- }
+ // Arrange 3
+ var endpoint3 = CreateEndpoint("/c", routeName: "c");
- // Regression test for https://github.com/dotnet/aspnetcore/issues/35592
- [Fact]
- public void GetOutboundMatches_DoesNotInclude_EndpointsWithoutRequiredValuesInMatchesWithRequiredValues()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "api/orders/{id}",
- defaults: new { controller = "Orders", action = "GetById" },
- routeName: "a");
+ // Act 3
+ // Trigger change
+ dynamicDataSource.AddEndpoint(endpoint3);
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
+ // Assert 3
+ Assert.NotSame(state, addressScheme.State);
+ state = addressScheme.State;
- // Assert
- Assert.Empty(addressScheme.State.MatchesWithRequiredValues);
+ // Arrange 4
+ var endpoint4 = CreateEndpoint("/d", routeName: "d");
- var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value;
- var namedMatch = Assert.Single(namedMatches).Match;
- Assert.Same(endpoint, namedMatch.Entry.Data);
- }
+ // Act 4
+ // Trigger change
+ dynamicDataSource.AddEndpoint(endpoint4);
- [Fact]
- public void GetOutboundMatches_DoesNotInclude_EndpointsWithSuppressLinkGenerationMetadata()
- {
- // Arrange
- var endpoint = CreateEndpoint(
- "api/orders/{id}",
- defaults: new { controller = "Orders", action = "GetById" },
- metadataRequiredValues: new { controller = "Orders", action = "GetById" },
- routeName: "a",
- metadataCollection: new EndpointMetadataCollection(new[] { new SuppressLinkGenerationMetadata() }));
-
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
-
- // Assert
- var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- Assert.Empty(allMatches);
- }
+ // Assert 4
+ Assert.NotSame(state, addressScheme.State);
+ state = addressScheme.State;
- [Fact]
- public void AddressScheme_UnsuppressedEndpoint_IsUsed()
- {
- // Arrange
- var endpoint = EndpointFactory.CreateRouteEndpoint(
- "/a",
- metadata: new object[] { new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), new RouteNameMetadata("a"), });
+ allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- // Act
- var addressScheme = CreateAddressScheme(endpoint);
+ Assert.NotEmpty(allMatches);
+ Assert.Collection(
+ allMatches,
+ (m) =>
+ {
+ actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
+ Assert.Same(endpoint1, actual);
+ },
+ (m) =>
+ {
+ actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
+ Assert.Same(endpoint2, actual);
+ },
+ (m) =>
+ {
+ actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
+ Assert.Same(endpoint3, actual);
+ },
+ (m) =>
+ {
+ actual = Assert.IsType<RouteEndpoint>(m.Entry.Data);
+ Assert.Same(endpoint4, actual);
+ });
+ }
- // Assert
- var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
- Assert.Same(endpoint, Assert.Single(allMatches).Entry.Data);
- }
+ [Fact]
+ public void FindEndpoints_LookedUpByCriteria_NoMatch()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { zipCode = 3510 },
+ metadataRequiredValues: new { id = 7 });
+ var endpoint2 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { id = 12 },
+ metadataRequiredValues: new { zipCode = 3510 });
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
+ {
+ ExplicitValues = new RouteValueDictionary(new { id = 8 }),
+ AmbientValues = new RouteValueDictionary(new { urgent = false }),
+ });
- private RouteValuesAddressScheme CreateAddressScheme(params Endpoint[] endpoints)
- {
- return CreateAddressScheme(new DefaultEndpointDataSource(endpoints));
- }
+ // Assert
+ Assert.Empty(foundEndpoints);
+ }
- private RouteValuesAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources)
- {
- return new RouteValuesAddressScheme(new CompositeEndpointDataSource(dataSources));
- }
+ [Fact]
+ public void FindEndpoints_LookedUpByCriteria_OneMatch()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { zipCode = 3510 },
+ metadataRequiredValues: new { id = 7 });
+ var endpoint2 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { id = 12 });
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
+ {
+ ExplicitValues = new RouteValueDictionary(new { id = 7 }),
+ AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
+ });
- private RouteEndpoint CreateEndpoint(
- string template,
- object defaults = null,
- object metadataRequiredValues = null,
- int order = 0,
- string routeName = null,
- EndpointMetadataCollection metadataCollection = null)
- {
- if (metadataCollection == null)
+ // Assert
+ var actual = Assert.Single(foundEndpoints);
+ Assert.Same(endpoint1, actual);
+ }
+
+ [Fact]
+ public void FindEndpoints_LookedUpByCriteria_MultipleMatches()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { zipCode = 3510 },
+ metadataRequiredValues: new { id = 7 });
+ var endpoint2 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent}/{zipCode}",
+ defaults: new { id = 12 },
+ metadataRequiredValues: new { id = 12 });
+ var endpoint3 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { id = 12 },
+ metadataRequiredValues: new { id = 12 });
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
{
- var metadata = new List<object>();
- if (!string.IsNullOrEmpty(routeName))
- {
- metadata.Add(new RouteNameMetadata(routeName));
- }
- metadataCollection = new EndpointMetadataCollection(metadata);
- }
+ ExplicitValues = new RouteValueDictionary(new { id = 12 }),
+ AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
+ });
+
+ // Assert
+ Assert.Collection(foundEndpoints,
+ e => Assert.Equal(endpoint3, e),
+ e => Assert.Equal(endpoint2, e));
+ }
- return new RouteEndpoint(
- TestConstants.EmptyRequestDelegate,
- RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues: metadataRequiredValues),
- order,
- metadataCollection,
- null);
- }
+ [Fact]
+ public void FindEndpoints_LookedUpByCriteria_ExcludeEndpointWithoutRouteValuesAddressMetadata()
+ {
+ // Arrange
+ var endpoint1 = CreateEndpoint(
+ "api/orders/{id}/{name?}/{urgent=true}/{zipCode}",
+ defaults: new { zipCode = 3510 },
+ metadataRequiredValues: new { id = 7 });
+ var endpoint2 = CreateEndpoint("test");
+
+ var addressScheme = CreateAddressScheme(endpoint1, endpoint2);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
+ {
+ ExplicitValues = new RouteValueDictionary(new { id = 7 }),
+ AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }),
+ }).ToList();
- private static List<Tree.OutboundMatch> GetMatchesWithRequiredValuesPlusNamedMatches(RouteValuesAddressScheme routeValuesAddressScheme)
- {
- var state = routeValuesAddressScheme.State;
+ // Assert
+ Assert.DoesNotContain(endpoint2, foundEndpoints);
+ Assert.Contains(endpoint1, foundEndpoints);
+ }
- Assert.NotNull(state.MatchesWithRequiredValues);
- Assert.NotNull(state.NamedMatches);
+ [Fact]
+ public void FindEndpoints_ReturnsEndpoint_WhenLookedUpByRouteName()
+ {
+ // Arrange
+ var expected = CreateEndpoint(
+ "api/orders/{id}",
+ defaults: new { controller = "Orders", action = "GetById" },
+ metadataRequiredValues: new { controller = "Orders", action = "GetById" },
+ routeName: "OrdersApi");
+ var addressScheme = CreateAddressScheme(expected);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
+ {
+ ExplicitValues = new RouteValueDictionary(new { id = 10 }),
+ AmbientValues = new RouteValueDictionary(new { controller = "Home", action = "Index" }),
+ RouteName = "OrdersApi"
+ });
+
+ // Assert
+ var actual = Assert.Single(foundEndpoints);
+ Assert.Same(expected, actual);
+ }
- var namedMatches = state.NamedMatches.Aggregate(Enumerable.Empty<Tree.OutboundMatch>(),
- (acc, kvp) => acc.Concat(kvp.Value.Select(matchResult => matchResult.Match)));
- return state.MatchesWithRequiredValues.Concat(namedMatches).ToList();
- }
+ [Fact]
+ public void FindEndpoints_ReturnsEndpoint_UsingRoutePatternRequiredValues()
+ {
+ // Arrange
+ var expected = CreateEndpoint(
+ "api/orders/{id}",
+ defaults: new { controller = "Orders", action = "GetById" },
+ metadataRequiredValues: new { controller = "Orders", action = "GetById" });
+ var addressScheme = CreateAddressScheme(expected);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
+ {
+ ExplicitValues = new RouteValueDictionary(new { id = 10 }),
+ AmbientValues = new RouteValueDictionary(new { controller = "Orders", action = "GetById" }),
+ });
+
+ // Assert
+ var actual = Assert.Single(foundEndpoints);
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void FindEndpoints_AlwaysReturnsEndpointsByRouteName_IgnoringMissingRequiredParameterValues()
+ {
+ // Here 'id' is the required value. The endpoint addressScheme would always return an endpoint by looking up
+ // name only. Its the link generator which uses these endpoints finally to generate a link or not
+ // based on the required parameter values being present or not.
+
+ // Arrange
+ var expected = CreateEndpoint(
+ "api/orders/{id}",
+ defaults: new { controller = "Orders", action = "GetById" },
+ metadataRequiredValues: new { controller = "Orders", action = "GetById" },
+ routeName: "OrdersApi");
+ var addressScheme = CreateAddressScheme(expected);
+
+ // Act
+ var foundEndpoints = addressScheme.FindEndpoints(
+ new RouteValuesAddress
+ {
+ ExplicitValues = new RouteValueDictionary(),
+ AmbientValues = new RouteValueDictionary(),
+ RouteName = "OrdersApi"
+ });
+
+ // Assert
+ var actual = Assert.Single(foundEndpoints);
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void GetOutboundMatches_Includes_SameEndpointInNamedMatchesAndMatchesWithRequiredValues()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "api/orders/{id}",
+ defaults: new { controller = "Orders", action = "GetById" },
+ metadataRequiredValues: new { controller = "Orders", action = "GetById" },
+ routeName: "a");
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
+
+ // Assert
+ var matchWithRequiredValue = Assert.Single(addressScheme.State.MatchesWithRequiredValues);
+ var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value;
+ var namedMatch = Assert.Single(namedMatches).Match;
+
+ Assert.Same(endpoint, matchWithRequiredValue.Entry.Data);
+ Assert.Same(endpoint, namedMatch.Entry.Data);
+ }
+
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/35592
+ [Fact]
+ public void GetOutboundMatches_DoesNotInclude_EndpointsWithoutRequiredValuesInMatchesWithRequiredValues()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "api/orders/{id}",
+ defaults: new { controller = "Orders", action = "GetById" },
+ routeName: "a");
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
+
+ // Assert
+ Assert.Empty(addressScheme.State.MatchesWithRequiredValues);
+
+ var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value;
+ var namedMatch = Assert.Single(namedMatches).Match;
+ Assert.Same(endpoint, namedMatch.Entry.Data);
+ }
+
+ [Fact]
+ public void GetOutboundMatches_DoesNotInclude_EndpointsWithSuppressLinkGenerationMetadata()
+ {
+ // Arrange
+ var endpoint = CreateEndpoint(
+ "api/orders/{id}",
+ defaults: new { controller = "Orders", action = "GetById" },
+ metadataRequiredValues: new { controller = "Orders", action = "GetById" },
+ routeName: "a",
+ metadataCollection: new EndpointMetadataCollection(new[] { new SuppressLinkGenerationMetadata() }));
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
+
+ // Assert
+ var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
+ Assert.Empty(allMatches);
+ }
+
+ [Fact]
+ public void AddressScheme_UnsuppressedEndpoint_IsUsed()
+ {
+ // Arrange
+ var endpoint = EndpointFactory.CreateRouteEndpoint(
+ "/a",
+ metadata: new object[] { new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), new RouteNameMetadata("a"), });
+
+ // Act
+ var addressScheme = CreateAddressScheme(endpoint);
- private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata
+ // Assert
+ var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme);
+ Assert.Same(endpoint, Assert.Single(allMatches).Entry.Data);
+ }
+
+ private RouteValuesAddressScheme CreateAddressScheme(params Endpoint[] endpoints)
+ {
+ return CreateAddressScheme(new DefaultEndpointDataSource(endpoints));
+ }
+
+ private RouteValuesAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources)
+ {
+ return new RouteValuesAddressScheme(new CompositeEndpointDataSource(dataSources));
+ }
+
+ private RouteEndpoint CreateEndpoint(
+ string template,
+ object defaults = null,
+ object metadataRequiredValues = null,
+ int order = 0,
+ string routeName = null,
+ EndpointMetadataCollection metadataCollection = null)
+ {
+ if (metadataCollection == null)
{
- public bool SuppressLinkGeneration => false;
+ var metadata = new List<object>();
+ if (!string.IsNullOrEmpty(routeName))
+ {
+ metadata.Add(new RouteNameMetadata(routeName));
+ }
+ metadataCollection = new EndpointMetadataCollection(metadata);
}
+
+ return new RouteEndpoint(
+ TestConstants.EmptyRequestDelegate,
+ RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues: metadataRequiredValues),
+ order,
+ metadataCollection,
+ null);
+ }
+
+ private static List<Tree.OutboundMatch> GetMatchesWithRequiredValuesPlusNamedMatches(RouteValuesAddressScheme routeValuesAddressScheme)
+ {
+ var state = routeValuesAddressScheme.State;
+
+ Assert.NotNull(state.MatchesWithRequiredValues);
+ Assert.NotNull(state.NamedMatches);
+
+ var namedMatches = state.NamedMatches.Aggregate(Enumerable.Empty<Tree.OutboundMatch>(),
+ (acc, kvp) => acc.Concat(kvp.Value.Select(matchResult => matchResult.Match)));
+ return state.MatchesWithRequiredValues.Concat(namedMatches).ToList();
+ }
+
+ private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata
+ {
+ public bool SuppressLinkGeneration => false;
}
}
diff --git a/src/Http/Routing/test/UnitTests/RouterMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/RouterMiddlewareTest.cs
index d72963238a..eed862bc10 100644
--- a/src/Http/Routing/test/UnitTests/RouterMiddlewareTest.cs
+++ b/src/Http/Routing/test/UnitTests/RouterMiddlewareTest.cs
@@ -12,147 +12,146 @@ using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RouterMiddlewareTest
{
- public class RouterMiddlewareTest
+ [Fact]
+ public async Task RoutingFeatureSetInIRouter()
{
- [Fact]
- public async Task RoutingFeatureSetInIRouter()
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddLogging();
+ var httpContext = new DefaultHttpContext
{
- // Arrange
- var services = new ServiceCollection();
- services.AddLogging();
- var httpContext = new DefaultHttpContext
- {
- RequestServices = services.BuildServiceProvider()
- };
+ RequestServices = services.BuildServiceProvider()
+ };
- httpContext.Request.Path = "/foo/10";
+ httpContext.Request.Path = "/foo/10";
- var routeHandlerExecuted = false;
+ var routeHandlerExecuted = false;
- var handler = new RouteHandler(context =>
- {
- routeHandlerExecuted = true;
+ var handler = new RouteHandler(context =>
+ {
+ routeHandlerExecuted = true;
- var routingFeature = context.Features.Get<IRoutingFeature>();
+ var routingFeature = context.Features.Get<IRoutingFeature>();
- Assert.NotNull(routingFeature);
- Assert.NotNull(context.Features.Get<IRouteValuesFeature>());
+ Assert.NotNull(routingFeature);
+ Assert.NotNull(context.Features.Get<IRouteValuesFeature>());
- Assert.Single(routingFeature.RouteData.Values);
- Assert.Single(context.Request.RouteValues);
- Assert.True(routingFeature.RouteData.Values.ContainsKey("id"));
- Assert.True(context.Request.RouteValues.ContainsKey("id"));
- Assert.Equal("10", routingFeature.RouteData.Values["id"]);
- Assert.Equal("10", context.Request.RouteValues["id"]);
- Assert.Equal("10", context.GetRouteValue("id"));
- Assert.Same(routingFeature.RouteData, context.GetRouteData());
+ Assert.Single(routingFeature.RouteData.Values);
+ Assert.Single(context.Request.RouteValues);
+ Assert.True(routingFeature.RouteData.Values.ContainsKey("id"));
+ Assert.True(context.Request.RouteValues.ContainsKey("id"));
+ Assert.Equal("10", routingFeature.RouteData.Values["id"]);
+ Assert.Equal("10", context.Request.RouteValues["id"]);
+ Assert.Equal("10", context.GetRouteValue("id"));
+ Assert.Same(routingFeature.RouteData, context.GetRouteData());
- return Task.CompletedTask;
- });
+ return Task.CompletedTask;
+ });
- var route = new Route(handler, "/foo/{id}", Mock.Of<IInlineConstraintResolver>());
+ var route = new Route(handler, "/foo/{id}", Mock.Of<IInlineConstraintResolver>());
- var middleware = new RouterMiddleware(context => Task.CompletedTask, NullLoggerFactory.Instance, route);
+ var middleware = new RouterMiddleware(context => Task.CompletedTask, NullLoggerFactory.Instance, route);
- // Act
- await middleware.Invoke(httpContext);
+ // Act
+ await middleware.Invoke(httpContext);
- // Assert
- Assert.True(routeHandlerExecuted);
+ // Assert
+ Assert.True(routeHandlerExecuted);
- }
+ }
- [Fact]
- public async Task Invoke_LogsCorrectValues_WhenNotHandled()
+ [Fact]
+ public async Task Invoke_LogsCorrectValues_WhenNotHandled()
+ {
+ // Arrange
+ var expectedMessage = "Request did not match any routes";
+ var isHandled = false;
+
+ var sink = new TestSink(
+ TestSink.EnableWithTypeName<RouterMiddleware>,
+ TestSink.EnableWithTypeName<RouterMiddleware>);
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceProvider();
+
+ RequestDelegate next = (c) =>
{
- // Arrange
- var expectedMessage = "Request did not match any routes";
- var isHandled = false;
+ return Task.FromResult<object>(null);
+ };
- var sink = new TestSink(
- TestSink.EnableWithTypeName<RouterMiddleware>,
- TestSink.EnableWithTypeName<RouterMiddleware>);
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ var router = new TestRouter(isHandled);
+ var middleware = new RouterMiddleware(next, loggerFactory, router);
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceProvider();
+ // Act
+ await middleware.Invoke(httpContext);
- RequestDelegate next = (c) =>
- {
- return Task.FromResult<object>(null);
- };
+ // Assert
+ Assert.Empty(sink.Scopes);
+ var write = Assert.Single(sink.Writes);
+ Assert.Equal(expectedMessage, write.State?.ToString());
+ }
- var router = new TestRouter(isHandled);
- var middleware = new RouterMiddleware(next, loggerFactory, router);
+ [Fact]
+ public async Task Invoke_DoesNotLog_WhenHandled()
+ {
+ // Arrange
+ var isHandled = true;
- // Act
- await middleware.Invoke(httpContext);
+ var sink = new TestSink(
+ TestSink.EnableWithTypeName<RouterMiddleware>,
+ TestSink.EnableWithTypeName<RouterMiddleware>);
+ var loggerFactory = new TestLoggerFactory(sink, enabled: true);
- // Assert
- Assert.Empty(sink.Scopes);
- var write = Assert.Single(sink.Writes);
- Assert.Equal(expectedMessage, write.State?.ToString());
- }
+ var httpContext = new DefaultHttpContext();
+ httpContext.RequestServices = new ServiceProvider();
- [Fact]
- public async Task Invoke_DoesNotLog_WhenHandled()
+ RequestDelegate next = (c) =>
{
- // Arrange
- var isHandled = true;
+ return Task.FromResult<object>(null);
+ };
- var sink = new TestSink(
- TestSink.EnableWithTypeName<RouterMiddleware>,
- TestSink.EnableWithTypeName<RouterMiddleware>);
- var loggerFactory = new TestLoggerFactory(sink, enabled: true);
+ var router = new TestRouter(isHandled);
+ var middleware = new RouterMiddleware(next, loggerFactory, router);
- var httpContext = new DefaultHttpContext();
- httpContext.RequestServices = new ServiceProvider();
+ // Act
+ await middleware.Invoke(httpContext);
- RequestDelegate next = (c) =>
- {
- return Task.FromResult<object>(null);
- };
+ // Assert
+ Assert.Empty(sink.Scopes);
+ Assert.Empty(sink.Writes);
+ }
- var router = new TestRouter(isHandled);
- var middleware = new RouterMiddleware(next, loggerFactory, router);
+ private class TestRouter : IRouter
+ {
+ private readonly bool _isHandled;
- // Act
- await middleware.Invoke(httpContext);
+ public TestRouter(bool isHandled)
+ {
+ _isHandled = isHandled;
+ }
- // Assert
- Assert.Empty(sink.Scopes);
- Assert.Empty(sink.Writes);
+ public VirtualPathData GetVirtualPath(VirtualPathContext context)
+ {
+ return new VirtualPathData(this, "");
}
- private class TestRouter : IRouter
+ public Task RouteAsync(RouteContext context)
{
- private readonly bool _isHandled;
-
- public TestRouter(bool isHandled)
- {
- _isHandled = isHandled;
- }
-
- public VirtualPathData GetVirtualPath(VirtualPathContext context)
- {
- return new VirtualPathData(this, "");
- }
-
- public Task RouteAsync(RouteContext context)
- {
- context.Handler = _isHandled ? (RequestDelegate)((c) => Task.CompletedTask) : null;
- return Task.FromResult<object>(null);
- }
+ context.Handler = _isHandled ? (RequestDelegate)((c) => Task.CompletedTask) : null;
+ return Task.FromResult<object>(null);
}
+ }
- private class ServiceProvider : IServiceProvider
+ private class ServiceProvider : IServiceProvider
+ {
+ public object GetService(Type serviceType)
{
- public object GetService(Type serviceType)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/RoutingEndpointConventionBuilderExtensionsTests.cs b/src/Http/Routing/test/UnitTests/RoutingEndpointConventionBuilderExtensionsTests.cs
index 58c806302e..fd38d17587 100644
--- a/src/Http/Routing/test/UnitTests/RoutingEndpointConventionBuilderExtensionsTests.cs
+++ b/src/Http/Routing/test/UnitTests/RoutingEndpointConventionBuilderExtensionsTests.cs
@@ -10,38 +10,37 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class RoutingEndpointConventionBuilderExtensionsTests
{
- public class RoutingEndpointConventionBuilderExtensionsTests
+ [Fact]
+ public void RequireHost_HostNames()
{
- [Fact]
- public void RequireHost_HostNames()
- {
- // Arrange
- var builder = new TestEndpointConventionBuilder();
+ // Arrange
+ var builder = new TestEndpointConventionBuilder();
- // Act
- builder.RequireHost("contoso.com:8080");
+ // Act
+ builder.RequireHost("contoso.com:8080");
- // Assert
- var convention = Assert.Single(builder.Conventions);
+ // Assert
+ var convention = Assert.Single(builder.Conventions);
- var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
- convention(endpointModel);
+ var endpointModel = new RouteEndpointBuilder((context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0);
+ convention(endpointModel);
- var hostMetadata = Assert.IsType<HostAttribute>(Assert.Single(endpointModel.Metadata));
+ var hostMetadata = Assert.IsType<HostAttribute>(Assert.Single(endpointModel.Metadata));
- Assert.Equal("contoso.com:8080", hostMetadata.Hosts.Single());
- }
+ Assert.Equal("contoso.com:8080", hostMetadata.Hosts.Single());
+ }
- private class TestEndpointConventionBuilder : IEndpointConventionBuilder
- {
- public IList<Action<EndpointBuilder>> Conventions { get; } = new List<Action<EndpointBuilder>>();
+ private class TestEndpointConventionBuilder : IEndpointConventionBuilder
+ {
+ public IList<Action<EndpointBuilder>> Conventions { get; } = new List<Action<EndpointBuilder>>();
- public void Add(Action<EndpointBuilder> convention)
- {
- Conventions.Add(convention);
- }
+ public void Add(Action<EndpointBuilder> convention)
+ {
+ Conventions.Add(convention);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Template/RoutePatternPrecedenceTests.cs b/src/Http/Routing/test/UnitTests/Template/RoutePatternPrecedenceTests.cs
index 023b9ab2ab..e5c4db0d2b 100644
--- a/src/Http/Routing/test/UnitTests/Template/RoutePatternPrecedenceTests.cs
+++ b/src/Http/Routing/test/UnitTests/Template/RoutePatternPrecedenceTests.cs
@@ -5,39 +5,38 @@ using System;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+public class RoutePatternPrecedenceTests : RoutePrecedenceTestsBase
{
- public class RoutePatternPrecedenceTests : RoutePrecedenceTestsBase
+ protected override decimal ComputeMatched(string template)
+ {
+ return ComputeRoutePattern(template, RoutePrecedence.ComputeInbound);
+ }
+
+ protected override decimal ComputeGenerated(string template)
+ {
+ return ComputeRoutePattern(template, RoutePrecedence.ComputeOutbound);
+ }
+
+ private static decimal ComputeRoutePattern(string template, Func<RoutePattern, decimal> func)
+ {
+ var parsed = RoutePatternFactory.Parse(template);
+ return func(parsed);
+ }
+
+ [Fact]
+ public void InboundPrecedence_ParameterWithRequiredValue_HasPrecedence()
{
- protected override decimal ComputeMatched(string template)
- {
- return ComputeRoutePattern(template, RoutePrecedence.ComputeInbound);
- }
-
- protected override decimal ComputeGenerated(string template)
- {
- return ComputeRoutePattern(template, RoutePrecedence.ComputeOutbound);
- }
-
- private static decimal ComputeRoutePattern(string template, Func<RoutePattern, decimal> func)
- {
- var parsed = RoutePatternFactory.Parse(template);
- return func(parsed);
- }
-
- [Fact]
- public void InboundPrecedence_ParameterWithRequiredValue_HasPrecedence()
- {
- var parameterPrecedence = RoutePatternFactory.Parse(
- "{controller}").InboundPrecedence;
-
- var requiredValueParameterPrecedence = RoutePatternFactory.Parse(
- "{controller}",
- defaults: null,
- parameterPolicies: null,
- requiredValues: new { controller = "Home" }).InboundPrecedence;
-
- Assert.True(requiredValueParameterPrecedence < parameterPrecedence);
- }
+ var parameterPrecedence = RoutePatternFactory.Parse(
+ "{controller}").InboundPrecedence;
+
+ var requiredValueParameterPrecedence = RoutePatternFactory.Parse(
+ "{controller}",
+ defaults: null,
+ parameterPolicies: null,
+ requiredValues: new { controller = "Home" }).InboundPrecedence;
+
+ Assert.True(requiredValueParameterPrecedence < parameterPrecedence);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Template/RoutePrecedenceTestsBase.cs b/src/Http/Routing/test/UnitTests/Template/RoutePrecedenceTestsBase.cs
index 545564daa3..0fd8ba8902 100644
--- a/src/Http/Routing/test/UnitTests/Template/RoutePrecedenceTestsBase.cs
+++ b/src/Http/Routing/test/UnitTests/Template/RoutePrecedenceTestsBase.cs
@@ -4,128 +4,127 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+public abstract class RoutePrecedenceTestsBase
{
- public abstract class RoutePrecedenceTestsBase
+ [Theory]
+ [InlineData("Employees/{id}", "Employees/{employeeId}")]
+ [InlineData("abc", "def")]
+ [InlineData("{x:alpha}", "{x:int}")]
+ public void ComputeMatched_IsEqual(string xTemplate, string yTemplate)
{
- [Theory]
- [InlineData("Employees/{id}", "Employees/{employeeId}")]
- [InlineData("abc", "def")]
- [InlineData("{x:alpha}", "{x:int}")]
- public void ComputeMatched_IsEqual(string xTemplate, string yTemplate)
- {
- // Arrange & Act
- var xPrededence = ComputeMatched(xTemplate);
- var yPrededence = ComputeMatched(yTemplate);
-
- // Assert
- Assert.Equal(xPrededence, yPrededence);
- }
-
- [Theory]
- [InlineData("Employees/{id}", "Employees/{employeeId}")]
- [InlineData("abc", "def")]
- [InlineData("{x:alpha}", "{x:int}")]
- public void ComputeGenerated_IsEqual(string xTemplate, string yTemplate)
- {
- // Arrange & Act
- var xPrededence = ComputeGenerated(xTemplate);
- var yPrededence = ComputeGenerated(yTemplate);
-
- // Assert
- Assert.Equal(xPrededence, yPrededence);
- }
-
- [Theory]
- [InlineData("abc", "a{x}")]
- [InlineData("abc", "{x}c")]
- [InlineData("abc", "{x:int}")]
- [InlineData("abc", "{x}")]
- [InlineData("abc", "{*x}")]
- [InlineData("{x:int}", "{x}")]
- [InlineData("{x:int}", "{*x}")]
- [InlineData("a{x}", "{x}")]
- [InlineData("{x}c", "{x}")]
- [InlineData("a{x}", "{*x}")]
- [InlineData("{x}c", "{*x}")]
- [InlineData("{x}", "{*x}")]
- [InlineData("{*x:maxlength(10)}", "{*x}")]
- [InlineData("abc/def", "abc/{x:int}")]
- [InlineData("abc/def", "abc/{x}")]
- [InlineData("abc/def", "abc/{*x}")]
- [InlineData("abc/{x:int}", "abc/{x}")]
- [InlineData("abc/{x:int}", "abc/{*x}")]
- [InlineData("abc/{x}", "abc/{*x}")]
- [InlineData("{x}/{y:int}", "{x}/{y}")]
- public void ComputeMatched_IsLessThan(string xTemplate, string yTemplate)
- {
- // Arrange & Act
- var xPrededence = ComputeMatched(xTemplate);
- var yPrededence = ComputeMatched(yTemplate);
-
- // Assert
- Assert.True(xPrededence < yPrededence);
- }
-
- [Theory]
- [InlineData("abc", "a{x}")]
- [InlineData("abc", "{x}c")]
- [InlineData("abc", "{x:int}")]
- [InlineData("abc", "{x}")]
- [InlineData("abc", "{*x}")]
- [InlineData("{x:int}", "{x}")]
- [InlineData("{x:int}", "{*x}")]
- [InlineData("a{x}", "{x}")]
- [InlineData("{x}c", "{x}")]
- [InlineData("a{x}", "{*x}")]
- [InlineData("{x}c", "{*x}")]
- [InlineData("{x}", "{*x}")]
- [InlineData("{*x:maxlength(10)}", "{*x}")]
- [InlineData("abc/def", "abc/{x:int}")]
- [InlineData("abc/def", "abc/{x}")]
- [InlineData("abc/def", "abc/{*x}")]
- [InlineData("abc/{x:int}", "abc/{x}")]
- [InlineData("abc/{x:int}", "abc/{*x}")]
- [InlineData("abc/{x}", "abc/{*x}")]
- [InlineData("{x}/{y:int}", "{x}/{y}")]
- public void ComputeGenerated_IsGreaterThan(string xTemplate, string yTemplate)
- {
- // Arrange & Act
- var xPrecedence = ComputeGenerated(xTemplate);
- var yPrecedence = ComputeGenerated(yTemplate);
+ // Arrange & Act
+ var xPrededence = ComputeMatched(xTemplate);
+ var yPrededence = ComputeMatched(yTemplate);
+
+ // Assert
+ Assert.Equal(xPrededence, yPrededence);
+ }
+
+ [Theory]
+ [InlineData("Employees/{id}", "Employees/{employeeId}")]
+ [InlineData("abc", "def")]
+ [InlineData("{x:alpha}", "{x:int}")]
+ public void ComputeGenerated_IsEqual(string xTemplate, string yTemplate)
+ {
+ // Arrange & Act
+ var xPrededence = ComputeGenerated(xTemplate);
+ var yPrededence = ComputeGenerated(yTemplate);
- // Assert
- Assert.True(xPrecedence > yPrecedence);
- }
+ // Assert
+ Assert.Equal(xPrededence, yPrededence);
+ }
+
+ [Theory]
+ [InlineData("abc", "a{x}")]
+ [InlineData("abc", "{x}c")]
+ [InlineData("abc", "{x:int}")]
+ [InlineData("abc", "{x}")]
+ [InlineData("abc", "{*x}")]
+ [InlineData("{x:int}", "{x}")]
+ [InlineData("{x:int}", "{*x}")]
+ [InlineData("a{x}", "{x}")]
+ [InlineData("{x}c", "{x}")]
+ [InlineData("a{x}", "{*x}")]
+ [InlineData("{x}c", "{*x}")]
+ [InlineData("{x}", "{*x}")]
+ [InlineData("{*x:maxlength(10)}", "{*x}")]
+ [InlineData("abc/def", "abc/{x:int}")]
+ [InlineData("abc/def", "abc/{x}")]
+ [InlineData("abc/def", "abc/{*x}")]
+ [InlineData("abc/{x:int}", "abc/{x}")]
+ [InlineData("abc/{x:int}", "abc/{*x}")]
+ [InlineData("abc/{x}", "abc/{*x}")]
+ [InlineData("{x}/{y:int}", "{x}/{y}")]
+ public void ComputeMatched_IsLessThan(string xTemplate, string yTemplate)
+ {
+ // Arrange & Act
+ var xPrededence = ComputeMatched(xTemplate);
+ var yPrededence = ComputeMatched(yTemplate);
- [Fact]
- public void ComputeGenerated_TooManySegments_ThrowHumaneError()
+ // Assert
+ Assert.True(xPrededence < yPrededence);
+ }
+
+ [Theory]
+ [InlineData("abc", "a{x}")]
+ [InlineData("abc", "{x}c")]
+ [InlineData("abc", "{x:int}")]
+ [InlineData("abc", "{x}")]
+ [InlineData("abc", "{*x}")]
+ [InlineData("{x:int}", "{x}")]
+ [InlineData("{x:int}", "{*x}")]
+ [InlineData("a{x}", "{x}")]
+ [InlineData("{x}c", "{x}")]
+ [InlineData("a{x}", "{*x}")]
+ [InlineData("{x}c", "{*x}")]
+ [InlineData("{x}", "{*x}")]
+ [InlineData("{*x:maxlength(10)}", "{*x}")]
+ [InlineData("abc/def", "abc/{x:int}")]
+ [InlineData("abc/def", "abc/{x}")]
+ [InlineData("abc/def", "abc/{*x}")]
+ [InlineData("abc/{x:int}", "abc/{x}")]
+ [InlineData("abc/{x:int}", "abc/{*x}")]
+ [InlineData("abc/{x}", "abc/{*x}")]
+ [InlineData("{x}/{y:int}", "{x}/{y}")]
+ public void ComputeGenerated_IsGreaterThan(string xTemplate, string yTemplate)
+ {
+ // Arrange & Act
+ var xPrecedence = ComputeGenerated(xTemplate);
+ var yPrecedence = ComputeGenerated(yTemplate);
+
+ // Assert
+ Assert.True(xPrecedence > yPrecedence);
+ }
+
+ [Fact]
+ public void ComputeGenerated_TooManySegments_ThrowHumaneError()
+ {
+ var ex = Assert.Throws<InvalidOperationException>(() =>
{
- var ex = Assert.Throws<InvalidOperationException>(() =>
- {
// Arrange & Act
ComputeGenerated("{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}/{a2}/{b2}/{b3}");
- });
+ });
- // Assert
- Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message);
- }
+ // Assert
+ Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message);
+ }
- [Fact]
- public void ComputeMatched_TooManySegments_ThrowHumaneError()
+ [Fact]
+ public void ComputeMatched_TooManySegments_ThrowHumaneError()
+ {
+ var ex = Assert.Throws<InvalidOperationException>(() =>
{
- var ex = Assert.Throws<InvalidOperationException>(() =>
- {
// Arrange & Act
ComputeMatched("{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}/{a2}/{b2}/{b3}");
- });
+ });
- // Assert
- Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message);
- }
+ // Assert
+ Assert.Equal("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed.", ex.Message);
+ }
- protected abstract decimal ComputeMatched(string template);
+ protected abstract decimal ComputeMatched(string template);
- protected abstract decimal ComputeGenerated(string template);
- }
+ protected abstract decimal ComputeGenerated(string template);
}
diff --git a/src/Http/Routing/test/UnitTests/Template/RouteTemplatePrecedenceTests.cs b/src/Http/Routing/test/UnitTests/Template/RouteTemplatePrecedenceTests.cs
index 2dab105abf..184f4f4a72 100644
--- a/src/Http/Routing/test/UnitTests/Template/RouteTemplatePrecedenceTests.cs
+++ b/src/Http/Routing/test/UnitTests/Template/RouteTemplatePrecedenceTests.cs
@@ -3,24 +3,23 @@
using System;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+public class RouteTemplatePrecedenceTests : RoutePrecedenceTestsBase
{
- public class RouteTemplatePrecedenceTests : RoutePrecedenceTestsBase
+ protected override decimal ComputeMatched(string template)
{
- protected override decimal ComputeMatched(string template)
- {
- return ComputeRouteTemplate(template, RoutePrecedence.ComputeInbound);
- }
+ return ComputeRouteTemplate(template, RoutePrecedence.ComputeInbound);
+ }
- protected override decimal ComputeGenerated(string template)
- {
- return ComputeRouteTemplate(template, RoutePrecedence.ComputeOutbound);
- }
+ protected override decimal ComputeGenerated(string template)
+ {
+ return ComputeRouteTemplate(template, RoutePrecedence.ComputeOutbound);
+ }
- private static decimal ComputeRouteTemplate(string template, Func<RouteTemplate, decimal> func)
- {
- var parsed = TemplateParser.Parse(template);
- return func(parsed);
- }
+ private static decimal ComputeRouteTemplate(string template, Func<RouteTemplate, decimal> func)
+ {
+ var parsed = TemplateParser.Parse(template);
+ return func(parsed);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Template/TemplateBinderTests.cs b/src/Http/Routing/test/UnitTests/Template/TemplateBinderTests.cs
index ffb585da50..ebc8fd4fcf 100644
--- a/src/Http/Routing/test/UnitTests/Template/TemplateBinderTests.cs
+++ b/src/Http/Routing/test/UnitTests/Template/TemplateBinderTests.cs
@@ -13,13 +13,13 @@ using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Template.Tests
+namespace Microsoft.AspNetCore.Routing.Template.Tests;
+
+public class TemplateBinderTests
{
- public class TemplateBinderTests
- {
- public static TheoryData EmptyAndNullDefaultValues =>
- new TheoryData<string, RouteValueDictionary, RouteValueDictionary, string>
- {
+ public static TheoryData EmptyAndNullDefaultValues =>
+ new TheoryData<string, RouteValueDictionary, RouteValueDictionary, string>
+ {
{
"Test/{val1}/{val2}",
new RouteValueDictionary(new {val1 = "", val2 = ""}),
@@ -104,85 +104,85 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
new RouteValueDictionary(new {val1 = "42", val2 = "11"}),
"/Test/42/11"
},
- };
-
- [Theory]
- [MemberData(nameof(EmptyAndNullDefaultValues))]
- public void Binding_WithEmptyAndNull_DefaultValues(
- string template,
- RouteValueDictionary defaults,
- RouteValueDictionary values,
- string expected)
- {
- // Arrange
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- TemplateParser.Parse(template),
- defaults);
-
- // Act & Assert
- var result = binder.GetValues(ambientValues: null, values: values);
- if (result == null)
- {
- if (expected == null)
- {
- return;
- }
- else
- {
- Assert.NotNull(result);
- }
- }
+ };
+
+ [Theory]
+ [MemberData(nameof(EmptyAndNullDefaultValues))]
+ public void Binding_WithEmptyAndNull_DefaultValues(
+ string template,
+ RouteValueDictionary defaults,
+ RouteValueDictionary values,
+ string expected)
+ {
+ // Arrange
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ TemplateParser.Parse(template),
+ defaults);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
+ // Act & Assert
+ var result = binder.GetValues(ambientValues: null, values: values);
+ if (result == null)
+ {
if (expected == null)
{
- Assert.Null(boundTemplate);
+ return;
}
else
{
- Assert.NotNull(boundTemplate);
- Assert.Equal(expected, boundTemplate);
+ Assert.NotNull(result);
}
}
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnBothEndsMatches()
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+ if (expected == null)
{
- RunTest(
- "language/{lang}-{region}",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- "/language/xx-yy");
+ Assert.Null(boundTemplate);
}
-
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnLeftEndMatches()
+ else
{
- RunTest(
- "language/{lang}-{region}a",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- "/language/xx-yya");
+ Assert.NotNull(boundTemplate);
+ Assert.Equal(expected, boundTemplate);
}
+ }
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnRightEndMatches()
- {
- RunTest(
- "language/a{lang}-{region}",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- "/language/axx-yy");
- }
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnBothEndsMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ "/language/xx-yy");
+ }
- public static TheoryData OptionalParamValues =>
- new TheoryData<string, RouteValueDictionary, RouteValueDictionary, RouteValueDictionary, string>
- {
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnLeftEndMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}a",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ "/language/xx-yya");
+ }
+
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnRightEndMatches()
+ {
+ RunTest(
+ "language/a{lang}-{region}",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ "/language/axx-yy");
+ }
+
+ public static TheoryData OptionalParamValues =>
+ new TheoryData<string, RouteValueDictionary, RouteValueDictionary, RouteValueDictionary, string>
+ {
// defaults
// ambient values
// values
@@ -252,487 +252,487 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
"/Test/someval1.someval2?" +
"val3=someval3"
},
- };
-
- [Theory]
- [MemberData(nameof(OptionalParamValues))]
- public void GetVirtualPathWithMultiSegmentWithOptionalParam(
- string template,
- RouteValueDictionary defaults,
- RouteValueDictionary ambientValues,
- RouteValueDictionary values,
- string expected)
- {
- // Arrange
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- TemplateParser.Parse(template),
- defaults);
-
- // Act & Assert
- var result = binder.GetValues(ambientValues: ambientValues, values: values);
- if (result == null)
- {
- if (expected == null)
- {
- return;
- }
- else
- {
- Assert.NotNull(result);
- }
- }
+ };
+
+ [Theory]
+ [MemberData(nameof(OptionalParamValues))]
+ public void GetVirtualPathWithMultiSegmentWithOptionalParam(
+ string template,
+ RouteValueDictionary defaults,
+ RouteValueDictionary ambientValues,
+ RouteValueDictionary values,
+ string expected)
+ {
+ // Arrange
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ TemplateParser.Parse(template),
+ defaults);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
+ // Act & Assert
+ var result = binder.GetValues(ambientValues: ambientValues, values: values);
+ if (result == null)
+ {
if (expected == null)
{
- Assert.Null(boundTemplate);
+ return;
}
else
{
- Assert.NotNull(boundTemplate);
- Assert.Equal(expected, boundTemplate);
+ Assert.NotNull(result);
}
}
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndMatches()
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+ if (expected == null)
{
- RunTest(
- "language/a{lang}-{region}a",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- "/language/axx-yya");
- }
-
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndDoesNotMatch()
- {
- RunTest(
- "language/a{lang}-{region}a",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "", region = "yy" }),
- null);
- }
-
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
- {
- RunTest(
- "language/a{lang}-{region}a",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "xx", region = "" }),
- null);
- }
-
- [Fact]
- public void GetVirtualPathWithSimpleMultiSegmentParamsOnBothEndsMatches()
- {
- RunTest(
- "language/{lang}",
- null,
- new RouteValueDictionary(new { lang = "en" }),
- new RouteValueDictionary(new { lang = "xx" }),
- "/language/xx");
- }
-
- [Fact]
- public void GetVirtualPathWithSimpleMultiSegmentParamsOnLeftEndMatches()
- {
- RunTest(
- "language/{lang}-",
- null,
- new RouteValueDictionary(new { lang = "en" }),
- new RouteValueDictionary(new { lang = "xx" }),
- "/language/xx-");
- }
-
- [Fact]
- public void GetVirtualPathWithSimpleMultiSegmentParamsOnRightEndMatches()
- {
- RunTest(
- "language/a{lang}",
- null,
- new RouteValueDictionary(new { lang = "en" }),
- new RouteValueDictionary(new { lang = "xx" }),
- "/language/axx");
+ Assert.Null(boundTemplate);
}
-
- [Fact]
- public void GetVirtualPathWithSimpleMultiSegmentParamsOnNeitherEndMatches()
+ else
{
- RunTest(
- "language/a{lang}a",
- null,
- new RouteValueDictionary(new { lang = "en" }),
- new RouteValueDictionary(new { lang = "xx" }),
- "/language/axxa");
+ Assert.NotNull(boundTemplate);
+ Assert.Equal(expected, boundTemplate);
}
+ }
- [Fact]
- public void GetVirtualPathWithMultiSegmentStandardMvcRouteMatches()
- {
- RunTest(
- "{controller}.mvc/{action}/{id}",
- new RouteValueDictionary(new { action = "Index", id = (string)null }),
- new RouteValueDictionary(new { controller = "home", action = "list", id = (string)null }),
- new RouteValueDictionary(new { controller = "products" }),
- "/products.mvc");
- }
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndMatches()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ "/language/axx-yya");
+ }
- [Fact]
- public void GetVirtualPathWithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
- {
- RunTest(
- "language/{lang}-{region}",
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- new RouteValueDictionary(new { lang = "en", region = "US" }),
- new RouteValueDictionary(new { lang = "zz" }),
- "/language/zz-yy");
- }
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndDoesNotMatch()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "", region = "yy" }),
+ null);
+ }
- [Fact]
- public void GetUrlWithDefaultValue()
- {
- // URL should be found but excluding the 'id' parameter, which has only a default value.
- RunTest(
- "{controller}/{action}/{id}",
- new RouteValueDictionary(new { id = "defaultid" }),
- new RouteValueDictionary(new { controller = "home", action = "oldaction" }),
- new RouteValueDictionary(new { action = "newaction" }),
- "/home/newaction");
- }
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "xx", region = "" }),
+ null);
+ }
- [Fact]
- public void GetVirtualPathWithEmptyStringRequiredValueReturnsNull()
- {
- RunTest(
- "foo/{controller}",
- null,
- new RouteValueDictionary(new { }),
- new RouteValueDictionary(new { controller = "" }),
- null);
- }
+ [Fact]
+ public void GetVirtualPathWithSimpleMultiSegmentParamsOnBothEndsMatches()
+ {
+ RunTest(
+ "language/{lang}",
+ null,
+ new RouteValueDictionary(new { lang = "en" }),
+ new RouteValueDictionary(new { lang = "xx" }),
+ "/language/xx");
+ }
- [Fact]
- public void GetVirtualPathWithNullRequiredValueReturnsNull()
- {
- RunTest(
- "foo/{controller}",
- null,
- new RouteValueDictionary(new { }),
- new RouteValueDictionary(new { controller = (string)null }),
- null);
- }
+ [Fact]
+ public void GetVirtualPathWithSimpleMultiSegmentParamsOnLeftEndMatches()
+ {
+ RunTest(
+ "language/{lang}-",
+ null,
+ new RouteValueDictionary(new { lang = "en" }),
+ new RouteValueDictionary(new { lang = "xx" }),
+ "/language/xx-");
+ }
- [Fact]
- public void GetVirtualPathWithRequiredValueReturnsPath()
- {
- RunTest(
- "foo/{controller}",
- null,
- new RouteValueDictionary(new { }),
- new RouteValueDictionary(new { controller = "home" }),
- "/foo/home");
- }
+ [Fact]
+ public void GetVirtualPathWithSimpleMultiSegmentParamsOnRightEndMatches()
+ {
+ RunTest(
+ "language/a{lang}",
+ null,
+ new RouteValueDictionary(new { lang = "en" }),
+ new RouteValueDictionary(new { lang = "xx" }),
+ "/language/axx");
+ }
- [Fact]
- public void GetUrlWithNullDefaultValue()
- {
- // URL should be found but excluding the 'id' parameter, which has only a default value.
- RunTest(
- "{controller}/{action}/{id}",
- new RouteValueDictionary(new { id = (string)null }),
- new RouteValueDictionary(new { controller = "home", action = "oldaction", id = (string)null }),
- new RouteValueDictionary(new { action = "newaction" }),
- "/home/newaction");
- }
+ [Fact]
+ public void GetVirtualPathWithSimpleMultiSegmentParamsOnNeitherEndMatches()
+ {
+ RunTest(
+ "language/a{lang}a",
+ null,
+ new RouteValueDictionary(new { lang = "en" }),
+ new RouteValueDictionary(new { lang = "xx" }),
+ "/language/axxa");
+ }
- [Fact]
- public void GetVirtualPathCanFillInSeparatedParametersWithDefaultValues()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- new RouteValueDictionary(),
- new RouteValueDictionary(new { controller = "Orders" }),
- "/Orders/en-US");
- }
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentStandardMvcRouteMatches()
+ {
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ new RouteValueDictionary(new { action = "Index", id = (string)null }),
+ new RouteValueDictionary(new { controller = "home", action = "list", id = (string)null }),
+ new RouteValueDictionary(new { controller = "products" }),
+ "/products.mvc");
+ }
- [Fact]
- public void GetVirtualPathWithUnusedNullValueShouldGenerateUrlAndIgnoreNullValue()
- {
- RunTest(
- "{controller}.mvc/{action}/{id}",
- new RouteValueDictionary(new { action = "Index", id = "" }),
- new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
- new RouteValueDictionary(new { controller = "Home", action = "TestAction", id = "1", format = (string)null }),
- "/Home.mvc/TestAction/1");
- }
+ [Fact]
+ public void GetVirtualPathWithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}",
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ new RouteValueDictionary(new { lang = "en", region = "US" }),
+ new RouteValueDictionary(new { lang = "zz" }),
+ "/language/zz-yy");
+ }
- [Fact]
- public void GetUrlWithMissingValuesDoesntMatch()
- {
- RunTest(
- "{controller}/{action}/{id}",
- null,
- new { controller = "home", action = "oldaction" },
- new { action = "newaction" },
- null);
- }
+ [Fact]
+ public void GetUrlWithDefaultValue()
+ {
+ // URL should be found but excluding the 'id' parameter, which has only a default value.
+ RunTest(
+ "{controller}/{action}/{id}",
+ new RouteValueDictionary(new { id = "defaultid" }),
+ new RouteValueDictionary(new { controller = "home", action = "oldaction" }),
+ new RouteValueDictionary(new { action = "newaction" }),
+ "/home/newaction");
+ }
- [Fact]
- public void GetUrlWithEmptyRequiredValuesReturnsNull()
- {
- RunTest(
- "{p1}/{p2}/{p3}",
- null,
- new { p1 = "v1", },
- new { p2 = "", p3 = "" },
- null);
- }
+ [Fact]
+ public void GetVirtualPathWithEmptyStringRequiredValueReturnsNull()
+ {
+ RunTest(
+ "foo/{controller}",
+ null,
+ new RouteValueDictionary(new { }),
+ new RouteValueDictionary(new { controller = "" }),
+ null);
+ }
- [Fact]
- public void GetUrlWithEmptyOptionalValuesReturnsShortUrl()
- {
- RunTest(
- "{p1}/{p2}/{p3}",
- new { p2 = "d2", p3 = "d3" },
- new { p1 = "v1", },
- new { p2 = "", p3 = "" },
- "/v1");
- }
+ [Fact]
+ public void GetVirtualPathWithNullRequiredValueReturnsNull()
+ {
+ RunTest(
+ "foo/{controller}",
+ null,
+ new RouteValueDictionary(new { }),
+ new RouteValueDictionary(new { controller = (string)null }),
+ null);
+ }
- [Fact]
- public void GetUrlShouldIgnoreValuesAfterChangedParameter()
- {
- RunTest(
- "{controller}/{action}/{id}",
- new { action = "Index", id = (string)null },
- new { controller = "orig", action = "init", id = "123" },
- new { action = "new", },
- "/orig/new");
- }
+ [Fact]
+ public void GetVirtualPathWithRequiredValueReturnsPath()
+ {
+ RunTest(
+ "foo/{controller}",
+ null,
+ new RouteValueDictionary(new { }),
+ new RouteValueDictionary(new { controller = "home" }),
+ "/foo/home");
+ }
- [Fact]
- public void GetUrlWithNullForMiddleParameterIgnoresRemainingParameters()
- {
- RunTest(
- "UrlGeneration1/{controller}.mvc/{action}/{category}/{year}/{occasion}/{SafeParam}",
- new { year = 1995, occasion = "Christmas", action = "Play", SafeParam = "SafeParamValue" },
- new { controller = "UrlRouting", action = "Play", category = "Photos", year = "2008", occasion = "Easter", SafeParam = "SafeParamValue" },
- new { year = (string)null, occasion = "Hola" },
- "/UrlGeneration1/UrlRouting.mvc/Play/"
- + "Photos/1995/Hola");
- }
+ [Fact]
+ public void GetUrlWithNullDefaultValue()
+ {
+ // URL should be found but excluding the 'id' parameter, which has only a default value.
+ RunTest(
+ "{controller}/{action}/{id}",
+ new RouteValueDictionary(new { id = (string)null }),
+ new RouteValueDictionary(new { controller = "home", action = "oldaction", id = (string)null }),
+ new RouteValueDictionary(new { action = "newaction" }),
+ "/home/newaction");
+ }
- [Fact]
- public void GetUrlWithEmptyStringForMiddleParameterIgnoresRemainingParameters()
- {
- var ambientValues = new RouteValueDictionary();
- ambientValues.Add("controller", "UrlRouting");
- ambientValues.Add("action", "Play");
- ambientValues.Add("category", "Photos");
- ambientValues.Add("year", "2008");
- ambientValues.Add("occasion", "Easter");
- ambientValues.Add("SafeParam", "SafeParamValue");
-
- var values = new RouteValueDictionary();
- values.Add("year", String.Empty);
- values.Add("occasion", "Hola");
+ [Fact]
+ public void GetVirtualPathCanFillInSeparatedParametersWithDefaultValues()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ new RouteValueDictionary(),
+ new RouteValueDictionary(new { controller = "Orders" }),
+ "/Orders/en-US");
+ }
- RunTest(
- "UrlGeneration1/{controller}.mvc/{action}/{category}/{year}/{occasion}/{SafeParam}",
- new RouteValueDictionary(new { year = 1995, occasion = "Christmas", action = "Play", SafeParam = "SafeParamValue" }),
- ambientValues,
- values,
- "/UrlGeneration1/UrlRouting.mvc/"
- + "Play/Photos/1995/Hola");
- }
+ [Fact]
+ public void GetVirtualPathWithUnusedNullValueShouldGenerateUrlAndIgnoreNullValue()
+ {
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ new RouteValueDictionary(new { action = "Index", id = "" }),
+ new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" }),
+ new RouteValueDictionary(new { controller = "Home", action = "TestAction", id = "1", format = (string)null }),
+ "/Home.mvc/TestAction/1");
+ }
- [Fact]
- public void GetUrlWithEmptyStringForMiddleParameterShouldUseDefaultValue()
- {
- var ambientValues = new RouteValueDictionary();
- ambientValues.Add("Controller", "Test");
- ambientValues.Add("Action", "Fallback");
- ambientValues.Add("param1", "fallback1");
- ambientValues.Add("param2", "fallback2");
- ambientValues.Add("param3", "fallback3");
+ [Fact]
+ public void GetUrlWithMissingValuesDoesntMatch()
+ {
+ RunTest(
+ "{controller}/{action}/{id}",
+ null,
+ new { controller = "home", action = "oldaction" },
+ new { action = "newaction" },
+ null);
+ }
- var values = new RouteValueDictionary();
- values.Add("controller", "subtest");
- values.Add("param1", "b");
+ [Fact]
+ public void GetUrlWithEmptyRequiredValuesReturnsNull()
+ {
+ RunTest(
+ "{p1}/{p2}/{p3}",
+ null,
+ new { p1 = "v1", },
+ new { p2 = "", p3 = "" },
+ null);
+ }
- RunTest(
- "{controller}.mvc/{action}/{param1}",
- new RouteValueDictionary(new { action = "Default" }),
- ambientValues,
- values,
- "/subtest.mvc/Default/b");
- }
+ [Fact]
+ public void GetUrlWithEmptyOptionalValuesReturnsShortUrl()
+ {
+ RunTest(
+ "{p1}/{p2}/{p3}",
+ new { p2 = "d2", p3 = "d3" },
+ new { p1 = "v1", },
+ new { p2 = "", p3 = "" },
+ "/v1");
+ }
- [Fact]
- public void GetUrlVerifyEncoding()
- {
- var values = new RouteValueDictionary();
- values.Add("controller", "#;?:@&=+$,");
- values.Add("action", "showcategory");
- values.Add("id", 123);
- values.Add("so?rt", "de?sc");
- values.Add("maxPrice", 100);
+ [Fact]
+ public void GetUrlShouldIgnoreValuesAfterChangedParameter()
+ {
+ RunTest(
+ "{controller}/{action}/{id}",
+ new { action = "Index", id = (string)null },
+ new { controller = "orig", action = "init", id = "123" },
+ new { action = "new", },
+ "/orig/new");
+ }
- RunTest(
- "{controller}.mvc/{action}/{id}",
- new RouteValueDictionary(new { controller = "Home" }),
- new RouteValueDictionary(new { controller = "home", action = "Index", id = (string)null }),
- values,
- "/%23;%3F%3A@%26%3D%2B$,.mvc/showcategory/123?so%3Frt=de%3Fsc&maxPrice=100");
- }
+ [Fact]
+ public void GetUrlWithNullForMiddleParameterIgnoresRemainingParameters()
+ {
+ RunTest(
+ "UrlGeneration1/{controller}.mvc/{action}/{category}/{year}/{occasion}/{SafeParam}",
+ new { year = 1995, occasion = "Christmas", action = "Play", SafeParam = "SafeParamValue" },
+ new { controller = "UrlRouting", action = "Play", category = "Photos", year = "2008", occasion = "Easter", SafeParam = "SafeParamValue" },
+ new { year = (string)null, occasion = "Hola" },
+ "/UrlGeneration1/UrlRouting.mvc/Play/"
+ + "Photos/1995/Hola");
+ }
- [Fact]
- public void GetUrlGeneratesQueryStringForNewValuesAndEscapesQueryString()
- {
- var values = new RouteValueDictionary(new { controller = "products", action = "showcategory", id = 123, maxPrice = 100 });
- values.Add("so?rt", "de?sc");
+ [Fact]
+ public void GetUrlWithEmptyStringForMiddleParameterIgnoresRemainingParameters()
+ {
+ var ambientValues = new RouteValueDictionary();
+ ambientValues.Add("controller", "UrlRouting");
+ ambientValues.Add("action", "Play");
+ ambientValues.Add("category", "Photos");
+ ambientValues.Add("year", "2008");
+ ambientValues.Add("occasion", "Easter");
+ ambientValues.Add("SafeParam", "SafeParamValue");
+
+ var values = new RouteValueDictionary();
+ values.Add("year", String.Empty);
+ values.Add("occasion", "Hola");
+
+ RunTest(
+ "UrlGeneration1/{controller}.mvc/{action}/{category}/{year}/{occasion}/{SafeParam}",
+ new RouteValueDictionary(new { year = 1995, occasion = "Christmas", action = "Play", SafeParam = "SafeParamValue" }),
+ ambientValues,
+ values,
+ "/UrlGeneration1/UrlRouting.mvc/"
+ + "Play/Photos/1995/Hola");
+ }
- RunTest(
- "{controller}.mvc/{action}/{id}",
- new RouteValueDictionary(new { controller = "Home" }),
- new RouteValueDictionary(new { controller = "home", action = "Index", id = (string)null }),
- values,
- "/products.mvc/showcategory/123" +
- "?so%3Frt=de%3Fsc&maxPrice=100");
- }
+ [Fact]
+ public void GetUrlWithEmptyStringForMiddleParameterShouldUseDefaultValue()
+ {
+ var ambientValues = new RouteValueDictionary();
+ ambientValues.Add("Controller", "Test");
+ ambientValues.Add("Action", "Fallback");
+ ambientValues.Add("param1", "fallback1");
+ ambientValues.Add("param2", "fallback2");
+ ambientValues.Add("param3", "fallback3");
+
+ var values = new RouteValueDictionary();
+ values.Add("controller", "subtest");
+ values.Add("param1", "b");
+
+ RunTest(
+ "{controller}.mvc/{action}/{param1}",
+ new RouteValueDictionary(new { action = "Default" }),
+ ambientValues,
+ values,
+ "/subtest.mvc/Default/b");
+ }
- [Fact]
- public void GetUrlGeneratesQueryStringForNewValuesButIgnoresNewValuesThatMatchDefaults()
- {
- RunTest(
- "{controller}.mvc/{action}/{id}",
- new RouteValueDictionary(new { controller = "Home", Custom = "customValue" }),
- new RouteValueDictionary(new { controller = "Home", action = "Index", id = (string)null }),
- new RouteValueDictionary(
- new
- {
- controller = "products",
- action = "showcategory",
- id = 123,
- sort = "desc",
- maxPrice = 100,
- custom = "customValue"
- }),
- "/products.mvc/showcategory/123" +
- "?sort=desc&maxPrice=100");
- }
+ [Fact]
+ public void GetUrlVerifyEncoding()
+ {
+ var values = new RouteValueDictionary();
+ values.Add("controller", "#;?:@&=+$,");
+ values.Add("action", "showcategory");
+ values.Add("id", 123);
+ values.Add("so?rt", "de?sc");
+ values.Add("maxPrice", 100);
+
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ new RouteValueDictionary(new { controller = "Home" }),
+ new RouteValueDictionary(new { controller = "home", action = "Index", id = (string)null }),
+ values,
+ "/%23;%3F%3A@%26%3D%2B$,.mvc/showcategory/123?so%3Frt=de%3Fsc&maxPrice=100");
+ }
- [Fact]
- public void GetVirtualPathEncodesParametersAndLiterals()
- {
- RunTest(
- "bl%og/{controller}/he llo/{action}",
- null,
- new RouteValueDictionary(new { controller = "ho%me", action = "li st" }),
- new RouteValueDictionary(),
- "/bl%25og/ho%25me/he%20llo/li%20st");
- }
+ [Fact]
+ public void GetUrlGeneratesQueryStringForNewValuesAndEscapesQueryString()
+ {
+ var values = new RouteValueDictionary(new { controller = "products", action = "showcategory", id = 123, maxPrice = 100 });
+ values.Add("so?rt", "de?sc");
+
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ new RouteValueDictionary(new { controller = "Home" }),
+ new RouteValueDictionary(new { controller = "home", action = "Index", id = (string)null }),
+ values,
+ "/products.mvc/showcategory/123" +
+ "?so%3Frt=de%3Fsc&maxPrice=100");
+ }
- [Fact]
- public void GetVirtualDoesNotEncodeLeadingSlashes()
- {
- RunTest(
- "{controller}/{action}",
- null,
- new RouteValueDictionary(new { controller = "/home", action = "/my/index" }),
- new RouteValueDictionary(),
- "/home/%2Fmy%2Findex");
- }
+ [Fact]
+ public void GetUrlGeneratesQueryStringForNewValuesButIgnoresNewValuesThatMatchDefaults()
+ {
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ new RouteValueDictionary(new { controller = "Home", Custom = "customValue" }),
+ new RouteValueDictionary(new { controller = "Home", action = "Index", id = (string)null }),
+ new RouteValueDictionary(
+ new
+ {
+ controller = "products",
+ action = "showcategory",
+ id = 123,
+ sort = "desc",
+ maxPrice = 100,
+ custom = "customValue"
+ }),
+ "/products.mvc/showcategory/123" +
+ "?sort=desc&maxPrice=100");
+ }
- [Fact]
- public void GetUrlWithCatchAllWithValue()
- {
- RunTest(
- "{p1}/{*p2}",
- new RouteValueDictionary(new { id = "defaultid" }),
- new RouteValueDictionary(new { p1 = "v1" }),
- new RouteValueDictionary(new { p2 = "v2a/v2b" }),
- "/v1/v2a%2Fv2b");
- }
+ [Fact]
+ public void GetVirtualPathEncodesParametersAndLiterals()
+ {
+ RunTest(
+ "bl%og/{controller}/he llo/{action}",
+ null,
+ new RouteValueDictionary(new { controller = "ho%me", action = "li st" }),
+ new RouteValueDictionary(),
+ "/bl%25og/ho%25me/he%20llo/li%20st");
+ }
- [Fact]
- public void GetUrlWithCatchAllWithEmptyValue()
- {
- RunTest(
- "{p1}/{*p2}",
- new RouteValueDictionary(new { id = "defaultid" }),
- new RouteValueDictionary(new { p1 = "v1" }),
- new RouteValueDictionary(new { p2 = "" }),
- "/v1");
- }
+ [Fact]
+ public void GetVirtualDoesNotEncodeLeadingSlashes()
+ {
+ RunTest(
+ "{controller}/{action}",
+ null,
+ new RouteValueDictionary(new { controller = "/home", action = "/my/index" }),
+ new RouteValueDictionary(),
+ "/home/%2Fmy%2Findex");
+ }
- [Fact]
- public void GetUrlWithCatchAllWithNullValue()
- {
- RunTest(
- "{p1}/{*p2}",
- new RouteValueDictionary(new { id = "defaultid" }),
- new RouteValueDictionary(new { p1 = "v1" }),
- new RouteValueDictionary(new { p2 = (string)null }),
- "/v1");
- }
+ [Fact]
+ public void GetUrlWithCatchAllWithValue()
+ {
+ RunTest(
+ "{p1}/{*p2}",
+ new RouteValueDictionary(new { id = "defaultid" }),
+ new RouteValueDictionary(new { p1 = "v1" }),
+ new RouteValueDictionary(new { p2 = "v2a/v2b" }),
+ "/v1/v2a%2Fv2b");
+ }
- [Fact]
- public void GetUrlWithLeadingTildeSlash()
- {
- RunTest(
- "~/foo",
- null,
- null,
- new RouteValueDictionary(new { }),
- "/foo");
- }
+ [Fact]
+ public void GetUrlWithCatchAllWithEmptyValue()
+ {
+ RunTest(
+ "{p1}/{*p2}",
+ new RouteValueDictionary(new { id = "defaultid" }),
+ new RouteValueDictionary(new { p1 = "v1" }),
+ new RouteValueDictionary(new { p2 = "" }),
+ "/v1");
+ }
- [Fact]
- public void GetUrlWithLeadingSlash()
- {
- RunTest(
- "/foo",
- null,
- null,
- new RouteValueDictionary(new { }),
- "/foo");
- }
+ [Fact]
+ public void GetUrlWithCatchAllWithNullValue()
+ {
+ RunTest(
+ "{p1}/{*p2}",
+ new RouteValueDictionary(new { id = "defaultid" }),
+ new RouteValueDictionary(new { p1 = "v1" }),
+ new RouteValueDictionary(new { p2 = (string)null }),
+ "/v1");
+ }
- [Fact]
- public void TemplateBinder_KeepsExplicitlySuppliedRouteValues_OnFailedRouteMatch()
- {
- // Arrange
- var template = "{area?}/{controller=Home}/{action=Index}/{id?}";
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- TemplateParser.Parse(template),
- defaults: null);
- var ambientValues = new RouteValueDictionary();
- var routeValues = new RouteValueDictionary(new { controller = "Test", action = "Index" });
+ [Fact]
+ public void GetUrlWithLeadingTildeSlash()
+ {
+ RunTest(
+ "~/foo",
+ null,
+ null,
+ new RouteValueDictionary(new { }),
+ "/foo");
+ }
- // Act
- var templateValuesResult = binder.GetValues(ambientValues, routeValues);
- var boundTemplate = binder.BindValues(templateValuesResult.AcceptedValues);
+ [Fact]
+ public void GetUrlWithLeadingSlash()
+ {
+ RunTest(
+ "/foo",
+ null,
+ null,
+ new RouteValueDictionary(new { }),
+ "/foo");
+ }
- // Assert
- Assert.Null(boundTemplate);
- Assert.Equal(2, templateValuesResult.CombinedValues.Count);
- object routeValue;
- Assert.True(templateValuesResult.CombinedValues.TryGetValue("controller", out routeValue));
- Assert.Equal("Test", routeValue?.ToString());
- Assert.True(templateValuesResult.CombinedValues.TryGetValue("action", out routeValue));
- Assert.Equal("Index", routeValue?.ToString());
- }
+ [Fact]
+ public void TemplateBinder_KeepsExplicitlySuppliedRouteValues_OnFailedRouteMatch()
+ {
+ // Arrange
+ var template = "{area?}/{controller=Home}/{action=Index}/{id?}";
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ TemplateParser.Parse(template),
+ defaults: null);
+ var ambientValues = new RouteValueDictionary();
+ var routeValues = new RouteValueDictionary(new { controller = "Test", action = "Index" });
+
+ // Act
+ var templateValuesResult = binder.GetValues(ambientValues, routeValues);
+ var boundTemplate = binder.BindValues(templateValuesResult.AcceptedValues);
+
+ // Assert
+ Assert.Null(boundTemplate);
+ Assert.Equal(2, templateValuesResult.CombinedValues.Count);
+ object routeValue;
+ Assert.True(templateValuesResult.CombinedValues.TryGetValue("controller", out routeValue));
+ Assert.Equal("Test", routeValue?.ToString());
+ Assert.True(templateValuesResult.CombinedValues.TryGetValue("action", out routeValue));
+ Assert.Equal("Index", routeValue?.ToString());
+ }
#if ROUTE_COLLECTION
@@ -1133,340 +1133,339 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
#endif
- private static void RunTest(
- string template,
- RouteValueDictionary defaults,
- RouteValueDictionary ambientValues,
- RouteValueDictionary values,
- string expected)
- {
- // Arrange
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- TemplateParser.Parse(template),
- defaults);
-
- // Act & Assert
- var result = binder.GetValues(ambientValues, values);
- if (result == null)
- {
- if (expected == null)
- {
- return;
- }
- else
- {
- Assert.NotNull(result);
- }
- }
+ private static void RunTest(
+ string template,
+ RouteValueDictionary defaults,
+ RouteValueDictionary ambientValues,
+ RouteValueDictionary values,
+ string expected)
+ {
+ // Arrange
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ TemplateParser.Parse(template),
+ defaults);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
+ // Act & Assert
+ var result = binder.GetValues(ambientValues, values);
+ if (result == null)
+ {
if (expected == null)
{
- Assert.Null(boundTemplate);
+ return;
}
else
{
- Assert.NotNull(boundTemplate);
-
- // We want to chop off the query string and compare that using an unordered comparison
- var expectedParts = new PathAndQuery(expected);
- var actualParts = new PathAndQuery(boundTemplate);
-
- Assert.Equal(expectedParts.Path, actualParts.Path);
-
- if (expectedParts.Parameters == null)
- {
- Assert.Null(actualParts.Parameters);
- }
- else
- {
- Assert.Equal(expectedParts.Parameters.Count, actualParts.Parameters.Count);
-
- foreach (var kvp in expectedParts.Parameters)
- {
- string value;
- Assert.True(actualParts.Parameters.TryGetValue(kvp.Key, out value));
- Assert.Equal(kvp.Value, value);
- }
- }
+ Assert.NotNull(result);
}
}
- private static void RunTest(
- string template,
- object defaults,
- object ambientValues,
- object values,
- string expected)
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+ if (expected == null)
{
- RunTest(
- template,
- new RouteValueDictionary(defaults),
- new RouteValueDictionary(ambientValues),
- new RouteValueDictionary(values),
- expected);
+ Assert.Null(boundTemplate);
}
-
- [Theory]
- [InlineData(null, null, true)]
- [InlineData("", null, true)]
- [InlineData(null, "", true)]
- [InlineData("blog", null, false)]
- [InlineData(null, "store", false)]
- [InlineData("Cool", "cool", true)]
- [InlineData("Co0l", "cool", false)]
- public void RoutePartsEqualTest(object left, object right, bool expected)
+ else
{
- // Arrange & Act & Assert
- if (expected)
+ Assert.NotNull(boundTemplate);
+
+ // We want to chop off the query string and compare that using an unordered comparison
+ var expectedParts = new PathAndQuery(expected);
+ var actualParts = new PathAndQuery(boundTemplate);
+
+ Assert.Equal(expectedParts.Path, actualParts.Path);
+
+ if (expectedParts.Parameters == null)
{
- Assert.True(TemplateBinder.RoutePartsEqual(left, right));
+ Assert.Null(actualParts.Parameters);
}
else
{
- Assert.False(TemplateBinder.RoutePartsEqual(left, right));
+ Assert.Equal(expectedParts.Parameters.Count, actualParts.Parameters.Count);
+
+ foreach (var kvp in expectedParts.Parameters)
+ {
+ string value;
+ Assert.True(actualParts.Parameters.TryGetValue(kvp.Key, out value));
+ Assert.Equal(kvp.Value, value);
+ }
}
}
+ }
- [Fact]
- public void GetValues_SuccessfullyMatchesRouteValues_ForExplicitEmptyStringValue_AndNullDefault()
- {
- // Arrange
- var expected = "/Home/Index";
- var template = "Home/Index";
- var defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null });
- var ambientValues = new RouteValueDictionary(new { controller = "Rail", action = "Schedule", area = "Travel" });
- var explicitValues = new RouteValueDictionary(new { controller = "Home", action = "Index", area = "" });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- TemplateParser.Parse(template),
- defaults);
-
- // Act1
- var result = binder.GetValues(ambientValues, explicitValues);
-
- // Assert1
- Assert.NotNull(result);
-
- // Act2
- var boundTemplate = binder.BindValues(result.AcceptedValues);
-
- // Assert2
- Assert.NotNull(boundTemplate);
- Assert.Equal(expected, boundTemplate);
- }
+ private static void RunTest(
+ string template,
+ object defaults,
+ object ambientValues,
+ object values,
+ string expected)
+ {
+ RunTest(
+ template,
+ new RouteValueDictionary(defaults),
+ new RouteValueDictionary(ambientValues),
+ new RouteValueDictionary(values),
+ expected);
+ }
- [Fact]
- public void GetValues_SuccessfullyMatchesRouteValues_ForExplicitNullValue_AndEmptyStringDefault()
+ [Theory]
+ [InlineData(null, null, true)]
+ [InlineData("", null, true)]
+ [InlineData(null, "", true)]
+ [InlineData("blog", null, false)]
+ [InlineData(null, "store", false)]
+ [InlineData("Cool", "cool", true)]
+ [InlineData("Co0l", "cool", false)]
+ public void RoutePartsEqualTest(object left, object right, bool expected)
+ {
+ // Arrange & Act & Assert
+ if (expected)
{
- // Arrange
- var expected = "/Home/Index";
- var template = "Home/Index";
- var defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", area = "" });
- var ambientValues = new RouteValueDictionary(new { controller = "Rail", action = "Schedule", area = "Travel" });
- var explicitValues = new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- TemplateParser.Parse(template),
- defaults);
-
- // Act1
- var result = binder.GetValues(ambientValues, explicitValues);
-
- // Assert1
- Assert.NotNull(result);
-
- // Act2
- var boundTemplate = binder.BindValues(result.AcceptedValues);
-
- // Assert2
- Assert.NotNull(boundTemplate);
- Assert.Equal(expected, boundTemplate);
+ Assert.True(TemplateBinder.RoutePartsEqual(left, right));
}
-
- [Fact]
- public void BindValues_ParameterTransformer()
+ else
{
- // Arrange
- var expected = "/ConventionalTransformerRoute/conventional-transformer/Param/my-value";
-
- var template = "ConventionalTransformerRoute/conventional-transformer/Param/{param:length(500):slugify?}";
- var defaults = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
- var ambientValues = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
- var explicitValues = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param", param = "MyValue" });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- RoutePatternFactory.Parse(
- template,
- defaults,
- parameterPolicies: null,
- requiredValues: new { area = (string)null, action = "Param", controller = "ConventionalTransformer", page = (string)null }),
- defaults,
- requiredKeys: defaults.Keys,
- parameterPolicies: new (string, IParameterPolicy)[] { ("param", new LengthRouteConstraint(500)), ("param", new SlugifyParameterTransformer()), });
-
- // Act
- var result = binder.GetValues(ambientValues, explicitValues);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
-
- // Assert
- Assert.Equal(expected, boundTemplate);
+ Assert.False(TemplateBinder.RoutePartsEqual(left, right));
}
+ }
- [Fact]
- public void BindValues_AmbientAndExplicitValuesDoNotMatch_Success()
- {
- // Arrange
- var expected = "/Travel/Flight";
-
- var template = "{area}/{controller}/{action}";
- var defaults = new RouteValueDictionary(new { action = "Index" });
- var ambientValues = new RouteValueDictionary(new { area = "Travel", controller = "Rail", action = "Index" });
- var explicitValues = new RouteValueDictionary(new { controller = "Flight", action = "Index" });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- RoutePatternFactory.Parse(
- template,
- defaults,
- parameterPolicies: null,
- requiredValues: new { area = "Travel", action = "SomeAction", controller = "Flight", page = (string)null }),
- defaults,
- requiredKeys: new string[] { "area", "action", "controller", "page" },
- parameterPolicies: null);
-
- // Act
- var result = binder.GetValues(ambientValues, explicitValues);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
+ [Fact]
+ public void GetValues_SuccessfullyMatchesRouteValues_ForExplicitEmptyStringValue_AndNullDefault()
+ {
+ // Arrange
+ var expected = "/Home/Index";
+ var template = "Home/Index";
+ var defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null });
+ var ambientValues = new RouteValueDictionary(new { controller = "Rail", action = "Schedule", area = "Travel" });
+ var explicitValues = new RouteValueDictionary(new { controller = "Home", action = "Index", area = "" });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ TemplateParser.Parse(template),
+ defaults);
+
+ // Act1
+ var result = binder.GetValues(ambientValues, explicitValues);
+
+ // Assert1
+ Assert.NotNull(result);
+
+ // Act2
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert2
+ Assert.NotNull(boundTemplate);
+ Assert.Equal(expected, boundTemplate);
+ }
- // Assert
- Assert.Equal(expected, boundTemplate);
- }
+ [Fact]
+ public void GetValues_SuccessfullyMatchesRouteValues_ForExplicitNullValue_AndEmptyStringDefault()
+ {
+ // Arrange
+ var expected = "/Home/Index";
+ var template = "Home/Index";
+ var defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", area = "" });
+ var ambientValues = new RouteValueDictionary(new { controller = "Rail", action = "Schedule", area = "Travel" });
+ var explicitValues = new RouteValueDictionary(new { controller = "Home", action = "Index", area = (string)null });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ TemplateParser.Parse(template),
+ defaults);
+
+ // Act1
+ var result = binder.GetValues(ambientValues, explicitValues);
+
+ // Assert1
+ Assert.NotNull(result);
+
+ // Act2
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert2
+ Assert.NotNull(boundTemplate);
+ Assert.Equal(expected, boundTemplate);
+ }
- [Fact]
- public void BindValues_LinkingFromPageToAController_Success()
- {
- // Arrange
- var expected = "/LG2/SomeAction";
-
- var template = "{controller=Home}/{action=Index}/{id?}";
- var defaults = new RouteValueDictionary();
- var ambientValues = new RouteValueDictionary(new { page = "/LGAnotherPage", id = "17" });
- var explicitValues = new RouteValueDictionary(new { controller = "LG2", action = "SomeAction" });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- RoutePatternFactory.Parse(
- template,
- defaults,
- parameterPolicies: null,
- requiredValues: new { area = (string)null, action = "SomeAction", controller = "LG2", page = (string)null }),
+ [Fact]
+ public void BindValues_ParameterTransformer()
+ {
+ // Arrange
+ var expected = "/ConventionalTransformerRoute/conventional-transformer/Param/my-value";
+
+ var template = "ConventionalTransformerRoute/conventional-transformer/Param/{param:length(500):slugify?}";
+ var defaults = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
+ var ambientValues = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param" });
+ var explicitValues = new RouteValueDictionary(new { controller = "ConventionalTransformer", action = "Param", param = "MyValue" });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ RoutePatternFactory.Parse(
+ template,
defaults,
- requiredKeys: new string[] { "area", "action", "controller", "page" },
- parameterPolicies: null);
-
- // Act
- var result = binder.GetValues(ambientValues, explicitValues);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
-
- // Assert
- Assert.Equal(expected, boundTemplate);
- }
+ parameterPolicies: null,
+ requiredValues: new { area = (string)null, action = "Param", controller = "ConventionalTransformer", page = (string)null }),
+ defaults,
+ requiredKeys: defaults.Keys,
+ parameterPolicies: new (string, IParameterPolicy)[] { ("param", new LengthRouteConstraint(500)), ("param", new SlugifyParameterTransformer()), });
+
+ // Act
+ var result = binder.GetValues(ambientValues, explicitValues);
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert
+ Assert.Equal(expected, boundTemplate);
+ }
- // Regression test for dotnet/aspnetcore#4212
- //
- // An ambient value should be used to satisfy a required value even if if we're discarding
- // ambient values.
- [Fact]
- public void BindValues_LinkingFromPageToAControllerInAreaWithAmbientArea_Success()
- {
- // Arrange
- var expected = "/Admin/LG2/SomeAction";
-
- var template = "{area}/{controller=Home}/{action=Index}/{id?}";
- var defaults = new RouteValueDictionary();
- var ambientValues = new RouteValueDictionary(new { area = "Admin", page = "/LGAnotherPage", id = "17" });
- var explicitValues = new RouteValueDictionary(new { controller = "LG2", action = "SomeAction" });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- RoutePatternFactory.Parse(
- template,
- defaults,
- parameterPolicies: null,
- requiredValues: new { area = "Admin", action = "SomeAction", controller = "LG2", page = (string)null }),
+ [Fact]
+ public void BindValues_AmbientAndExplicitValuesDoNotMatch_Success()
+ {
+ // Arrange
+ var expected = "/Travel/Flight";
+
+ var template = "{area}/{controller}/{action}";
+ var defaults = new RouteValueDictionary(new { action = "Index" });
+ var ambientValues = new RouteValueDictionary(new { area = "Travel", controller = "Rail", action = "Index" });
+ var explicitValues = new RouteValueDictionary(new { controller = "Flight", action = "Index" });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ RoutePatternFactory.Parse(
+ template,
defaults,
- requiredKeys: new string[] { "area", "action", "controller", "page" },
- parameterPolicies: null);
-
- // Act
- var result = binder.GetValues(ambientValues, explicitValues);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
-
- // Assert
- Assert.Equal(expected, boundTemplate);
- }
+ parameterPolicies: null,
+ requiredValues: new { area = "Travel", action = "SomeAction", controller = "Flight", page = (string)null }),
+ defaults,
+ requiredKeys: new string[] { "area", "action", "controller", "page" },
+ parameterPolicies: null);
+
+ // Act
+ var result = binder.GetValues(ambientValues, explicitValues);
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert
+ Assert.Equal(expected, boundTemplate);
+ }
- [Fact]
- public void BindValues_HasUnmatchingAmbientValues_Discard()
- {
- // Arrange
- var expected = "/Admin/LG3/SomeAction?anothervalue=5";
-
- var template = "Admin/LG3/SomeAction/{id?}";
- var defaults = new RouteValueDictionary(new { controller = "LG3", action = "SomeAction", area = "Admin" });
- var ambientValues = new RouteValueDictionary(new { controller = "LG1", action = "LinkToAnArea", id = "17" });
- var explicitValues = new RouteValueDictionary(new { controller = "LG3", area = "Admin", action = "SomeAction", anothervalue = "5" });
- var binder = new TemplateBinder(
- UrlEncoder.Default,
- new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
- RoutePatternFactory.Parse(
- template,
- defaults,
- parameterPolicies: null,
- requiredValues: new { area = "Admin", action = "SomeAction", controller = "LG3", page = (string)null }),
+ [Fact]
+ public void BindValues_LinkingFromPageToAController_Success()
+ {
+ // Arrange
+ var expected = "/LG2/SomeAction";
+
+ var template = "{controller=Home}/{action=Index}/{id?}";
+ var defaults = new RouteValueDictionary();
+ var ambientValues = new RouteValueDictionary(new { page = "/LGAnotherPage", id = "17" });
+ var explicitValues = new RouteValueDictionary(new { controller = "LG2", action = "SomeAction" });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ RoutePatternFactory.Parse(
+ template,
defaults,
- requiredKeys: new string[] { "area", "action", "controller", "page" },
- parameterPolicies: null);
+ parameterPolicies: null,
+ requiredValues: new { area = (string)null, action = "SomeAction", controller = "LG2", page = (string)null }),
+ defaults,
+ requiredKeys: new string[] { "area", "action", "controller", "page" },
+ parameterPolicies: null);
+
+ // Act
+ var result = binder.GetValues(ambientValues, explicitValues);
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert
+ Assert.Equal(expected, boundTemplate);
+ }
- // Act
- var result = binder.GetValues(ambientValues, explicitValues);
- var boundTemplate = binder.BindValues(result.AcceptedValues);
+ // Regression test for dotnet/aspnetcore#4212
+ //
+ // An ambient value should be used to satisfy a required value even if if we're discarding
+ // ambient values.
+ [Fact]
+ public void BindValues_LinkingFromPageToAControllerInAreaWithAmbientArea_Success()
+ {
+ // Arrange
+ var expected = "/Admin/LG2/SomeAction";
+
+ var template = "{area}/{controller=Home}/{action=Index}/{id?}";
+ var defaults = new RouteValueDictionary();
+ var ambientValues = new RouteValueDictionary(new { area = "Admin", page = "/LGAnotherPage", id = "17" });
+ var explicitValues = new RouteValueDictionary(new { controller = "LG2", action = "SomeAction" });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ RoutePatternFactory.Parse(
+ template,
+ defaults,
+ parameterPolicies: null,
+ requiredValues: new { area = "Admin", action = "SomeAction", controller = "LG2", page = (string)null }),
+ defaults,
+ requiredKeys: new string[] { "area", "action", "controller", "page" },
+ parameterPolicies: null);
+
+ // Act
+ var result = binder.GetValues(ambientValues, explicitValues);
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert
+ Assert.Equal(expected, boundTemplate);
+ }
- // Assert
- Assert.Equal(expected, boundTemplate);
- }
+ [Fact]
+ public void BindValues_HasUnmatchingAmbientValues_Discard()
+ {
+ // Arrange
+ var expected = "/Admin/LG3/SomeAction?anothervalue=5";
+
+ var template = "Admin/LG3/SomeAction/{id?}";
+ var defaults = new RouteValueDictionary(new { controller = "LG3", action = "SomeAction", area = "Admin" });
+ var ambientValues = new RouteValueDictionary(new { controller = "LG1", action = "LinkToAnArea", id = "17" });
+ var explicitValues = new RouteValueDictionary(new { controller = "LG3", area = "Admin", action = "SomeAction", anothervalue = "5" });
+ var binder = new TemplateBinder(
+ UrlEncoder.Default,
+ new DefaultObjectPoolProvider().Create(new UriBuilderContextPooledObjectPolicy()),
+ RoutePatternFactory.Parse(
+ template,
+ defaults,
+ parameterPolicies: null,
+ requiredValues: new { area = "Admin", action = "SomeAction", controller = "LG3", page = (string)null }),
+ defaults,
+ requiredKeys: new string[] { "area", "action", "controller", "page" },
+ parameterPolicies: null);
+
+ // Act
+ var result = binder.GetValues(ambientValues, explicitValues);
+ var boundTemplate = binder.BindValues(result.AcceptedValues);
+
+ // Assert
+ Assert.Equal(expected, boundTemplate);
+ }
- private class PathAndQuery
+ private class PathAndQuery
+ {
+ public PathAndQuery(string uri)
{
- public PathAndQuery(string uri)
+ var queryIndex = uri.IndexOf("?", StringComparison.Ordinal);
+ if (queryIndex == -1)
{
- var queryIndex = uri.IndexOf("?", StringComparison.Ordinal);
- if (queryIndex == -1)
- {
- Path = uri;
- }
- else
- {
- Path = uri.Substring(0, queryIndex);
-
- var query = uri.Substring(queryIndex + 1);
- Parameters =
- query
- .Split(new char[] { '&' }, StringSplitOptions.None)
- .Select(s => s.Split(new char[] { '=' }, StringSplitOptions.None))
- .ToDictionary(pair => pair[0], pair => pair[1]);
- }
+ Path = uri;
+ }
+ else
+ {
+ Path = uri.Substring(0, queryIndex);
+
+ var query = uri.Substring(queryIndex + 1);
+ Parameters =
+ query
+ .Split(new char[] { '&' }, StringSplitOptions.None)
+ .Select(s => s.Split(new char[] { '=' }, StringSplitOptions.None))
+ .ToDictionary(pair => pair[0], pair => pair[1]);
}
+ }
- public string Path { get; private set; }
+ public string Path { get; private set; }
- public Dictionary<string, string> Parameters { get; private set; }
- }
+ public Dictionary<string, string> Parameters { get; private set; }
}
}
diff --git a/src/Http/Routing/test/UnitTests/Template/TemplateMatcherTests.cs b/src/Http/Routing/test/UnitTests/Template/TemplateMatcherTests.cs
index 5b6235375e..14ab1dcc7c 100644
--- a/src/Http/Routing/test/UnitTests/Template/TemplateMatcherTests.cs
+++ b/src/Http/Routing/test/UnitTests/Template/TemplateMatcherTests.cs
@@ -7,1125 +7,1124 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Template.Tests
+namespace Microsoft.AspNetCore.Routing.Template.Tests;
+
+public class TemplateMatcherTests
{
- public class TemplateMatcherTests
+ [Fact]
+ public void TryMatch_Success()
{
- [Fact]
- public void TryMatch_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}");
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}");
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank/DoAction/123", values);
+ // Act
+ var match = matcher.TryMatch("/Bank/DoAction/123", values);
- // Assert
- Assert.True(match);
- Assert.Equal("Bank", values["controller"]);
- Assert.Equal("DoAction", values["action"]);
- Assert.Equal("123", values["id"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("Bank", values["controller"]);
+ Assert.Equal("DoAction", values["action"]);
+ Assert.Equal("123", values["id"]);
+ }
- [Fact]
- public void TryMatch_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}");
+ [Fact]
+ public void TryMatch_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}");
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank/DoAction", values);
+ // Act
+ var match = matcher.TryMatch("/Bank/DoAction", values);
- // Assert
- Assert.False(match);
- }
+ // Assert
+ Assert.False(match);
+ }
- [Fact]
- public void TryMatch_WithDefaults_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
+ [Fact]
+ public void TryMatch_WithDefaults_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank/DoAction", values);
+ // Act
+ var match = matcher.TryMatch("/Bank/DoAction", values);
- // Assert
- Assert.True(match);
- Assert.Equal("Bank", values["controller"]);
- Assert.Equal("DoAction", values["action"]);
- Assert.Equal("default id", values["id"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("Bank", values["controller"]);
+ Assert.Equal("DoAction", values["action"]);
+ Assert.Equal("default id", values["id"]);
+ }
- [Fact]
- public void TryMatch_WithDefaults_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
+ [Fact]
+ public void TryMatch_WithDefaults_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{controller}/{action}/{id}", new { id = "default id" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/Bank", values);
+ // Act
+ var match = matcher.TryMatch("/Bank", values);
- // Assert
- Assert.False(match);
- }
+ // Assert
+ Assert.False(match);
+ }
- [Fact]
- public void TryMatch_WithLiterals_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
+ [Fact]
+ public void TryMatch_WithLiterals_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/moo/111/bar/222", values);
+ // Act
+ var match = matcher.TryMatch("/moo/111/bar/222", values);
- // Assert
- Assert.True(match);
- Assert.Equal("111", values["p1"]);
- Assert.Equal("222", values["p2"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("111", values["p1"]);
+ Assert.Equal("222", values["p2"]);
+ }
- [Fact]
- public void TryMatch_RouteWithLiteralsAndDefaults_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
+ [Fact]
+ public void TryMatch_RouteWithLiteralsAndDefaults_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/{p1}/bar/{p2}", new { p2 = "default p2" });
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch("/moo/111/bar/", values);
+ // Act
+ var match = matcher.TryMatch("/moo/111/bar/", values);
- // Assert
- Assert.True(match);
- Assert.Equal("111", values["p1"]);
- Assert.Equal("default p2", values["p2"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal("111", values["p1"]);
+ Assert.Equal("default p2", values["p2"]);
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "/123-456-7890")] // ssn
- [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "/asd@assds.com")] // email
- [InlineData(@"{p1:regex(([}}])\w+)}", "/}sda")] // Not balanced }
- [InlineData(@"{p1:regex(([{{)])\w+)}", "/})sda")] // Not balanced {
- public void TryMatch_RegularExpressionConstraint_Valid(
- string template,
- string path)
- {
- // Arrange
- var matcher = CreateMatcher(template);
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", "/123-456-7890")] // ssn
+ [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", "/asd@assds.com")] // email
+ [InlineData(@"{p1:regex(([}}])\w+)}", "/}sda")] // Not balanced }
+ [InlineData(@"{p1:regex(([{{)])\w+)}", "/})sda")] // Not balanced {
+ public void TryMatch_RegularExpressionConstraint_Valid(
+ string template,
+ string path)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch(path, values);
+ // Act
+ var match = matcher.TryMatch(path, values);
- // Assert
- Assert.True(match);
- }
+ // Assert
+ Assert.True(match);
+ }
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", true, "foo", "bar")]
- [InlineData("moo/{p1?}", "/moo/foo", true, "foo", null)]
- [InlineData("moo/{p1?}", "/moo", true, null, null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo", true, "foo", null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", true, "foo.", "bar")]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", true, "foo.moo", "bar")]
- [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", true, "foo", "bar")]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", true, "moo", "bar")]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", true, "moo", null)]
- [InlineData("moo/.{p2?}", "/moo/.foo", true, null, "foo")]
- [InlineData("moo/.{p2?}", "/moo", false, null, null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/....", true, "..", ".")]
- [InlineData("moo/{p1}.{p2?}", "/moo/.bar", true, ".bar", null)]
- public void TryMatch_OptionalParameter_FollowedByPeriod_Valid(
- string template,
- string path,
- bool expectedMatch,
- string p1,
- string p2)
- {
- // Arrange
- var matcher = CreateMatcher(template);
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", true, "foo", "bar")]
+ [InlineData("moo/{p1?}", "/moo/foo", true, "foo", null)]
+ [InlineData("moo/{p1?}", "/moo", true, null, null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo", true, "foo", null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", true, "foo.", "bar")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", true, "foo.moo", "bar")]
+ [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", true, "foo", "bar")]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", true, "moo", "bar")]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", true, "moo", null)]
+ [InlineData("moo/.{p2?}", "/moo/.foo", true, null, "foo")]
+ [InlineData("moo/.{p2?}", "/moo", false, null, null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/....", true, "..", ".")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.bar", true, ".bar", null)]
+ public void TryMatch_OptionalParameter_FollowedByPeriod_Valid(
+ string template,
+ string path,
+ bool expectedMatch,
+ string p1,
+ string p2)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- var values = new RouteValueDictionary();
+ var values = new RouteValueDictionary();
- // Act
- var match = matcher.TryMatch(path, values);
+ // Act
+ var match = matcher.TryMatch(path, values);
- // Assert
- Assert.Equal(expectedMatch, match);
- if (p1 != null)
- {
- Assert.Equal(p1, values["p1"]);
- }
- if (p2 != null)
- {
- Assert.Equal(p2, values["p2"]);
- }
+ // Assert
+ Assert.Equal(expectedMatch, match);
+ if (p1 != null)
+ {
+ Assert.Equal(p1, values["p1"]);
}
-
- [Theory]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
- [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
- [InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", "foo", "bar", "baz")]
- public void TryMatch_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
- string template,
- string path,
- string p1,
- string p2,
- string p3)
+ if (p2 != null)
{
- // Arrange
- var matcher = CreateMatcher(template);
+ Assert.Equal(p2, values["p2"]);
+ }
+ }
- var values = new RouteValueDictionary();
+ [Theory]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
+ [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
+ [InlineData("{p1}/{p2}/{p3?}", "/foo/bar/baz", "foo", "bar", "baz")]
+ public void TryMatch_OptionalParameter_FollowedByPeriod_3Parameters_Valid(
+ string template,
+ string path,
+ string p1,
+ string p2,
+ string p3)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- // Act
- var match = matcher.TryMatch(path, values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Equal(p1, values["p1"]);
+ // Act
+ var match = matcher.TryMatch(path, values);
- if (p2 != null)
- {
- Assert.Equal(p2, values["p2"]);
- }
+ // Assert
+ Assert.True(match);
+ Assert.Equal(p1, values["p1"]);
- if (p3 != null)
- {
- Assert.Equal(p3, values["p3"]);
- }
+ if (p2 != null)
+ {
+ Assert.Equal(p2, values["p2"]);
}
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
- [InlineData("moo/{p1}.{p2?}", "/moo/.")]
- [InlineData("moo/{p1}.{p2}", "/foo.")]
- [InlineData("moo/{p1}.{p2}", "/foo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
- [InlineData("moo/.{p2?}", "/moo/.")]
- [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
- public void TryMatch_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
+ if (p3 != null)
{
- // Arrange
- var matcher = CreateMatcher(template);
+ Assert.Equal(p3, values["p3"]);
+ }
+ }
- var values = new RouteValueDictionary();
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.")]
+ [InlineData("moo/{p1}.{p2}", "/foo.")]
+ [InlineData("moo/{p1}.{p2}", "/foo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
+ [InlineData("moo/.{p2?}", "/moo/.")]
+ [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
+ public void TryMatch_OptionalParameter_FollowedByPeriod_Invalid(string template, string path)
+ {
+ // Arrange
+ var matcher = CreateMatcher(template);
- // Act
- var match = matcher.TryMatch(path, values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch(path, values);
- [Fact]
- public void TryMatch_RouteWithOnlyLiterals_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bar");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithOnlyLiterals_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bar");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithOnlyLiterals_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bars");
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithOnlyLiterals_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bars");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithExtraSeparators_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bar");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithExtraSeparators_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bar");
- // Act
- var match = matcher.TryMatch("/moo/bar/", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar/", values);
- [Fact]
- public void TryMatch_UrlWithExtraSeparators_Success()
- {
- // Arrange
- var matcher = CreateMatcher("moo/bar/");
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_UrlWithExtraSeparators_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("moo/bar/");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithParametersAndExtraSeparators_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{p2}/");
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithParametersAndExtraSeparators_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{p2}/");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Equal("moo", values["p1"]);
- Assert.Equal("bar", values["p2"]);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_RouteWithDifferentLiterals_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{p2}/baz");
+ // Assert
+ Assert.True(match);
+ Assert.Equal("moo", values["p1"]);
+ Assert.Equal("bar", values["p2"]);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithDifferentLiterals_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{p2}/baz");
- // Act
- var match = matcher.TryMatch("/moo/bar/boo", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar/boo", values);
- [Fact]
- public void TryMatch_LongerUrl_Fails()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_LongerUrl_Fails()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}");
- // Act
- var match = matcher.TryMatch("/moo/bar", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.False(match);
- }
+ // Act
+ var match = matcher.TryMatch("/moo/bar", values);
- [Fact]
- public void TryMatch_SimpleFilename_Success()
- {
- // Arrange
- var matcher = CreateMatcher("DEFAULT.ASPX");
+ // Assert
+ Assert.False(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_SimpleFilename_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("DEFAULT.ASPX");
- // Act
- var match = matcher.TryMatch("/default.aspx", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- }
+ // Act
+ var match = matcher.TryMatch("/default.aspx", values);
- [Theory]
- [InlineData("{prefix}x{suffix}", "/xxxxxxxxxx")]
- [InlineData("{prefix}xyz{suffix}", "/xxxxyzxyzxxxxxxyz")]
- [InlineData("{prefix}xyz{suffix}", "/abcxxxxyzxyzxxxxxxyzxx")]
- [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz")]
- [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz1")]
- [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyz")]
- [InlineData("{prefix}aa{suffix}", "/aaaaa")]
- [InlineData("{prefix}aaa{suffix}", "/aaaaa")]
- public void TryMatch_RouteWithComplexSegment_Success(string template, string path)
- {
- var matcher = CreateMatcher(template);
+ // Assert
+ Assert.True(match);
+ }
- var values = new RouteValueDictionary();
+ [Theory]
+ [InlineData("{prefix}x{suffix}", "/xxxxxxxxxx")]
+ [InlineData("{prefix}xyz{suffix}", "/xxxxyzxyzxxxxxxyz")]
+ [InlineData("{prefix}xyz{suffix}", "/abcxxxxyzxyzxxxxxxyzxx")]
+ [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz")]
+ [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyzxyzxyz1")]
+ [InlineData("{prefix}xyz{suffix}", "/xyzxyzxyz")]
+ [InlineData("{prefix}aa{suffix}", "/aaaaa")]
+ [InlineData("{prefix}aaa{suffix}", "/aaaaa")]
+ public void TryMatch_RouteWithComplexSegment_Success(string template, string path)
+ {
+ var matcher = CreateMatcher(template);
- // Act
- var match = matcher.TryMatch(path, values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- }
+ // Act
+ var match = matcher.TryMatch(path, values);
- [Fact]
- public void TryMatch_RouteWithExtraDefaultValues_Success()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" });
+ // Assert
+ Assert.True(match);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_RouteWithExtraDefaultValues_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{p2}", new { p2 = (string)null, foo = "bar" });
- // Act
- var match = matcher.TryMatch("/v1", values);
+ var values = new RouteValueDictionary();
- // Assert
- Assert.True(match);
- Assert.Equal<int>(3, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Null(values["p2"]);
- Assert.Equal("bar", values["foo"]);
- }
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- [Fact]
- public void TryMatch_PrettyRouteWithExtraDefaultValues_Success()
- {
- // Arrange
- var matcher = CreateMatcher(
- "date/{y}/{m}/{d}",
- new { controller = "blog", action = "showpost", m = (string)null, d = (string)null });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(3, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Null(values["p2"]);
+ Assert.Equal("bar", values["foo"]);
+ }
- var values = new RouteValueDictionary();
+ [Fact]
+ public void TryMatch_PrettyRouteWithExtraDefaultValues_Success()
+ {
+ // Arrange
+ var matcher = CreateMatcher(
+ "date/{y}/{m}/{d}",
+ new { controller = "blog", action = "showpost", m = (string)null, d = (string)null });
+
+ var values = new RouteValueDictionary();
+
+ // Act
+ var match = matcher.TryMatch("/date/2007/08", values);
+
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(5, values.Count);
+ Assert.Equal("blog", values["controller"]);
+ Assert.Equal("showpost", values["action"]);
+ Assert.Equal("2007", values["y"]);
+ Assert.Equal("08", values["m"]);
+ Assert.Null(values["d"]);
+ }
- // Act
- var match = matcher.TryMatch("/date/2007/08", values);
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnBothEndsMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}",
+ "/language/en-US",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- // Assert
- Assert.True(match);
- Assert.Equal<int>(5, values.Count);
- Assert.Equal("blog", values["controller"]);
- Assert.Equal("showpost", values["action"]);
- Assert.Equal("2007", values["y"]);
- Assert.Equal("08", values["m"]);
- Assert.Null(values["d"]);
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnLeftEndMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}a",
+ "/language/en-USa",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnBothEndsMatches()
- {
- RunTest(
- "language/{lang}-{region}",
- "/language/en-US",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnRightEndMatches()
+ {
+ RunTest(
+ "language/a{lang}-{region}",
+ "/language/aen-US",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnLeftEndMatches()
- {
- RunTest(
- "language/{lang}-{region}a",
- "/language/en-USa",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnNeitherEndMatches()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ "/language/aen-USa",
+ null,
+ new RouteValueDictionary(new { lang = "en", region = "US" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnRightEndMatches()
- {
- RunTest(
- "language/a{lang}-{region}",
- "/language/aen-US",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ "/language/a-USa",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnNeitherEndMatches()
- {
- RunTest(
- "language/a{lang}-{region}a",
- "/language/aen-USa",
- null,
- new RouteValueDictionary(new { lang = "en", region = "US" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
+ {
+ RunTest(
+ "language/a{lang}-{region}a",
+ "/language/aen-a",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch()
- {
- RunTest(
- "language/a{lang}-{region}a",
- "/language/a-USa",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsMatches()
+ {
+ RunTest(
+ "language/{lang}",
+ "/language/en",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
- {
- RunTest(
- "language/a{lang}-{region}a",
- "/language/aen-a",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsTrailingSlashDoesNotMatch()
+ {
+ RunTest(
+ "language/{lang}",
+ "/language/",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsMatches()
- {
- RunTest(
- "language/{lang}",
- "/language/en",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsDoesNotMatch()
+ {
+ RunTest(
+ "language/{lang}",
+ "/language",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsTrailingSlashDoesNotMatch()
- {
- RunTest(
- "language/{lang}",
- "/language/",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnLeftEndMatches()
+ {
+ RunTest(
+ "language/{lang}-",
+ "/language/en-",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnBothEndsDoesNotMatch()
- {
- RunTest(
- "language/{lang}",
- "/language",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnRightEndMatches()
+ {
+ RunTest(
+ "language/a{lang}",
+ "/language/aen",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnLeftEndMatches()
- {
- RunTest(
- "language/{lang}-",
- "/language/en-",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithSimpleMultiSegmentParamsOnNeitherEndMatches()
+ {
+ RunTest(
+ "language/a{lang}a",
+ "/language/aena",
+ null,
+ new RouteValueDictionary(new { lang = "en" }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnRightEndMatches()
- {
- RunTest(
- "language/a{lang}",
- "/language/aen",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentStandamatchMvcRouteMatches()
+ {
+ RunTest(
+ "{controller}.mvc/{action}/{id}",
+ "/home.mvc/index",
+ new RouteValueDictionary(new { action = "Index", id = (string)null }),
+ new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null }));
+ }
- [Fact]
- public void TryMatch_WithSimpleMultiSegmentParamsOnNeitherEndMatches()
- {
- RunTest(
- "language/a{lang}a",
- "/language/aena",
- null,
- new RouteValueDictionary(new { lang = "en" }));
- }
+ [Fact]
+ public void TryMatch_WithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
+ {
+ RunTest(
+ "language/{lang}-{region}",
+ "/language/-",
+ new RouteValueDictionary(new { lang = "xx", region = "yy" }),
+ null);
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentStandamatchMvcRouteMatches()
- {
- RunTest(
- "{controller}.mvc/{action}/{id}",
- "/home.mvc/index",
- new RouteValueDictionary(new { action = "Index", id = (string)null }),
- new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithMultiSegmentWithRepeatedDots()
+ {
+ RunTest(
+ "{Controller}..mvc/{id}/{Param1}",
+ "/Home..mvc/123/p1",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
+ }
- [Fact]
- public void TryMatch_WithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
- {
- RunTest(
- "language/{lang}-{region}",
- "/language/-",
- new RouteValueDictionary(new { lang = "xx", region = "yy" }),
- null);
- }
+ [Fact]
+ public void TryMatch_WithUrlWithTwoRepeatedDots()
+ {
+ RunTest(
+ "{Controller}.mvc/../{action}",
+ "/Home.mvc/../index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithMultiSegmentWithRepeatedDots()
- {
- RunTest(
- "{Controller}..mvc/{id}/{Param1}",
- "/Home..mvc/123/p1",
- null,
- new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithThreeRepeatedDots()
+ {
+ RunTest(
+ "{Controller}.mvc/.../{action}",
+ "/Home.mvc/.../index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithTwoRepeatedDots()
- {
- RunTest(
- "{Controller}.mvc/../{action}",
- "/Home.mvc/../index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithManyRepeatedDots()
+ {
+ RunTest(
+ "{Controller}.mvc/../../../{action}",
+ "/Home.mvc/../../../index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithThreeRepeatedDots()
- {
- RunTest(
- "{Controller}.mvc/.../{action}",
- "/Home.mvc/.../index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithExclamationPoint()
+ {
+ RunTest(
+ "{Controller}.mvc!/{action}",
+ "/Home.mvc!/index",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", action = "index" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithManyRepeatedDots()
- {
- RunTest(
- "{Controller}.mvc/../../../{action}",
- "/Home.mvc/../../../index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithStartingDotDotSlash()
+ {
+ RunTest(
+ "../{Controller}.mvc",
+ "/../Home.mvc",
+ null,
+ new RouteValueDictionary(new { Controller = "Home" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithExclamationPoint()
- {
- RunTest(
- "{Controller}.mvc!/{action}",
- "/Home.mvc!/index",
- null,
- new RouteValueDictionary(new { Controller = "Home", action = "index" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithStartingBackslash()
+ {
+ RunTest(
+ @"\{Controller}.mvc",
+ @"/\Home.mvc",
+ null,
+ new RouteValueDictionary(new { Controller = "Home" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithStartingDotDotSlash()
- {
- RunTest(
- "../{Controller}.mvc",
- "/../Home.mvc",
- null,
- new RouteValueDictionary(new { Controller = "Home" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithBackslashSeparators()
+ {
+ RunTest(
+ @"{Controller}.mvc\{id}\{Param1}",
+ @"/Home.mvc\123\p1",
+ null,
+ new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithStartingBackslash()
- {
- RunTest(
- @"\{Controller}.mvc",
- @"/\Home.mvc",
- null,
- new RouteValueDictionary(new { Controller = "Home" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithParenthesesLiterals()
+ {
+ RunTest(
+ @"(Controller).mvc",
+ @"/(Controller).mvc",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_WithUrlWithBackslashSeparators()
- {
- RunTest(
- @"{Controller}.mvc\{id}\{Param1}",
- @"/Home.mvc\123\p1",
- null,
- new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
- }
+ [Fact]
+ public void TryMatch_WithUrlWithTrailingSlashSpace()
+ {
+ RunTest(
+ @"Controller.mvc/ ",
+ @"/Controller.mvc/ ",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_WithUrlWithParenthesesLiterals()
- {
- RunTest(
- @"(Controller).mvc",
- @"/(Controller).mvc",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_WithUrlWithTrailingSpace()
+ {
+ RunTest(
+ @"Controller.mvc ",
+ @"/Controller.mvc ",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_WithUrlWithTrailingSlashSpace()
- {
- RunTest(
- @"Controller.mvc/ ",
- @"/Controller.mvc/ ",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_WithCatchAllCapturesDots()
+ {
+ // DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "."
+ RunTest(
+ "Home/ShowPilot/{missionId}/{*name}",
+ "/Home/ShowPilot/777/12345./foobar",
+ new RouteValueDictionary(new
+ {
+ controller = "Home",
+ action = "ShowPilot",
+ missionId = (string)null,
+ name = (string)null
+ }),
+ new RouteValueDictionary(new { controller = "Home", action = "ShowPilot", missionId = "777", name = "12345./foobar" }));
+ }
- [Fact]
- public void TryMatch_WithUrlWithTrailingSpace()
- {
- RunTest(
- @"Controller.mvc ",
- @"/Controller.mvc ",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesMultiplePathSegments()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- [Fact]
- public void TryMatch_WithCatchAllCapturesDots()
- {
- // DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "."
- RunTest(
- "Home/ShowPilot/{missionId}/{*name}",
- "/Home/ShowPilot/777/12345./foobar",
- new RouteValueDictionary(new
- {
- controller = "Home",
- action = "ShowPilot",
- missionId = (string)null,
- name = (string)null
- }),
- new RouteValueDictionary(new { controller = "Home", action = "ShowPilot", missionId = "777", name = "12345./foobar" }));
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesMultiplePathSegments()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1/v2/v3", values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("v2/v3", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1/v2/v3", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesTrailingSlash()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("v2/v3", values["p2"]);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesTrailingSlash()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1/", values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Null(values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1/", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesEmptyContent()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Null(values["p2"]);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesEmptyContent()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Null(values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_MatchesEmptyContent_DoesNotReplaceExistingRouteValue()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}");
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Null(values["p2"]);
- }
+ var values = new RouteValueDictionary(new { p2 = "hello" });
- [Fact]
- public void TryMatch_RouteWithCatchAll_MatchesEmptyContent_DoesNotReplaceExistingRouteValue()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}");
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- var values = new RouteValueDictionary(new { p2 = "hello" });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("hello", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_UsesDefaultValueForEmptyContent()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("hello", values["p2"]);
- }
+ var values = new RouteValueDictionary(new { p2 = "overridden" });
- [Fact]
- public void TryMatch_RouteWithCatchAll_UsesDefaultValueForEmptyContent()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
+ // Act
+ var match = matcher.TryMatch("/v1", values);
- var values = new RouteValueDictionary(new { p2 = "overridden" });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("catchall", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1", values);
+ [Fact]
+ public void TryMatch_RouteWithCatchAll_IgnoresDefaultValueForNonEmptyContent()
+ {
+ // Arrange
+ var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("catchall", values["p2"]);
- }
+ var values = new RouteValueDictionary(new { p2 = "overridden" });
- [Fact]
- public void TryMatch_RouteWithCatchAll_IgnoresDefaultValueForNonEmptyContent()
- {
- // Arrange
- var matcher = CreateMatcher("{p1}/{*p2}", new { p2 = "catchall" });
+ // Act
+ var match = matcher.TryMatch("/v1/hello/whatever", values);
- var values = new RouteValueDictionary(new { p2 = "overridden" });
+ // Assert
+ Assert.True(match);
+ Assert.Equal<int>(2, values.Count);
+ Assert.Equal("v1", values["p1"]);
+ Assert.Equal("hello/whatever", values["p2"]);
+ }
- // Act
- var match = matcher.TryMatch("/v1/hello/whatever", values);
+ [Fact]
+ public void TryMatch_DoesNotMatchOnlyLeftLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/fooBAR",
+ null,
+ null);
+ }
- // Assert
- Assert.True(match);
- Assert.Equal<int>(2, values.Count);
- Assert.Equal("v1", values["p1"]);
- Assert.Equal("hello/whatever", values["p2"]);
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchOnlyRightLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/BARfoo",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchOnlyLeftLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/fooBAR",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchMiddleLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/BARfooBAR",
+ null,
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchOnlyRightLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/BARfoo",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_DoesMatchesExactLiteralMatch()
+ {
+ // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
+ RunTest(
+ "foo",
+ "/foo",
+ null,
+ new RouteValueDictionary());
+ }
- [Fact]
- public void TryMatch_DoesNotMatchMiddleLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/BARfooBAR",
- null,
- null);
- }
+ [Fact]
+ public void TryMatch_WithWeimatchParameterNames()
+ {
+ RunTest(
+ "foo/{ }/{.!$%}/{dynamic.data}/{op.tional}",
+ "/foo/space/weimatch/omatcherid",
+ new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } },
+ new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weimatch" }, { "dynamic.data", "omatcherid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } });
+ }
- [Fact]
- public void TryMatch_DoesMatchesExactLiteralMatch()
- {
- // DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
- RunTest(
- "foo",
- "/foo",
- null,
- new RouteValueDictionary());
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchRouteWithLiteralSeparatomatchefaultsButNoValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ null);
+ }
- [Fact]
- public void TryMatch_WithWeimatchParameterNames()
- {
- RunTest(
- "foo/{ }/{.!$%}/{dynamic.data}/{op.tional}",
- "/foo/space/weimatch/omatcherid",
- new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } },
- new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weimatch" }, { "dynamic.data", "omatcherid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } });
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndLeftValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo/xx-",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchRouteWithLiteralSeparatomatchefaultsButNoValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- null);
- }
+ [Fact]
+ public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndRightValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo/-yy",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ null);
+ }
- [Fact]
- public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndLeftValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo/xx-",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- null);
- }
+ [Fact]
+ public void TryMatch_MatchesRouteWithLiteralSeparatomatchefaultsAndValue()
+ {
+ RunTest(
+ "{controller}/{language}-{locale}",
+ "/foo/xx-yy",
+ new RouteValueDictionary(new { language = "en", locale = "US" }),
+ new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
+ }
- [Fact]
- public void TryMatch_DoesNotMatchesRouteWithLiteralSeparatomatchefaultsAndRightValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo/-yy",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- null);
- }
+ [Fact]
+ public void TryMatch_SetsOptionalParameter()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action?}");
+ var url = "/Home/Index";
- [Fact]
- public void TryMatch_MatchesRouteWithLiteralSeparatomatchefaultsAndValue()
- {
- RunTest(
- "{controller}/{language}-{locale}",
- "/foo/xx-yy",
- new RouteValueDictionary(new { language = "en", locale = "US" }),
- new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_SetsOptionalParameter()
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action?}");
- var url = "/Home/Index";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal(2, values.Count);
+ Assert.Equal("Home", values["controller"]);
+ Assert.Equal("Index", values["action"]);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch_DoesNotSetOptionalParameter()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action?}");
+ var url = "/Home";
- // Assert
- Assert.True(match);
- Assert.Equal(2, values.Count);
- Assert.Equal("Home", values["controller"]);
- Assert.Equal("Index", values["action"]);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_DoesNotSetOptionalParameter()
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action?}");
- var url = "/Home";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Single(values);
+ Assert.Equal("Home", values["controller"]);
+ Assert.False(values.ContainsKey("action"));
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch_DoesNotSetOptionalParameter_EmptyString()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller?}");
+ var url = "";
- // Assert
- Assert.True(match);
- Assert.Single(values);
- Assert.Equal("Home", values["controller"]);
- Assert.False(values.ContainsKey("action"));
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_DoesNotSetOptionalParameter_EmptyString()
- {
- // Arrange
- var route = CreateMatcher("{controller?}");
- var url = "";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ Assert.False(values.ContainsKey("controller"));
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch__EmptyRouteWith_EmptyString()
+ {
+ // Arrange
+ var route = CreateMatcher("");
+ var url = "";
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- Assert.False(values.ContainsKey("controller"));
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch__EmptyRouteWith_EmptyString()
- {
- // Arrange
- var route = CreateMatcher("");
- var url = "";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Empty(values);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Fact]
+ public void TryMatch_MultipleOptionalParameters()
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action?}/{id?}");
+ var url = "/Home/Index";
- // Assert
- Assert.True(match);
- Assert.Empty(values);
- }
+ var values = new RouteValueDictionary();
- [Fact]
- public void TryMatch_MultipleOptionalParameters()
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action?}/{id?}");
- var url = "/Home/Index";
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ Assert.Equal(2, values.Count);
+ Assert.Equal("Home", values["controller"]);
+ Assert.Equal("Index", values["action"]);
+ Assert.False(values.ContainsKey("id"));
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("///")]
+ [InlineData("/a//")]
+ [InlineData("/a/b//")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public void TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller?}/{action?}/{id?}");
- // Assert
- Assert.True(match);
- Assert.Equal(2, values.Count);
- Assert.Equal("Home", values["controller"]);
- Assert.Equal("Index", values["action"]);
- Assert.False(values.ContainsKey("id"));
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("///")]
- [InlineData("/a//")]
- [InlineData("/a/b//")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public void TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller?}/{action?}/{id?}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.False(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("")]
+ [InlineData("/")]
+ [InlineData("/a")]
+ [InlineData("/a/")]
+ [InlineData("/a/b")]
+ [InlineData("/a/b/")]
+ [InlineData("/a/b/c")]
+ [InlineData("/a/b/c/")]
+ public void TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller?}/{action?}/{id?}");
- // Assert
- Assert.False(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("")]
- [InlineData("/")]
- [InlineData("/a")]
- [InlineData("/a/")]
- [InlineData("/a/b")]
- [InlineData("/a/b/")]
- [InlineData("/a/b/c")]
- [InlineData("/a/b/c/")]
- public void TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller?}/{action?}/{id?}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("///")]
+ [InlineData("////")]
+ [InlineData("/a//")]
+ [InlineData("/a///")]
+ [InlineData("//b/")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public void TryMatch_MultipleParameters_WithEmptyValues(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action}/{id}");
- // Assert
- Assert.True(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("///")]
- [InlineData("////")]
- [InlineData("/a//")]
- [InlineData("/a///")]
- [InlineData("//b/")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public void TryMatch_MultipleParameters_WithEmptyValues(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action}/{id}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.False(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("/a/b/c//")]
+ [InlineData("/a/b/c/////")]
+ public void TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action}/{*id}");
- // Assert
- Assert.False(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("/a/b/c//")]
- [InlineData("/a/b/c/////")]
- public void TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action}/{*id}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.True(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ [Theory]
+ [InlineData("/a/b//")]
+ [InlineData("/a/b///c")]
+ public void TryMatch_CatchAllParameters_WithEmptyValues(string url)
+ {
+ // Arrange
+ var route = CreateMatcher("{controller}/{action}/{*id}");
- // Assert
- Assert.True(match);
- }
+ var values = new RouteValueDictionary();
- [Theory]
- [InlineData("/a/b//")]
- [InlineData("/a/b///c")]
- public void TryMatch_CatchAllParameters_WithEmptyValues(string url)
- {
- // Arrange
- var route = CreateMatcher("{controller}/{action}/{*id}");
+ // Act
+ var match = route.TryMatch(url, values);
- var values = new RouteValueDictionary();
+ // Assert
+ Assert.False(match);
+ }
- // Act
- var match = route.TryMatch(url, values);
+ private TemplateMatcher CreateMatcher(string template, object defaults = null)
+ {
+ return new TemplateMatcher(
+ TemplateParser.Parse(template),
+ new RouteValueDictionary(defaults));
+ }
- // Assert
- Assert.False(match);
- }
+ private static void RunTest(
+ string template,
+ string path,
+ RouteValueDictionary defaults,
+ IDictionary<string, object> expected)
+ {
+ // Arrange
+ var matcher = new TemplateMatcher(
+ TemplateParser.Parse(template),
+ defaults ?? new RouteValueDictionary());
- private TemplateMatcher CreateMatcher(string template, object defaults = null)
+ var values = new RouteValueDictionary();
+
+ // Act
+ var match = matcher.TryMatch(new PathString(path), values);
+
+ // Assert
+ if (expected == null)
{
- return new TemplateMatcher(
- TemplateParser.Parse(template),
- new RouteValueDictionary(defaults));
+ Assert.False(match);
}
-
- private static void RunTest(
- string template,
- string path,
- RouteValueDictionary defaults,
- IDictionary<string, object> expected)
+ else
{
- // Arrange
- var matcher = new TemplateMatcher(
- TemplateParser.Parse(template),
- defaults ?? new RouteValueDictionary());
-
- var values = new RouteValueDictionary();
-
- // Act
- var match = matcher.TryMatch(new PathString(path), values);
-
- // Assert
- if (expected == null)
- {
- Assert.False(match);
- }
- else
+ Assert.True(match);
+ Assert.Equal(expected.Count, values.Count);
+ foreach (string key in values.Keys)
{
- Assert.True(match);
- Assert.Equal(expected.Count, values.Count);
- foreach (string key in values.Keys)
- {
- Assert.Equal(expected[key], values[key]);
- }
+ Assert.Equal(expected[key], values[key]);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Template/TemplateParserTests.cs b/src/Http/Routing/test/UnitTests/Template/TemplateParserTests.cs
index 1fa3c16501..b10f084e0a 100644
--- a/src/Http/Routing/test/UnitTests/Template/TemplateParserTests.cs
+++ b/src/Http/Routing/test/UnitTests/Template/TemplateParserTests.cs
@@ -7,896 +7,859 @@ using System.Linq;
using Microsoft.AspNetCore.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Template.Tests
+namespace Microsoft.AspNetCore.Routing.Template.Tests;
+
+public class TemplateRouteParserTests
{
- public class TemplateRouteParserTests
+ [Fact]
+ public void Parse_SingleLiteral()
{
- [Fact]
- public void Parse_SingleLiteral()
- {
- // Arrange
- var template = "cool";
+ // Arrange
+ var template = "cool";
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
- // Act
- var actual = TemplateParser.Parse(template);
+ // Act
+ var actual = TemplateParser.Parse(template);
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_SingleParameter()
- {
- // Arrange
- var template = "{p}";
+ [Fact]
+ public void Parse_SingleParameter()
+ {
+ // Arrange
+ var template = "{p}";
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(
- TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(
+ TemplatePart.CreateParameter("p", false, false, defaultValue: null, inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
- // Act
- var actual = TemplateParser.Parse(template);
+ // Act
+ var actual = TemplateParser.Parse(template);
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_OptionalParameter()
- {
- // Arrange
- var template = "{p?}";
+ [Fact]
+ public void Parse_OptionalParameter()
+ {
+ // Arrange
+ var template = "{p?}";
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(
- TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(
+ TemplatePart.CreateParameter("p", false, true, defaultValue: null, inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
- // Act
- var actual = TemplateParser.Parse(template);
+ // Act
+ var actual = TemplateParser.Parse(template);
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_MultipleLiterals()
- {
- // Arrange
- var template = "cool/awesome/super";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("awesome"));
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[2].Parts.Add(TemplatePart.CreateLiteral("super"));
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_MultipleLiterals()
+ {
+ // Arrange
+ var template = "cool/awesome/super";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("awesome"));
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[2].Parts.Add(TemplatePart.CreateLiteral("super"));
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_MultipleParameters()
- {
- // Arrange
- var template = "{p1}/{p2}/{*p3}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
-
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
-
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[1].Parts[0]);
-
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3",
- true,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[2].Parts[0]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_MultipleParameters()
+ {
+ // Arrange
+ var template = "{p1}/{p2}/{*p3}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[1].Parts[0]);
+
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3",
+ true,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[2].Parts[0]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_LP()
- {
- // Arrange
- var template = "cool-{p1}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[1]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_LP()
+ {
+ // Arrange
+ var template = "cool-{p1}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[1]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_PL()
- {
- // Arrange
- var template = "{p1}-cool";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_PL()
+ {
+ // Arrange
+ var template = "{p1}-cool";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_PLP()
- {
- // Arrange
- var template = "{p1}-cool-{p2}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[2]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_PLP()
+ {
+ // Arrange
+ var template = "{p1}-cool-{p2}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[2]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_LPL()
- {
- // Arrange
- var template = "cool-{p1}-awesome";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Parameters.Add(expected.Segments[0].Parts[1]);
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_LPL()
+ {
+ // Arrange
+ var template = "cool-{p1}-awesome";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Parameters.Add(expected.Segments[0].Parts[1]);
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
- {
- // Arrange
- var template = "{p1}.{p2?}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- true,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[0].Parts[2]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod()
+ {
+ // Arrange
+ var template = "{p1}.{p2?}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ true,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[0].Parts[2]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_ParametersFollowingPeriod()
- {
- // Arrange
- var template = "{p1}.{p2}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[0].Parts[2]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_ParametersFollowingPeriod()
+ {
+ // Arrange
+ var template = "{p1}.{p2}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[0].Parts[2]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
- {
- // Arrange
- var template = "{p1}.{p2}.{p3?}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
- false,
- true,
- defaultValue: null,
- inlineConstraints: null));
-
-
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[0].Parts[2]);
- expected.Parameters.Add(expected.Segments[0].Parts[4]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_ThreeParameters()
+ {
+ // Arrange
+ var template = "{p1}.{p2}.{p3?}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
+ false,
+ true,
+ defaultValue: null,
+ inlineConstraints: null));
+
+
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[0].Parts[2]);
+ expected.Parameters.Add(expected.Segments[0].Parts[4]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_ThreeParametersSeparatedByPeriod()
- {
- // Arrange
- var template = "{p1}.{p2}.{p3}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
-
-
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[0].Parts[2]);
- expected.Parameters.Add(expected.Segments[0].Parts[4]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_ThreeParametersSeparatedByPeriod()
+ {
+ // Arrange
+ var template = "{p1}.{p2}.{p3}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p3",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+
+
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[0].Parts[2]);
+ expected.Parameters.Add(expected.Segments[0].Parts[4]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
- {
- // Arrange
- var template = "{p1}.{p2?}/{p3}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- true,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[0].Parts[2]);
-
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
- false,
- false,
- null,
- null));
- expected.Parameters.Add(expected.Segments[1].Parts[0]);
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_MiddleSegment()
+ {
+ // Arrange
+ var template = "{p1}.{p2?}/{p3}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ true,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[0].Parts[2]);
+
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
+ false,
+ false,
+ null,
+ null));
+ expected.Parameters.Add(expected.Segments[1].Parts[0]);
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
- {
- // Arrange
- var template = "{p1}/{p2}.{p3?}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
- expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
- false,
- true,
- null,
- null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[1].Parts[0]);
- expected.Parameters.Add(expected.Segments[1].Parts[2]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_LastSegment()
+ {
+ // Arrange
+ var template = "{p1}/{p2}.{p3?}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+ expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
+ false,
+ true,
+ null,
+ null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[1].Parts[0]);
+ expected.Parameters.Add(expected.Segments[1].Parts[2]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Fact]
- public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
- {
- // Arrange
- var template = "{p2}/.{p3?}";
-
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
- false,
- false,
- defaultValue: null,
- inlineConstraints: null));
-
- expected.Segments.Add(new TemplateSegment());
- expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
- expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
- false,
- true,
- null,
- null));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
- expected.Parameters.Add(expected.Segments[1].Parts[1]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Fact]
+ public void Parse_ComplexSegment_OptionalParameterFollowingPeriod_PeriodAfterSlash()
+ {
+ // Arrange
+ var template = "{p2}/.{p3?}";
+
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: null));
+
+ expected.Segments.Add(new TemplateSegment());
+ expected.Segments[1].Parts.Add(TemplatePart.CreateLiteral("."));
+ expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p3",
+ false,
+ true,
+ null,
+ null));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+ expected.Parameters.Add(expected.Segments[1].Parts[1]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", @"regex(^\d{3}-\d{3}-\d{4}$)")] // ssn
- [InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)}", @"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")] // date
- [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", @"regex(^\w+\@\w+\.\w+)")] // email
- [InlineData(@"{p1:regex(([}}])\w+)}", @"regex(([}])\w+)")] // Not balanced }
- [InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced {
- public void Parse_RegularExpressions(string template, string constraint)
- {
- // Arrange
- var expected = new RouteTemplate(template, new List<TemplateSegment>());
- expected.Segments.Add(new TemplateSegment());
- var c = new InlineConstraint(constraint);
- expected.Segments[0].Parts.Add(
- TemplatePart.CreateParameter("p1",
- false,
- false,
- defaultValue: null,
- inlineConstraints: new List<InlineConstraint> { c }));
- expected.Parameters.Add(expected.Segments[0].Parts[0]);
-
- // Act
- var actual = TemplateParser.Parse(template);
-
- // Assert
- Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
- }
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}", @"regex(^\d{3}-\d{3}-\d{4}$)")] // ssn
+ [InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)}", @"regex(^\d{1,2}\/\d{1,2}\/\d{4}$)")] // date
+ [InlineData(@"{p1:regex(^\w+\@\w+\.\w+)}", @"regex(^\w+\@\w+\.\w+)")] // email
+ [InlineData(@"{p1:regex(([}}])\w+)}", @"regex(([}])\w+)")] // Not balanced }
+ [InlineData(@"{p1:regex(([{{(])\w+)}", @"regex(([{(])\w+)")] // Not balanced {
+ public void Parse_RegularExpressions(string template, string constraint)
+ {
+ // Arrange
+ var expected = new RouteTemplate(template, new List<TemplateSegment>());
+ expected.Segments.Add(new TemplateSegment());
+ var c = new InlineConstraint(constraint);
+ expected.Segments[0].Parts.Add(
+ TemplatePart.CreateParameter("p1",
+ false,
+ false,
+ defaultValue: null,
+ inlineConstraints: new List<InlineConstraint> { c }));
+ expected.Parameters.Add(expected.Segments[0].Parts[0]);
+
+ // Act
+ var actual = TemplateParser.Parse(template);
+
+ // Assert
+ Assert.Equal<RouteTemplate>(expected, actual, new TemplateEqualityComparer());
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}}$)}")] // extra }
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}}")] // extra } at the end
- [InlineData(@"{{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}")] // extra { at the beginning
- [InlineData(@"{p1:regex(([}])\w+}")] // Not escaped }
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}$)}")] // Not escaped }
- [InlineData(@"{p1:regex(abc)")]
- [ReplaceCulture]
- public void Parse_RegularExpressions_Invalid(string template)
- {
- // Act and Assert
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse(template),
- "There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
- "'}' character. (Parameter 'routeTemplate')");
- }
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}}$)}")] // extra }
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}}")] // extra } at the end
+ [InlineData(@"{{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}}$)}")] // extra { at the beginning
+ [InlineData(@"{p1:regex(([}])\w+}")] // Not escaped }
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{4}$)}")] // Not escaped }
+ [InlineData(@"{p1:regex(abc)")]
+ [ReplaceCulture]
+ public void Parse_RegularExpressions_Invalid(string template)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse(template),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a matching " +
+ "'}' character. (Parameter 'routeTemplate')");
+ }
- [Theory]
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{{4}}$)}")] // extra {
- [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{4}}$)}")] // Not escaped {
- [ReplaceCulture]
- public void Parse_RegularExpressions_Unescaped(string template)
- {
- // Act and Assert
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse(template),
- "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'. (Parameter 'routeTemplate')");
- }
+ [Theory]
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{{{4}}$)}")] // extra {
+ [InlineData(@"{p1:regex(^\d{{3}}-\d{{3}}-\d{4}}$)}")] // Not escaped {
+ [ReplaceCulture]
+ public void Parse_RegularExpressions_Unescaped(string template)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse(template),
+ "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'. (Parameter 'routeTemplate')");
+ }
- [Theory]
- [InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
- [InlineData("{p1?}{p2}", "p1", "{p2}")]
- [InlineData("{p1?}{p2?}", "p1", "{p2?}")]
- [InlineData("{p1}.{p2?})", "p2", ")")]
- [InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
- [ReplaceCulture]
- public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
- string template,
- string parameter,
- string invalid)
- {
- // Act and Assert
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse(template),
- "An optional parameter must be at the end of the segment. In the segment '" + template +
- "', optional parameter '" + parameter + "' is followed by '" + invalid + "'. (Parameter 'routeTemplate')");
- }
+ [Theory]
+ [InlineData("{p1}.{p2?}.{p3}", "p2", ".")]
+ [InlineData("{p1?}{p2}", "p1", "{p2}")]
+ [InlineData("{p1?}{p2?}", "p1", "{p2?}")]
+ [InlineData("{p1}.{p2?})", "p2", ")")]
+ [InlineData("{foorb?}-bar-{z}", "foorb", "-bar-")]
+ [ReplaceCulture]
+ public void Parse_ComplexSegment_OptionalParameter_NotTheLastPart(
+ string template,
+ string parameter,
+ string invalid)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse(template),
+ "An optional parameter must be at the end of the segment. In the segment '" + template +
+ "', optional parameter '" + parameter + "' is followed by '" + invalid + "'. (Parameter 'routeTemplate')");
+ }
- [Theory]
- [InlineData("{p1}-{p2?}", "-")]
- [InlineData("{p1}..{p2?}", "..")]
- [InlineData("..{p2?}", "..")]
- [InlineData("{p1}.abc.{p2?}", ".abc.")]
- [InlineData("{p1}{p2?}", "{p1}")]
- [ReplaceCulture]
- public void Parse_ComplexSegment_OptionalParametersSeparatedByPeriod_Invalid(string template, string parameter)
- {
- // Act and Assert
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse(template),
- "In the segment '" + template + "', the optional parameter 'p2' is preceded by an invalid " +
- "segment '" + parameter + "'. Only a period (.) can precede an optional parameter. (Parameter 'routeTemplate')");
- }
+ [Theory]
+ [InlineData("{p1}-{p2?}", "-")]
+ [InlineData("{p1}..{p2?}", "..")]
+ [InlineData("..{p2?}", "..")]
+ [InlineData("{p1}.abc.{p2?}", ".abc.")]
+ [InlineData("{p1}{p2?}", "{p1}")]
+ [ReplaceCulture]
+ public void Parse_ComplexSegment_OptionalParametersSeparatedByPeriod_Invalid(string template, string parameter)
+ {
+ // Act and Assert
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse(template),
+ "In the segment '" + template + "', the optional parameter 'p2' is preceded by an invalid " +
+ "segment '" + parameter + "'. Only a period (.) can precede an optional parameter. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_WithRepeatedParameter()
- {
- var ex = ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
- "The route parameter name 'controller' appears more than one time in the route template. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_WithRepeatedParameter()
+ {
+ var ex = ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{Controller}.mvc/{id}/{controller}"),
+ "The route parameter name 'controller' appears more than one time in the route template. (Parameter 'routeTemplate')");
+ }
- [Theory]
- [InlineData("123{a}abc{")]
- [InlineData("123{a}abc}")]
- [InlineData("xyz}123{a}abc}")]
- [InlineData("{{p1}")]
- [InlineData("{p1}}")]
- [InlineData("p1}}p2{")]
- [ReplaceCulture]
- public void InvalidTemplate_WithMismatchedBraces(string template)
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse(template),
- @"There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character. (Parameter 'routeTemplate')");
- }
+ [Theory]
+ [InlineData("123{a}abc{")]
+ [InlineData("123{a}abc}")]
+ [InlineData("xyz}123{a}abc}")]
+ [InlineData("{{p1}")]
+ [InlineData("{p1}}")]
+ [InlineData("p1}}p2{")]
+ [ReplaceCulture]
+ public void InvalidTemplate_WithMismatchedBraces(string template)
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse(template),
+ @"There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("123{a}abc{*moo}"),
- "A path segment that contains more than one section, such as a literal section or a parameter, " +
- "cannot contain a catch-all parameter. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotHaveCatchAllInMultiSegment()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("123{a}abc{*moo}"),
+ "A path segment that contains more than one section, such as a literal section or a parameter, " +
+ "cannot contain a catch-all parameter. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{*p1}/{*p2}"),
- "A catch-all parameter can only appear as the last segment of the route template. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotHaveMoreThanOneCatchAll()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{*p1}/{*p2}"),
+ "A catch-all parameter can only appear as the last segment of the route template. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{*p1}abc{*p2}"),
- "A path segment that contains more than one section, such as a literal section or a parameter, " +
- "cannot contain a catch-all parameter. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotHaveMoreThanOneCatchAllInMultiSegment()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{*p1}abc{*p2}"),
+ "A path segment that contains more than one section, such as a literal section or a parameter, " +
+ "cannot contain a catch-all parameter. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotHaveCatchAllWithNoName()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("foo/{*}"),
- "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional," +
- " and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotHaveCatchAllWithNoName()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("foo/{*}"),
+ "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional," +
+ " and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
+ }
- [Theory]
- [InlineData("{a*}", "a*")]
- [InlineData("{*a*}", "a*")]
- [InlineData("{*a*:int}", "a*")]
- [InlineData("{*a*=5}", "a*")]
- [InlineData("{*a*b=5}", "a*b")]
- [InlineData("{p1?}.{p2/}/{p3}", "p2/")]
- [InlineData("{p{{}", "p{")]
- [InlineData("{p}}}", "p}")]
- [InlineData("{p/}", "p/")]
- [ReplaceCulture]
- public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(
- string template,
- string parameterName)
- {
- // Arrange
- var expectedMessage = "The route parameter name '" + parameterName + "' is invalid. Route parameter " +
- "names must be non-empty and cannot contain these characters: '{', '}', '/'. The '?' character " +
- "marks a parameter as optional, and can occur only at the end of the parameter. The '*' character " +
- "marks a parameter as catch-all, and can occur only at the start of the parameter.";
-
- // Act & Assert
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse(template), expectedMessage + " (Parameter 'routeTemplate')");
- }
+ [Theory]
+ [InlineData("{a*}", "a*")]
+ [InlineData("{*a*}", "a*")]
+ [InlineData("{*a*:int}", "a*")]
+ [InlineData("{*a*=5}", "a*")]
+ [InlineData("{*a*b=5}", "a*b")]
+ [InlineData("{p1?}.{p2/}/{p3}", "p2/")]
+ [InlineData("{p{{}", "p{")]
+ [InlineData("{p}}}", "p}")]
+ [InlineData("{p/}", "p/")]
+ [ReplaceCulture]
+ public void ParseRouteParameter_ThrowsIf_ParameterContainsSpecialCharacters(
+ string template,
+ string parameterName)
+ {
+ // Arrange
+ var expectedMessage = "The route parameter name '" + parameterName + "' is invalid. Route parameter " +
+ "names must be non-empty and cannot contain these characters: '{', '}', '/'. The '?' character " +
+ "marks a parameter as optional, and can occur only at the end of the parameter. The '*' character " +
+ "marks a parameter as catch-all, and can occur only at the start of the parameter.";
+
+ // Act & Assert
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse(template), expectedMessage + " (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("foo/{{p1}"),
- "There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("foo/{{p1}"),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("foo/{p1}}"),
- "There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotHaveConsecutiveCloseBrace()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("foo/{p1}}"),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_SameParameterTwiceThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{aaa}/{AAA}"),
- "The route parameter name 'AAA' appears more than one time in the route template. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_SameParameterTwiceThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{aaa}/{AAA}"),
+ "The route parameter name 'AAA' appears more than one time in the route template. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{aaa}/{*AAA}"),
- "The route parameter name 'AAA' appears more than one time in the route template. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_SameParameterTwiceAndOneCatchAllThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{aaa}/{*AAA}"),
+ "The route parameter name 'AAA' appears more than one time in the route template. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{a}/{aa}a}/{z}"),
- "There is an incomplete parameter in the route template. Check that each '{' character has a " +
- "matching '}' character. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_InvalidParameterNameWithCloseBracketThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{a}/{aa}a}/{z}"),
+ "There is an incomplete parameter in the route template. Check that each '{' character has a " +
+ "matching '}' character. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{a}/{a{aa}/{z}"),
- "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_InvalidParameterNameWithOpenBracketThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{a}/{a{aa}/{z}"),
+ "In a route parameter, '{' and '}' must be escaped with '{{' and '}}'. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{a}/{}/{z}"),
- "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
- " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_InvalidParameterNameWithEmptyNameThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{a}/{}/{z}"),
+ "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
+ " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{Controller}.mvc/{?}"),
- "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
- " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_InvalidParameterNameWithQuestionThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{Controller}.mvc/{?}"),
+ "The route parameter name '' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
+ " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{a}//{z}"),
- "The route template separator character '/' cannot appear consecutively. It must be separated by " +
- "either a parameter or a literal value. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_ConsecutiveSeparatorsSlashSlashThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{a}//{z}"),
+ "The route template separator character '/' cannot appear consecutively. It must be separated by " +
+ "either a parameter or a literal value. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
- "A catch-all parameter can only appear as the last segment of the route template. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_WithCatchAllNotAtTheEndThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("foo/{p1}/{*p2}/{p3}"),
+ "A catch-all parameter can only appear as the last segment of the route template. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_RepeatedParametersThrows()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("foo/aa{p1}{p2}"),
- "A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
- "a literal string. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_RepeatedParametersThrows()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("foo/aa{p1}{p2}"),
+ "A path segment cannot contain two consecutive parameters. They must be separated by a '/' or by " +
+ "a literal string. (Parameter 'routeTemplate')");
+ }
- [Theory]
- [InlineData("/foo")]
- [InlineData("~/foo")]
- public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routeTemplate)
- {
- // Arrange & Act
- var pattern = TemplateParser.Parse(routeTemplate);
+ [Theory]
+ [InlineData("/foo")]
+ [InlineData("~/foo")]
+ public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routeTemplate)
+ {
+ // Arrange & Act
+ var pattern = TemplateParser.Parse(routeTemplate);
- // Assert
- Assert.Equal(routeTemplate, pattern.TemplateText);
- }
+ // Assert
+ Assert.Equal(routeTemplate, pattern.TemplateText);
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotStartWithTilde()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("~foo"),
- "The route template cannot start with a '~' character unless followed by a '/'. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotStartWithTilde()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("~foo"),
+ "The route template cannot start with a '~' character unless followed by a '/'. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CannotContainQuestionMark()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("foor?bar"),
- "The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CannotContainQuestionMark()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("foor?bar"),
+ "The literal section 'foor?bar' is invalid. Literal sections cannot contain the '?' character. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{foor?b}"),
- "The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot" +
- " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
- " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
- " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_ParameterCannotContainQuestionMark_UnlessAtEnd()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{foor?b}"),
+ "The route parameter name 'foor?b' is invalid. Route parameter names must be non-empty and cannot" +
+ " contain these characters: '{', '}', '/'. The '?' character marks a parameter as optional, and" +
+ " can occur only at the end of the parameter. The '*' character marks a parameter as catch-all," +
+ " and can occur only at the start of the parameter. (Parameter 'routeTemplate')");
+ }
- [Fact]
- [ReplaceCulture]
- public void InvalidTemplate_CatchAllMarkedOptional()
- {
- ExceptionAssert.Throws<ArgumentException>(
- () => TemplateParser.Parse("{a}/{*b?}"),
- "A catch-all parameter cannot be marked optional. (Parameter 'routeTemplate')");
- }
+ [Fact]
+ [ReplaceCulture]
+ public void InvalidTemplate_CatchAllMarkedOptional()
+ {
+ ExceptionAssert.Throws<ArgumentException>(
+ () => TemplateParser.Parse("{a}/{*b?}"),
+ "A catch-all parameter cannot be marked optional. (Parameter 'routeTemplate')");
+ }
- private class TemplateEqualityComparer : IEqualityComparer<RouteTemplate>
+ private class TemplateEqualityComparer : IEqualityComparer<RouteTemplate>
+ {
+ public bool Equals(RouteTemplate x, RouteTemplate y)
{
- public bool Equals(RouteTemplate x, RouteTemplate y)
+ if (x == null && y == null)
+ {
+ return true;
+ }
+ else if (x == null || y == null)
+ {
+ return false;
+ }
+ else
{
- if (x == null && y == null)
+ if (!string.Equals(x.TemplateText, y.TemplateText, StringComparison.Ordinal))
{
- return true;
+ return false;
}
- else if (x == null || y == null)
+
+ if (x.Segments.Count != y.Segments.Count)
{
return false;
}
- else
- {
- if (!string.Equals(x.TemplateText, y.TemplateText, StringComparison.Ordinal))
- {
- return false;
- }
- if (x.Segments.Count != y.Segments.Count)
- {
- return false;
- }
-
- for (var i = 0; i < x.Segments.Count; i++)
- {
- if (x.Segments[i].Parts.Count != y.Segments[i].Parts.Count)
- {
- return false;
- }
-
- for (int j = 0; j < x.Segments[i].Parts.Count; j++)
- {
- if (!Equals(x.Segments[i].Parts[j], y.Segments[i].Parts[j]))
- {
- return false;
- }
- }
- }
-
- if (x.Parameters.Count != y.Parameters.Count)
+ for (var i = 0; i < x.Segments.Count; i++)
+ {
+ if (x.Segments[i].Parts.Count != y.Segments[i].Parts.Count)
{
return false;
}
- for (var i = 0; i < x.Parameters.Count; i++)
+ for (int j = 0; j < x.Segments[i].Parts.Count; j++)
{
- if (!Equals(x.Parameters[i], y.Parameters[i]))
+ if (!Equals(x.Segments[i].Parts[j], y.Segments[i].Parts[j]))
{
return false;
}
}
-
- return true;
- }
- }
-
- private bool Equals(TemplatePart x, TemplatePart y)
- {
- if (x.IsLiteral != y.IsLiteral ||
- x.IsParameter != y.IsParameter ||
- x.IsCatchAll != y.IsCatchAll ||
- x.IsOptional != y.IsOptional ||
- !String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
- !String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
- (x.InlineConstraints == null && y.InlineConstraints != null) ||
- (x.InlineConstraints != null && y.InlineConstraints == null))
- {
- return false;
- }
-
- if (x.InlineConstraints == null && y.InlineConstraints == null)
- {
- return true;
}
- if (x.InlineConstraints.Count() != y.InlineConstraints.Count())
+ if (x.Parameters.Count != y.Parameters.Count)
{
return false;
}
- foreach (var xconstraint in x.InlineConstraints)
+ for (var i = 0; i < x.Parameters.Count; i++)
{
- if (!y.InlineConstraints.Any<InlineConstraint>(
- c => string.Equals(c.Constraint, xconstraint.Constraint, StringComparison.Ordinal)))
+ if (!Equals(x.Parameters[i], y.Parameters[i]))
{
return false;
}
@@ -904,11 +867,47 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
return true;
}
+ }
+
+ private bool Equals(TemplatePart x, TemplatePart y)
+ {
+ if (x.IsLiteral != y.IsLiteral ||
+ x.IsParameter != y.IsParameter ||
+ x.IsCatchAll != y.IsCatchAll ||
+ x.IsOptional != y.IsOptional ||
+ !String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
+ !String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
+ (x.InlineConstraints == null && y.InlineConstraints != null) ||
+ (x.InlineConstraints != null && y.InlineConstraints == null))
+ {
+ return false;
+ }
+
+ if (x.InlineConstraints == null && y.InlineConstraints == null)
+ {
+ return true;
+ }
+
+ if (x.InlineConstraints.Count() != y.InlineConstraints.Count())
+ {
+ return false;
+ }
- public int GetHashCode(RouteTemplate obj)
+ foreach (var xconstraint in x.InlineConstraints)
{
- throw new NotImplementedException();
+ if (!y.InlineConstraints.Any<InlineConstraint>(
+ c => string.Equals(c.Constraint, xconstraint.Constraint, StringComparison.Ordinal)))
+ {
+ return false;
+ }
}
+
+ return true;
+ }
+
+ public int GetHashCode(RouteTemplate obj)
+ {
+ throw new NotImplementedException();
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/Template/TemplateSegmentTest.cs b/src/Http/Routing/test/UnitTests/Template/TemplateSegmentTest.cs
index b5a5e8b3f1..32ccf2d46d 100644
--- a/src/Http/Routing/test/UnitTests/Template/TemplateSegmentTest.cs
+++ b/src/Http/Routing/test/UnitTests/Template/TemplateSegmentTest.cs
@@ -5,45 +5,44 @@ using System;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Template
+namespace Microsoft.AspNetCore.Routing.Template;
+
+public class TemplateSegmentTest
{
- public class TemplateSegmentTest
+ [Fact]
+ public void Ctor_RoutePatternPathSegment_ShouldThrowArgumentNullExceptionWhenOtherIsNull()
{
- [Fact]
- public void Ctor_RoutePatternPathSegment_ShouldThrowArgumentNullExceptionWhenOtherIsNull()
- {
- const RoutePatternPathSegment other = null;
+ const RoutePatternPathSegment other = null;
- var actual = Assert.ThrowsAny<ArgumentNullException>(() => new TemplateSegment(other));
- Assert.Equal(nameof(other), actual.ParamName);
- }
+ var actual = Assert.ThrowsAny<ArgumentNullException>(() => new TemplateSegment(other));
+ Assert.Equal(nameof(other), actual.ParamName);
+ }
- [Fact]
- public void ToRoutePatternPathSegment()
- {
- // Arrange
- var literalPartA = RoutePatternFactory.LiteralPart("A");
- var paramPartB = RoutePatternFactory.ParameterPart("B");
- var paramPartC = RoutePatternFactory.ParameterPart("C");
- var paramPartD = RoutePatternFactory.ParameterPart("D");
- var separatorPartE = RoutePatternFactory.SeparatorPart("E");
- var templateSegment = new TemplateSegment(RoutePatternFactory.Segment(paramPartC, literalPartA, separatorPartE, paramPartB));
+ [Fact]
+ public void ToRoutePatternPathSegment()
+ {
+ // Arrange
+ var literalPartA = RoutePatternFactory.LiteralPart("A");
+ var paramPartB = RoutePatternFactory.ParameterPart("B");
+ var paramPartC = RoutePatternFactory.ParameterPart("C");
+ var paramPartD = RoutePatternFactory.ParameterPart("D");
+ var separatorPartE = RoutePatternFactory.SeparatorPart("E");
+ var templateSegment = new TemplateSegment(RoutePatternFactory.Segment(paramPartC, literalPartA, separatorPartE, paramPartB));
- // Act
- var routePatternPathSegment = templateSegment.ToRoutePatternPathSegment();
- templateSegment.Parts[1] = new TemplatePart(RoutePatternFactory.ParameterPart("D"));
- templateSegment.Parts.RemoveAt(0);
+ // Act
+ var routePatternPathSegment = templateSegment.ToRoutePatternPathSegment();
+ templateSegment.Parts[1] = new TemplatePart(RoutePatternFactory.ParameterPart("D"));
+ templateSegment.Parts.RemoveAt(0);
- // Assert
- Assert.Equal(4, routePatternPathSegment.Parts.Count);
- Assert.IsType<RoutePatternParameterPart>(routePatternPathSegment.Parts[0]);
- Assert.Equal(paramPartC.Name, ((RoutePatternParameterPart) routePatternPathSegment.Parts[0]).Name);
- Assert.IsType<RoutePatternLiteralPart>(routePatternPathSegment.Parts[1]);
- Assert.Equal(literalPartA.Content, ((RoutePatternLiteralPart) routePatternPathSegment.Parts[1]).Content);
- Assert.IsType<RoutePatternSeparatorPart>(routePatternPathSegment.Parts[2]);
- Assert.Equal(separatorPartE.Content, ((RoutePatternSeparatorPart) routePatternPathSegment.Parts[2]).Content);
- Assert.IsType<RoutePatternParameterPart>(routePatternPathSegment.Parts[3]);
- Assert.Equal(paramPartB.Name, ((RoutePatternParameterPart) routePatternPathSegment.Parts[3]).Name);
- }
+ // Assert
+ Assert.Equal(4, routePatternPathSegment.Parts.Count);
+ Assert.IsType<RoutePatternParameterPart>(routePatternPathSegment.Parts[0]);
+ Assert.Equal(paramPartC.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[0]).Name);
+ Assert.IsType<RoutePatternLiteralPart>(routePatternPathSegment.Parts[1]);
+ Assert.Equal(literalPartA.Content, ((RoutePatternLiteralPart)routePatternPathSegment.Parts[1]).Content);
+ Assert.IsType<RoutePatternSeparatorPart>(routePatternPathSegment.Parts[2]);
+ Assert.Equal(separatorPartE.Content, ((RoutePatternSeparatorPart)routePatternPathSegment.Parts[2]).Content);
+ Assert.IsType<RoutePatternParameterPart>(routePatternPathSegment.Parts[3]);
+ Assert.Equal(paramPartB.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[3]).Name);
}
}
diff --git a/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs b/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs
index 760c46aa77..bd12008e45 100644
--- a/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs
+++ b/src/Http/Routing/test/UnitTests/TemplateParserDefaultValuesTests.cs
@@ -8,143 +8,142 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tests
+namespace Microsoft.AspNetCore.Routing.Tests;
+
+public class TemplateParserDefaultValuesTests
{
- public class TemplateParserDefaultValuesTests
+ private static readonly IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
+
+ [Fact]
+ public void InlineDefaultValueSpecified_InlineValueIsUsed()
{
- private static readonly IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
+ // Arrange & Act
+ var routeBuilder = CreateRouteBuilder();
+
+ // Act
+ routeBuilder.MapRoute("mockName",
+ "{controller}/{action}/{id:int=12}",
+ defaults: null,
+ constraints: null);
+
+ // Assert
+ var defaults = ((Route)routeBuilder.Routes[0]).Defaults;
+ Assert.Equal("12", defaults["id"]);
+ }
- [Fact]
- public void InlineDefaultValueSpecified_InlineValueIsUsed()
- {
- // Arrange & Act
- var routeBuilder = CreateRouteBuilder();
+ [Theory]
+ [InlineData(@"{controller}/{action}/{p1:regex(([}}])\w+)=}}asd}", "}asd")]
+ [InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)=12/12/1234}", @"12/12/1234")]
+ public void InlineDefaultValueSpecified_WithSpecialCharacters(string template, string value)
+ {
+ // Arrange & Act
+ var routeBuilder = CreateRouteBuilder();
+
+ // Act
+ routeBuilder.MapRoute("mockName",
+ template,
+ defaults: null,
+ constraints: null);
+
+ // Assert
+ var defaults = ((Route)routeBuilder.Routes[0]).Defaults;
+ Assert.Equal(value, defaults["p1"]);
+ }
- // Act
- routeBuilder.MapRoute("mockName",
- "{controller}/{action}/{id:int=12}",
- defaults: null,
- constraints: null);
+ [Fact]
+ public void ExplicitDefaultValueSpecified_WithInlineDefaultValue_Throws()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
+
+ // Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(
+ () => routeBuilder.MapRoute("mockName",
+ "{controller}/{action}/{id:int=12}",
+ defaults: new { id = 13 },
+ constraints: null));
+
+ var message = "An error occurred while creating the route with name 'mockName' and template" +
+ " '{controller}/{action}/{id:int=12}'.";
+ Assert.Equal(message, ex.Message);
+
+ Assert.NotNull(ex.InnerException);
+ message = "The route parameter 'id' has both an inline default value and an explicit default" +
+ " value specified. A route parameter cannot contain an inline default value when" +
+ " a default value is specified explicitly. Consider removing one of them.";
+ Assert.Equal(message, ex.InnerException.Message);
+ }
- // Assert
- var defaults = ((Route)routeBuilder.Routes[0]).Defaults;
- Assert.Equal("12", defaults["id"]);
- }
+ [Fact]
+ [ReplaceCulture]
+ public void EmptyDefaultValue_WithOptionalParameter_Throws()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
+
+ // Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(
+ () => routeBuilder.MapRoute("mockName",
+ "{controller}/{action}/{id:int=?}",
+ defaults: new { id = 13 },
+ constraints: null));
+
+ var message = "An error occurred while creating the route with name 'mockName' and template" +
+ " '{controller}/{action}/{id:int=?}'.";
+ Assert.Equal(message, ex.Message);
+
+ Assert.NotNull(ex.InnerException);
+ message = "An optional parameter cannot have default value. (Parameter 'routeTemplate')";
+ Assert.Equal(message, ex.InnerException.Message);
+ }
- [Theory]
- [InlineData(@"{controller}/{action}/{p1:regex(([}}])\w+)=}}asd}", "}asd")]
- [InlineData(@"{p1:regex(^\d{{1,2}}\/\d{{1,2}}\/\d{{4}}$)=12/12/1234}", @"12/12/1234")]
- public void InlineDefaultValueSpecified_WithSpecialCharacters(string template, string value)
- {
- // Arrange & Act
- var routeBuilder = CreateRouteBuilder();
+ [Fact]
+ [ReplaceCulture]
+ public void NonEmptyDefaultValue_WithOptionalParameter_Throws()
+ {
+ // Arrange
+ var routeBuilder = CreateRouteBuilder();
- // Act
- routeBuilder.MapRoute("mockName",
- template,
- defaults: null,
+ // Act & Assert
+ var ex = Assert.Throws<RouteCreationException>(() =>
+ {
+ routeBuilder.MapRoute(
+ "mockName",
+ "{controller}/{action}/{id:int=12?}",
+ defaults: new { id = 13 },
constraints: null);
+ });
- // Assert
- var defaults = ((Route)routeBuilder.Routes[0]).Defaults;
- Assert.Equal(value, defaults["p1"]);
- }
-
- [Fact]
- public void ExplicitDefaultValueSpecified_WithInlineDefaultValue_Throws()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
-
- // Act & Assert
- var ex = Assert.Throws<RouteCreationException>(
- () => routeBuilder.MapRoute("mockName",
- "{controller}/{action}/{id:int=12}",
- defaults: new { id = 13 },
- constraints: null));
-
- var message = "An error occurred while creating the route with name 'mockName' and template" +
- " '{controller}/{action}/{id:int=12}'.";
- Assert.Equal(message, ex.Message);
-
- Assert.NotNull(ex.InnerException);
- message = "The route parameter 'id' has both an inline default value and an explicit default" +
- " value specified. A route parameter cannot contain an inline default value when" +
- " a default value is specified explicitly. Consider removing one of them.";
- Assert.Equal(message, ex.InnerException.Message);
- }
-
- [Fact]
- [ReplaceCulture]
- public void EmptyDefaultValue_WithOptionalParameter_Throws()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
-
- // Act & Assert
- var ex = Assert.Throws<RouteCreationException>(
- () => routeBuilder.MapRoute("mockName",
- "{controller}/{action}/{id:int=?}",
- defaults: new { id = 13 },
- constraints: null));
-
- var message = "An error occurred while creating the route with name 'mockName' and template" +
- " '{controller}/{action}/{id:int=?}'.";
- Assert.Equal(message, ex.Message);
-
- Assert.NotNull(ex.InnerException);
- message = "An optional parameter cannot have default value. (Parameter 'routeTemplate')";
- Assert.Equal(message, ex.InnerException.Message);
- }
-
- [Fact]
- [ReplaceCulture]
- public void NonEmptyDefaultValue_WithOptionalParameter_Throws()
- {
- // Arrange
- var routeBuilder = CreateRouteBuilder();
-
- // Act & Assert
- var ex = Assert.Throws<RouteCreationException>(() =>
- {
- routeBuilder.MapRoute(
- "mockName",
- "{controller}/{action}/{id:int=12?}",
- defaults: new { id = 13 },
- constraints: null);
- });
-
- var message = "An error occurred while creating the route with name 'mockName' and template" +
- " '{controller}/{action}/{id:int=12?}'.";
- Assert.Equal(message, ex.Message);
-
- Assert.NotNull(ex.InnerException);
- message = "An optional parameter cannot have default value. (Parameter 'routeTemplate')";
- Assert.Equal(message, ex.InnerException.Message);
- }
-
- private static IRouteBuilder CreateRouteBuilder()
- {
- var services = new ServiceCollection();
- services.AddSingleton<IInlineConstraintResolver>(_inlineConstraintResolver);
- services.AddSingleton<RoutingMarkerService>();
- services.AddSingleton<ParameterPolicyFactory, DefaultParameterPolicyFactory>();
- services.Configure<RouteOptions>(options => { });
+ var message = "An error occurred while creating the route with name 'mockName' and template" +
+ " '{controller}/{action}/{id:int=12?}'.";
+ Assert.Equal(message, ex.Message);
- var applicationBuilder = Mock.Of<IApplicationBuilder>();
- applicationBuilder.ApplicationServices = services.BuildServiceProvider();
+ Assert.NotNull(ex.InnerException);
+ message = "An optional parameter cannot have default value. (Parameter 'routeTemplate')";
+ Assert.Equal(message, ex.InnerException.Message);
+ }
- var routeBuilder = new RouteBuilder(applicationBuilder);
- routeBuilder.DefaultHandler = Mock.Of<IRouter>();
- return routeBuilder;
- }
+ private static IRouteBuilder CreateRouteBuilder()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton<IInlineConstraintResolver>(_inlineConstraintResolver);
+ services.AddSingleton<RoutingMarkerService>();
+ services.AddSingleton<ParameterPolicyFactory, DefaultParameterPolicyFactory>();
+ services.Configure<RouteOptions>(options => { });
+
+ var applicationBuilder = Mock.Of<IApplicationBuilder>();
+ applicationBuilder.ApplicationServices = services.BuildServiceProvider();
+
+ var routeBuilder = new RouteBuilder(applicationBuilder);
+ routeBuilder.DefaultHandler = Mock.Of<IRouter>();
+ return routeBuilder;
+ }
- private static IInlineConstraintResolver GetInlineConstraintResolver()
- {
- var services = new ServiceCollection().AddOptions();
- var serviceProvider = services.BuildServiceProvider();
- var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
- return new DefaultInlineConstraintResolver(accessor, serviceProvider);
- }
+ private static IInlineConstraintResolver GetInlineConstraintResolver()
+ {
+ var services = new ServiceCollection().AddOptions();
+ var serviceProvider = services.BuildServiceProvider();
+ var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
+ return new DefaultInlineConstraintResolver(accessor, serviceProvider);
}
}
diff --git a/src/Http/Routing/test/UnitTests/TestConstants.cs b/src/Http/Routing/test/UnitTests/TestConstants.cs
index 6a09494aa5..73e3f5ba47 100644
--- a/src/Http/Routing/test/UnitTests/TestConstants.cs
+++ b/src/Http/Routing/test/UnitTests/TestConstants.cs
@@ -4,10 +4,9 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public static class TestConstants
{
- public static class TestConstants
- {
- internal static readonly RequestDelegate EmptyRequestDelegate = (context) => Task.CompletedTask;
- }
+ internal static readonly RequestDelegate EmptyRequestDelegate = (context) => Task.CompletedTask;
}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/CapturingConstraint.cs b/src/Http/Routing/test/UnitTests/TestObjects/CapturingConstraint.cs
index 501a27ed23..cb14f1ed4b 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/CapturingConstraint.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/CapturingConstraint.cs
@@ -4,21 +4,20 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+internal class CapturingConstraint : IRouteConstraint
{
- internal class CapturingConstraint : IRouteConstraint
- {
- public IDictionary<string, object> Values { get; private set; }
+ public IDictionary<string, object> Values { get; private set; }
- public bool Match(
- HttpContext httpContext,
- IRouter route,
- string routeKey,
- RouteValueDictionary values,
- RouteDirection routeDirection)
- {
- Values = new RouteValueDictionary(values);
- return true;
- }
+ public bool Match(
+ HttpContext httpContext,
+ IRouter route,
+ string routeKey,
+ RouteValueDictionary values,
+ RouteDirection routeDirection)
+ {
+ Values = new RouteValueDictionary(values);
+ return true;
}
}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/DynamicEndpointDataSource.cs b/src/Http/Routing/test/UnitTests/TestObjects/DynamicEndpointDataSource.cs
index c4d2175413..67d81494f7 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/DynamicEndpointDataSource.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/DynamicEndpointDataSource.cs
@@ -6,49 +6,48 @@ using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+public class DynamicEndpointDataSource : EndpointDataSource
{
- public class DynamicEndpointDataSource : EndpointDataSource
+ private readonly List<Endpoint> _endpoints;
+ private CancellationTokenSource _cts;
+ private CancellationChangeToken _changeToken;
+ private readonly object _lock;
+
+ public DynamicEndpointDataSource(params Endpoint[] endpoints)
+ {
+ _endpoints = new List<Endpoint>();
+ _endpoints.AddRange(endpoints);
+ _lock = new object();
+
+ CreateChangeToken();
+ }
+
+ public override IChangeToken GetChangeToken() => _changeToken;
+
+ public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
+
+ // Trigger change
+ public void AddEndpoint(Endpoint endpoint)
+ {
+ _endpoints.Add(endpoint);
+
+ // Capture the old tokens so that we can raise the callbacks on them. This is important so that
+ // consumers do not register callbacks on an inflight event causing a stackoverflow.
+ var oldTokenSource = _cts;
+ var oldToken = _changeToken;
+
+ CreateChangeToken();
+
+ // Raise consumer callbacks. Any new callback registration would happen on the new token
+ // created in earlier step.
+ oldTokenSource.Cancel();
+ }
+
+ private void CreateChangeToken()
{
- private readonly List<Endpoint> _endpoints;
- private CancellationTokenSource _cts;
- private CancellationChangeToken _changeToken;
- private readonly object _lock;
-
- public DynamicEndpointDataSource(params Endpoint[] endpoints)
- {
- _endpoints = new List<Endpoint>();
- _endpoints.AddRange(endpoints);
- _lock = new object();
-
- CreateChangeToken();
- }
-
- public override IChangeToken GetChangeToken() => _changeToken;
-
- public override IReadOnlyList<Endpoint> Endpoints => _endpoints;
-
- // Trigger change
- public void AddEndpoint(Endpoint endpoint)
- {
- _endpoints.Add(endpoint);
-
- // Capture the old tokens so that we can raise the callbacks on them. This is important so that
- // consumers do not register callbacks on an inflight event causing a stackoverflow.
- var oldTokenSource = _cts;
- var oldToken = _changeToken;
-
- CreateChangeToken();
-
- // Raise consumer callbacks. Any new callback registration would happen on the new token
- // created in earlier step.
- oldTokenSource.Cancel();
- }
-
- private void CreateChangeToken()
- {
- _cts = new CancellationTokenSource();
- _changeToken = new CancellationChangeToken(_cts.Token);
- }
+ _cts = new CancellationTokenSource();
+ _changeToken = new CancellationChangeToken(_cts.Token);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/SlugifyParameterTransformer.cs b/src/Http/Routing/test/UnitTests/TestObjects/SlugifyParameterTransformer.cs
index 209b4a106b..cfbb3c6f6f 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/SlugifyParameterTransformer.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/SlugifyParameterTransformer.cs
@@ -3,14 +3,13 @@
using System.Text.RegularExpressions;
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
- public class SlugifyParameterTransformer : IOutboundParameterTransformer
+ public string TransformOutbound(object value)
{
- public string TransformOutbound(object value)
- {
- // Slugify value
- return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLowerInvariant();
- }
+ // Slugify value
+ return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLowerInvariant();
}
}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/TestMatcher.cs b/src/Http/Routing/test/UnitTests/TestObjects/TestMatcher.cs
index 4982b8d8b6..8c234537a3 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/TestMatcher.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/TestMatcher.cs
@@ -6,26 +6,25 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+internal class TestMatcher : Matcher
{
- internal class TestMatcher : Matcher
+ private readonly bool _isHandled;
+
+ public TestMatcher(bool isHandled)
{
- private readonly bool _isHandled;
+ _isHandled = isHandled;
+ }
- public TestMatcher(bool isHandled)
+ public override Task MatchAsync(HttpContext httpContext)
+ {
+ if (_isHandled)
{
- _isHandled = isHandled;
+ httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index" });
+ httpContext.SetEndpoint(new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "Test endpoint"));
}
- public override Task MatchAsync(HttpContext httpContext)
- {
- if (_isHandled)
- {
- httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index" });
- httpContext.SetEndpoint(new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "Test endpoint"));
- }
-
- return Task.CompletedTask;
- }
+ return Task.CompletedTask;
}
}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/TestMatcherFactory.cs b/src/Http/Routing/test/UnitTests/TestObjects/TestMatcherFactory.cs
index 9894c590f0..ece8696eba 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/TestMatcherFactory.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/TestMatcherFactory.cs
@@ -3,20 +3,19 @@
using Microsoft.AspNetCore.Routing.Matching;
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+internal class TestMatcherFactory : MatcherFactory
{
- internal class TestMatcherFactory : MatcherFactory
- {
- private readonly bool _isHandled;
+ private readonly bool _isHandled;
- public TestMatcherFactory(bool isHandled)
- {
- _isHandled = isHandled;
- }
+ public TestMatcherFactory(bool isHandled)
+ {
+ _isHandled = isHandled;
+ }
- public override Matcher CreateMatcher(EndpointDataSource dataSource)
- {
- return new TestMatcher(_isHandled);
- }
+ public override Matcher CreateMatcher(EndpointDataSource dataSource)
+ {
+ return new TestMatcher(_isHandled);
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/TestServiceProvider.cs b/src/Http/Routing/test/UnitTests/TestObjects/TestServiceProvider.cs
index 95a82088d9..279bf80b66 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/TestServiceProvider.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/TestServiceProvider.cs
@@ -3,13 +3,12 @@
using System;
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+internal class TestServiceProvider : IServiceProvider
{
- internal class TestServiceProvider : IServiceProvider
+ public object GetService(Type serviceType)
{
- public object GetService(Type serviceType)
- {
- throw new NotImplementedException();
- }
+ throw new NotImplementedException();
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/UnitTests/TestObjects/UpperCaseParameterTransform.cs b/src/Http/Routing/test/UnitTests/TestObjects/UpperCaseParameterTransform.cs
index b5bb1e8064..072e823180 100644
--- a/src/Http/Routing/test/UnitTests/TestObjects/UpperCaseParameterTransform.cs
+++ b/src/Http/Routing/test/UnitTests/TestObjects/UpperCaseParameterTransform.cs
@@ -1,13 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.Routing.TestObjects
+namespace Microsoft.AspNetCore.Routing.TestObjects;
+
+public class UpperCaseParameterTransform : IOutboundParameterTransformer
{
- public class UpperCaseParameterTransform : IOutboundParameterTransformer
+ public string TransformOutbound(object value)
{
- public string TransformOutbound(object value)
- {
- return value?.ToString()?.ToUpperInvariant();
- }
+ return value?.ToString()?.ToUpperInvariant();
}
}
diff --git a/src/Http/Routing/test/UnitTests/Tree/LinkGenerationDecisionTreeTest.cs b/src/Http/Routing/test/UnitTests/Tree/LinkGenerationDecisionTreeTest.cs
index e01c33908c..9e44787811 100644
--- a/src/Http/Routing/test/UnitTests/Tree/LinkGenerationDecisionTreeTest.cs
+++ b/src/Http/Routing/test/UnitTests/Tree/LinkGenerationDecisionTreeTest.cs
@@ -9,761 +9,760 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+public class LinkGenerationDecisionTreeTest
{
- public class LinkGenerationDecisionTreeTest
+ [Fact]
+ public void GetMatches_AllowsNullAmbientValues()
{
- [Fact]
- public void GetMatches_AllowsNullAmbientValues()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { });
- entries.Add(entry);
+ var entry = CreateMatch(new { });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { });
+ var context = CreateContext(new { });
- // Act
- var matches = tree.GetMatches(context.Values, ambientValues: null);
+ // Act
+ var matches = tree.GetMatches(context.Values, ambientValues: null);
- // Assert
- Assert.Same(entry, Assert.Single(matches).Match);
- }
+ // Assert
+ Assert.Same(entry, Assert.Single(matches).Match);
+ }
- [Fact]
- public void SelectSingleEntry_NoCriteria()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_NoCriteria()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { });
- entries.Add(entry);
+ var entry = CreateMatch(new { });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { });
+ var context = CreateContext(new { });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- Assert.Same(entry, Assert.Single(matches).Match);
- }
+ // Assert
+ Assert.Same(entry, Assert.Single(matches).Match);
+ }
- [Fact]
- public void SelectSingleEntry_MultipleCriteria()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_MultipleCriteria()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry);
+ var entry = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "Buy" });
+ var context = CreateContext(new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- Assert.Same(entry, Assert.Single(matches).Match);
- }
+ // Assert
+ Assert.Same(entry, Assert.Single(matches).Match);
+ }
- [Fact]
- public void SelectSingleEntry_MultipleCriteria_AmbientValues()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_MultipleCriteria_AmbientValues()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry);
+ var entry = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(values: null, ambientValues: new { controller = "Store", action = "Buy" });
+ var context = CreateContext(values: null, ambientValues: new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- var match = Assert.Single(matches);
- Assert.Same(entry, match.Match);
- Assert.False(match.IsFallbackMatch);
- }
+ // Assert
+ var match = Assert.Single(matches);
+ Assert.Same(entry, match.Match);
+ Assert.False(match.IsFallbackMatch);
+ }
- [Fact]
- public void SelectSingleEntry_MultipleCriteria_Replaced()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_MultipleCriteria_Replaced()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry);
+ var entry = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(
- values: new { action = "Buy" },
- ambientValues: new { controller = "Store", action = "Cart" });
+ var context = CreateContext(
+ values: new { action = "Buy" },
+ ambientValues: new { controller = "Store", action = "Cart" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- var match = Assert.Single(matches);
- Assert.Same(entry, match.Match);
- Assert.False(match.IsFallbackMatch);
- }
+ // Assert
+ var match = Assert.Single(matches);
+ Assert.Same(entry, match.Match);
+ Assert.False(match.IsFallbackMatch);
+ }
- [Fact]
- public void SelectSingleEntry_MultipleCriteria_AmbientValue_Ignored()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_MultipleCriteria_AmbientValue_Ignored()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { controller = "Store", action = (string)null });
- entries.Add(entry);
+ var entry = CreateMatch(new { controller = "Store", action = (string)null });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(
- values: new { controller = "Store" },
- ambientValues: new { controller = "Store", action = "Buy" });
+ var context = CreateContext(
+ values: new { controller = "Store" },
+ ambientValues: new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- var match = Assert.Single(matches);
- Assert.Same(entry, match.Match);
- Assert.True(match.IsFallbackMatch);
- }
+ // Assert
+ var match = Assert.Single(matches);
+ Assert.Same(entry, match.Match);
+ Assert.True(match.IsFallbackMatch);
+ }
- [Fact]
- public void SelectSingleEntry_MultipleCriteria_NoMatch()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_MultipleCriteria_NoMatch()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry);
+ var entry = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "AddToCart" });
+ var context = CreateContext(new { controller = "Store", action = "AddToCart" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- Assert.Empty(matches);
- }
+ // Assert
+ Assert.Empty(matches);
+ }
- [Fact]
- public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry);
+ var entry = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(
- values: new { controller = "Store" },
- ambientValues: new { controller = "Store", action = "Cart" });
+ var context = CreateContext(
+ values: new { controller = "Store" },
+ ambientValues: new { controller = "Store", action = "Cart" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- Assert.Empty(matches);
- }
+ // Assert
+ Assert.Empty(matches);
+ }
- [Fact]
- public void SelectMultipleEntries_OneDoesntMatch()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectMultipleEntries_OneDoesntMatch()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Cart" });
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Cart" });
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(
- values: new { controller = "Store" },
- ambientValues: new { controller = "Store", action = "Buy" });
+ var context = CreateContext(
+ values: new { controller = "Store" },
+ ambientValues: new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues);
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues);
- // Assert
- Assert.Same(entry1, Assert.Single(matches).Match);
- }
+ // Assert
+ Assert.Same(entry1, Assert.Single(matches).Match);
+ }
- [Fact]
- public void SelectMultipleEntries_BothMatch_CriteriaSubset()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectMultipleEntries_BothMatch_CriteriaSubset()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store" });
- entry2.Entry.Order = 1;
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store" });
+ entry2.Entry.Order = 1;
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(
- values: new { controller = "Store" },
- ambientValues: new { controller = "Store", action = "Buy" });
+ var context = CreateContext(
+ values: new { controller = "Store" },
+ ambientValues: new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Equal(entries, matches);
- }
+ // Assert
+ Assert.Equal(entries, matches);
+ }
- [Fact]
- public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { slug = "1234" });
- entry2.Entry.Order = 1;
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { slug = "1234" });
+ entry2.Entry.Order = 1;
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "Buy", slug = "1234" });
+ var context = CreateContext(new { controller = "Store", action = "Buy", slug = "1234" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Equal(entries, matches);
- }
+ // Assert
+ Assert.Equal(entries, matches);
+ }
- // Precedence is ignored for sorting because they have different order
- [Fact]
- public void SelectMultipleEntries_BothMatch_OrderedByOrder()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Precedence is ignored for sorting because they have different order
+ [Fact]
+ public void SelectMultipleEntries_BothMatch_OrderedByOrder()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
- entry1.Entry.Precedence = 0;
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entry1.Entry.Precedence = 0;
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });
- entry2.Entry.Order = 1;
- entry2.Entry.Precedence = 1;
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entry2.Entry.Order = 1;
+ entry2.Entry.Precedence = 1;
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "Buy" });
+ var context = CreateContext(new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Equal(entries, matches);
- }
+ // Assert
+ Assert.Equal(entries, matches);
+ }
- // Precedence is used for sorting because they have the same order
- [Fact]
- public void SelectMultipleEntries_BothMatch_OrderedByPrecedence()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Precedence is used for sorting because they have the same order
+ [Fact]
+ public void SelectMultipleEntries_BothMatch_OrderedByPrecedence()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
- entry1.Entry.Precedence = 1;
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entry1.Entry.Precedence = 1;
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });
- entry2.Entry.Precedence = 0;
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entry2.Entry.Precedence = 0;
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "Buy" });
+ var context = CreateContext(new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Equal(entries, matches);
- }
+ // Assert
+ Assert.Equal(entries, matches);
+ }
- // Template is used for sorting because they have the same order
- [Fact]
- public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Template is used for sorting because they have the same order
+ [Fact]
+ public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Buy" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "Buy" });
+ var context = CreateContext(new { controller = "Store", action = "Buy" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Equal(entries, matches);
- }
+ // Assert
+ Assert.Equal(entries, matches);
+ }
- [Fact]
- public void GetMatches_ControllersWithArea_AllValuesExplicit()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_ControllersWithArea_AllValuesExplicit()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy", area = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy", area = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Buy", area = "Admin" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Buy", area = "Admin" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", action = "Buy", area = "Admin" });
+ var context = CreateContext(new { controller = "Store", action = "Buy", area = "Admin" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); });
- }
+ // Assert
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); });
+ }
- [Fact]
- public void GetMatches_ControllersWithArea_SomeValuesAmbient()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_ControllersWithArea_SomeValuesAmbient()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy", area = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy", area = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Buy", area = "Admin" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Buy", area = "Admin" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Store", }, new { action = "Buy", area = "Admin", });
+ var context = CreateContext(new { controller = "Store", }, new { action = "Buy", area = "Admin", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); },
- m => { Assert.Same(entry1, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); },
+ m => { Assert.Same(entry1, m); });
+ }
- [Fact]
- public void GetMatches_ControllersWithArea_AllValuesAmbient()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_ControllersWithArea_AllValuesAmbient()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Store", action = "Buy", area = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Store", action = "Buy", area = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { controller = "Store", action = "Buy", area = "Admin" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { controller = "Store", action = "Buy", area = "Admin" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { }, new { controller = "Store", action = "Buy", area = "Admin", });
+ var context = CreateContext(new { }, new { controller = "Store", action = "Buy", area = "Admin", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); },
- m => { Assert.Same(entry1, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); },
+ m => { Assert.Same(entry1, m); });
+ }
- [Fact]
- public void GetMatches_PagesWithArea_AllValuesExplicit()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_PagesWithArea_AllValuesExplicit()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { page = "/Store/Buy", area = "Admin" });
+ var context = CreateContext(new { page = "/Store/Buy", area = "Admin" });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); });
+ }
- [Fact]
- public void GetMatches_PagesWithArea_SomeValuesAmbient()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_PagesWithArea_SomeValuesAmbient()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { page = "/Store/Buy", }, new { area = "Admin", });
+ var context = CreateContext(new { page = "/Store/Buy", }, new { area = "Admin", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); },
- m => { Assert.Same(entry1, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); },
+ m => { Assert.Same(entry1, m); });
+ }
- [Fact]
- public void GetMatches_PagesWithArea_AllValuesAmbient()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_PagesWithArea_AllValuesAmbient()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { page = "/Store/Buy", area = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin" });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { }, new { page = "/Store/Buy", area = "Admin", });
+ var context = CreateContext(new { }, new { page = "/Store/Buy", area = "Admin", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); },
- m => { Assert.Same(entry1, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); },
+ m => { Assert.Same(entry1, m); });
+ }
- [Fact]
- public void GetMatches_LinkToControllerFromPage()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_LinkToControllerFromPage()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Home", action = "Index", }, new { page = "/Store/Buy", });
+ var context = CreateContext(new { controller = "Home", action = "Index", }, new { page = "/Store/Buy", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry1, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry1, m); });
+ }
- [Fact]
- public void GetMatches_LinkToControllerFromPage_WithArea()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_LinkToControllerFromPage_WithArea()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = "Admin", page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = "Admin", page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin", controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin", controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Home", action = "Index", }, new { page = "/Store/Buy", area = "Admin", });
+ var context = CreateContext(new { controller = "Home", action = "Index", }, new { page = "/Store/Buy", area = "Admin", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry1, m); });
- }
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry1, m); });
+ }
- [Fact]
- public void GetMatches_LinkToControllerFromPage_WithPageValue()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ [Fact]
+ public void GetMatches_LinkToControllerFromPage_WithPageValue()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var tree = new LinkGenerationDecisionTree(entries);
+ var tree = new LinkGenerationDecisionTree(entries);
- var context = CreateContext(new { controller = "Home", action = "Index", page = "16", }, new { page = "/Store/Buy", });
+ var context = CreateContext(new { controller = "Home", action = "Index", page = "16", }, new { page = "/Store/Buy", });
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- // Assert
- Assert.Empty(matches);
- }
-
- [Fact]
- public void GetMatches_LinkToControllerFromPage_WithPageValueAmbiguous()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Assert
+ Assert.Empty(matches);
+ }
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ [Fact]
+ public void GetMatches_LinkToControllerFromPage_WithPageValueAmbiguous()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var tree = new LinkGenerationDecisionTree(entries);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var context = CreateContext(new { controller = "Home", action = "Index", page = "/Store/Buy", }, new { page = "/Store/Buy", });
+ var tree = new LinkGenerationDecisionTree(entries);
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ var context = CreateContext(new { controller = "Home", action = "Index", page = "/Store/Buy", }, new { page = "/Store/Buy", });
- // Assert
- Assert.Empty(matches);
- }
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- [Fact]
- public void GetMatches_LinkToPageFromController()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Assert
+ Assert.Empty(matches);
+ }
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ [Fact]
+ public void GetMatches_LinkToPageFromController()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var tree = new LinkGenerationDecisionTree(entries);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var context = CreateContext(new { page = "/Store/Buy", }, new { controller = "Home", action = "Index", });
+ var tree = new LinkGenerationDecisionTree(entries);
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ var context = CreateContext(new { page = "/Store/Buy", }, new { controller = "Home", action = "Index", });
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); });
- }
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- [Fact]
- public void GetMatches_LinkToPageFromController_WithArea()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); });
+ }
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = "Admin", page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ [Fact]
+ public void GetMatches_LinkToPageFromController_WithArea()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin", controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = "Admin", page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var tree = new LinkGenerationDecisionTree(entries);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = "Admin", controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var context = CreateContext(new { page = "/Store/Buy", }, new { controller = "Home", action = "Index", area = "Admin", });
+ var tree = new LinkGenerationDecisionTree(entries);
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ var context = CreateContext(new { page = "/Store/Buy", }, new { controller = "Home", action = "Index", area = "Admin", });
- // Assert
- Assert.Collection(
- matches,
- m => { Assert.Same(entry2, m); });
- }
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- [Fact]
- public void GetMatches_LinkToPageFromController_WithActionValue()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Assert
+ Assert.Collection(
+ matches,
+ m => { Assert.Same(entry2, m); });
+ }
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ [Fact]
+ public void GetMatches_LinkToPageFromController_WithActionValue()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var tree = new LinkGenerationDecisionTree(entries);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var context = CreateContext(new { page = "/Store/Buy", action = "buy", }, new { controller = "Home", action = "Index", page = "16", });
+ var tree = new LinkGenerationDecisionTree(entries);
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ var context = CreateContext(new { page = "/Store/Buy", action = "buy", }, new { controller = "Home", action = "Index", page = "16", });
- // Assert
- Assert.Empty(matches);
- }
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- [Fact]
- public void GetMatches_LinkToPageFromController_WithActionValueAmbiguous()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
+ // Assert
+ Assert.Empty(matches);
+ }
- var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
- entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
- entries.Add(entry1);
+ [Fact]
+ public void GetMatches_LinkToPageFromController_WithActionValueAmbiguous()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
- var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
- entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
- entries.Add(entry2);
+ var entry1 = CreateMatch(new { controller = "Home", action = "Index", area = (string)null, page = (string)null, });
+ entry1.Entry.RouteTemplate = TemplateParser.Parse("a");
+ entries.Add(entry1);
- var tree = new LinkGenerationDecisionTree(entries);
+ var entry2 = CreateMatch(new { page = "/Store/Buy", area = (string)null, controller = (string)null, action = (string)null, });
+ entry2.Entry.RouteTemplate = TemplateParser.Parse("b");
+ entries.Add(entry2);
- var context = CreateContext(new { page = "/Store/Buy", action = "Index", }, new { controller = "Home", action = "Index", page = "16", });
+ var tree = new LinkGenerationDecisionTree(entries);
- // Act
- var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
+ var context = CreateContext(new { page = "/Store/Buy", action = "Index", }, new { controller = "Home", action = "Index", page = "16", });
- // Assert
- Assert.Empty(matches);
- }
+ // Act
+ var matches = tree.GetMatches(context.Values, context.AmbientValues).Select(m => m.Match).ToList();
- [Fact]
- public void ToDebuggerDisplayString_GivesAFlattenedTree()
- {
- // Arrange
- var entries = new List<OutboundMatch>();
- entries.Add(CreateMatch(new { action = "Buy", controller = "Store", version = "V1" }, "Store/Buy/V1"));
- entries.Add(CreateMatch(new { action = "Buy", controller = "Store", area = "Admin" }, "Admin/Store/Buy"));
- entries.Add(CreateMatch(new { action = "Buy", controller = "Products" }, "Products/Buy"));
- entries.Add(CreateMatch(new { action = "Buy", controller = "Store", version = "V2" }, "Store/Buy/V2"));
- entries.Add(CreateMatch(new { action = "Cart", controller = "Store" }, "Store/Cart"));
- entries.Add(CreateMatch(new { action = "Index", controller = "Home" }, "Home/Index/{id?}"));
- var tree = new LinkGenerationDecisionTree(entries);
- var newLine = Environment.NewLine;
- var expected =
- " => action: Buy => controller: Store => version: V1 (Matches: Store/Buy/V1)" + newLine +
- " => action: Buy => controller: Store => version: V2 (Matches: Store/Buy/V2)" + newLine +
- " => action: Buy => controller: Store => area: Admin (Matches: Admin/Store/Buy)" + newLine +
- " => action: Buy => controller: Products (Matches: Products/Buy)" + newLine +
- " => action: Cart => controller: Store (Matches: Store/Cart)" + newLine +
- " => action: Index => controller: Home (Matches: Home/Index/{id?})" + newLine;
-
- // Act
- var flattenedTree = tree.DebuggerDisplayString;
-
- // Assert
- Assert.Equal(expected, flattenedTree);
- }
+ // Assert
+ Assert.Empty(matches);
+ }
- private OutboundMatch CreateMatch(object requiredValues, string routeTemplate = null)
- {
- var match = new OutboundMatch();
- match.Entry = new OutboundRouteEntry();
- match.Entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
+ [Fact]
+ public void ToDebuggerDisplayString_GivesAFlattenedTree()
+ {
+ // Arrange
+ var entries = new List<OutboundMatch>();
+ entries.Add(CreateMatch(new { action = "Buy", controller = "Store", version = "V1" }, "Store/Buy/V1"));
+ entries.Add(CreateMatch(new { action = "Buy", controller = "Store", area = "Admin" }, "Admin/Store/Buy"));
+ entries.Add(CreateMatch(new { action = "Buy", controller = "Products" }, "Products/Buy"));
+ entries.Add(CreateMatch(new { action = "Buy", controller = "Store", version = "V2" }, "Store/Buy/V2"));
+ entries.Add(CreateMatch(new { action = "Cart", controller = "Store" }, "Store/Cart"));
+ entries.Add(CreateMatch(new { action = "Index", controller = "Home" }, "Home/Index/{id?}"));
+ var tree = new LinkGenerationDecisionTree(entries);
+ var newLine = Environment.NewLine;
+ var expected =
+ " => action: Buy => controller: Store => version: V1 (Matches: Store/Buy/V1)" + newLine +
+ " => action: Buy => controller: Store => version: V2 (Matches: Store/Buy/V2)" + newLine +
+ " => action: Buy => controller: Store => area: Admin (Matches: Admin/Store/Buy)" + newLine +
+ " => action: Buy => controller: Products (Matches: Products/Buy)" + newLine +
+ " => action: Cart => controller: Store (Matches: Store/Cart)" + newLine +
+ " => action: Index => controller: Home (Matches: Home/Index/{id?})" + newLine;
+
+ // Act
+ var flattenedTree = tree.DebuggerDisplayString;
+
+ // Assert
+ Assert.Equal(expected, flattenedTree);
+ }
- if (!string.IsNullOrEmpty(routeTemplate))
- {
- match.Entry.RouteTemplate = new RouteTemplate(RoutePatternFactory.Parse(routeTemplate));
- }
+ private OutboundMatch CreateMatch(object requiredValues, string routeTemplate = null)
+ {
+ var match = new OutboundMatch();
+ match.Entry = new OutboundRouteEntry();
+ match.Entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
- return match;
+ if (!string.IsNullOrEmpty(routeTemplate))
+ {
+ match.Entry.RouteTemplate = new RouteTemplate(RoutePatternFactory.Parse(routeTemplate));
}
- private VirtualPathContext CreateContext(object values, object ambientValues = null)
- {
- var context = new VirtualPathContext(
- new DefaultHttpContext(),
- new RouteValueDictionary(ambientValues),
- new RouteValueDictionary(values));
+ return match;
+ }
- return context;
- }
+ private VirtualPathContext CreateContext(object values, object ambientValues = null)
+ {
+ var context = new VirtualPathContext(
+ new DefaultHttpContext(),
+ new RouteValueDictionary(ambientValues),
+ new RouteValueDictionary(values));
+
+ return context;
}
}
diff --git a/src/Http/Routing/test/UnitTests/Tree/TreeRouteBuilderTest.cs b/src/Http/Routing/test/UnitTests/Tree/TreeRouteBuilderTest.cs
index 6c462613bf..7a2ec9d5de 100644
--- a/src/Http/Routing/test/UnitTests/Tree/TreeRouteBuilderTest.cs
+++ b/src/Http/Routing/test/UnitTests/Tree/TreeRouteBuilderTest.cs
@@ -10,255 +10,254 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+public class TreeRouteBuilderTest
{
- public class TreeRouteBuilderTest
+ [Fact]
+ public void TreeRouter_BuildThrows_RoutesWithTheSameNameAndDifferentTemplates()
{
- [Fact]
- public void TreeRouter_BuildThrows_RoutesWithTheSameNameAndDifferentTemplates()
- {
- // Arrange
- var builder = CreateBuilder();
-
- var message = "Two or more routes named 'Get_Products' have different templates.";
-
- builder.MapOutbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("api/Products"),
- new RouteValueDictionary(),
- "Get_Products",
- order: 0);
-
- builder.MapOutbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("Products/Index"),
- new RouteValueDictionary(),
- "Get_Products",
- order: 0);
-
- // Act & Assert
- ExceptionAssert.ThrowsArgument(() =>
- {
- builder.Build();
- }, "linkGenerationEntries", message);
- }
-
- [Fact]
- public void TreeRouter_BuildDoesNotThrow_RoutesWithTheSameNameAndSameTemplates()
+ // Arrange
+ var builder = CreateBuilder();
+
+ var message = "Two or more routes named 'Get_Products' have different templates.";
+
+ builder.MapOutbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("api/Products"),
+ new RouteValueDictionary(),
+ "Get_Products",
+ order: 0);
+
+ builder.MapOutbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("Products/Index"),
+ new RouteValueDictionary(),
+ "Get_Products",
+ order: 0);
+
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(() =>
{
- // Arrange
- var builder = CreateBuilder();
-
- builder.MapOutbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("api/Products"),
- new RouteValueDictionary(),
- "Get_Products",
- order: 0);
-
- builder.MapOutbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("api/products"),
- new RouteValueDictionary(),
- "Get_Products",
- order: 0);
-
- // Act & Assert (does not throw)
builder.Build();
- }
+ }, "linkGenerationEntries", message);
+ }
- [Fact]
- public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithDefaultValues()
- {
- // Arrange
- var builder = CreateBuilder();
+ [Fact]
+ public void TreeRouter_BuildDoesNotThrow_RoutesWithTheSameNameAndSameTemplates()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+
+ builder.MapOutbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("api/Products"),
+ new RouteValueDictionary(),
+ "Get_Products",
+ order: 0);
+
+ builder.MapOutbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("api/products"),
+ new RouteValueDictionary(),
+ "Get_Products",
+ order: 0);
+
+ // Act & Assert (does not throw)
+ builder.Build();
+ }
- builder.MapInbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("a/{b=3}/c"),
- "Intermediate",
- order: 0);
+ [Fact]
+ public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithDefaultValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var tree = builder.Build();
+ builder.MapInbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("a/{b=3}/c"),
+ "Intermediate",
+ order: 0);
- // Assert
- Assert.NotNull(tree);
- Assert.NotNull(tree.MatchingTrees);
- var matchingTree = Assert.Single(tree.MatchingTrees);
+ // Act
+ var tree = builder.Build();
- var firstSegment = Assert.Single(matchingTree.Root.Literals);
- Assert.Equal("a", firstSegment.Key);
- Assert.NotNull(firstSegment.Value.Parameters);
+ // Assert
+ Assert.NotNull(tree);
+ Assert.NotNull(tree.MatchingTrees);
+ var matchingTree = Assert.Single(tree.MatchingTrees);
- var secondSegment = firstSegment.Value.Parameters;
- Assert.Empty(secondSegment.Matches);
+ var firstSegment = Assert.Single(matchingTree.Root.Literals);
+ Assert.Equal("a", firstSegment.Key);
+ Assert.NotNull(firstSegment.Value.Parameters);
- var thirdSegment = Assert.Single(secondSegment.Literals);
- Assert.Equal("c", thirdSegment.Key);
- Assert.Single(thirdSegment.Value.Matches);
- }
+ var secondSegment = firstSegment.Value.Parameters;
+ Assert.Empty(secondSegment.Matches);
- [Fact]
- public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithMultipleIntermediateParametersWithDefaultOrOptionalValues()
- {
- // Arrange
- var builder = CreateBuilder();
+ var thirdSegment = Assert.Single(secondSegment.Literals);
+ Assert.Equal("c", thirdSegment.Key);
+ Assert.Single(thirdSegment.Value.Matches);
+ }
- builder.MapInbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("a/{b=3}/c/{d?}/e/{*f}"),
- "Intermediate",
- order: 0);
+ [Fact]
+ public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithMultipleIntermediateParametersWithDefaultOrOptionalValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var tree = builder.Build();
+ builder.MapInbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("a/{b=3}/c/{d?}/e/{*f}"),
+ "Intermediate",
+ order: 0);
- // Assert
- Assert.NotNull(tree);
- Assert.NotNull(tree.MatchingTrees);
- var matchingTree = Assert.Single(tree.MatchingTrees);
+ // Act
+ var tree = builder.Build();
- var firstSegment = Assert.Single(matchingTree.Root.Literals);
- Assert.Equal("a", firstSegment.Key);
- Assert.NotNull(firstSegment.Value.Parameters);
+ // Assert
+ Assert.NotNull(tree);
+ Assert.NotNull(tree.MatchingTrees);
+ var matchingTree = Assert.Single(tree.MatchingTrees);
- var secondSegment = firstSegment.Value.Parameters;
- Assert.Empty(secondSegment.Matches);
+ var firstSegment = Assert.Single(matchingTree.Root.Literals);
+ Assert.Equal("a", firstSegment.Key);
+ Assert.NotNull(firstSegment.Value.Parameters);
- var thirdSegment = Assert.Single(secondSegment.Literals);
- Assert.Equal("c", thirdSegment.Key);
- Assert.Empty(thirdSegment.Value.Matches);
+ var secondSegment = firstSegment.Value.Parameters;
+ Assert.Empty(secondSegment.Matches);
- var fourthSegment = thirdSegment.Value.Parameters;
- Assert.NotNull(fourthSegment);
- Assert.Empty(fourthSegment.Matches);
+ var thirdSegment = Assert.Single(secondSegment.Literals);
+ Assert.Equal("c", thirdSegment.Key);
+ Assert.Empty(thirdSegment.Value.Matches);
- var fifthSegment = Assert.Single(fourthSegment.Literals);
- Assert.Equal("e", fifthSegment.Key);
- Assert.Single(fifthSegment.Value.Matches);
+ var fourthSegment = thirdSegment.Value.Parameters;
+ Assert.NotNull(fourthSegment);
+ Assert.Empty(fourthSegment.Matches);
- var sixthSegment = fifthSegment.Value.CatchAlls;
- Assert.NotNull(sixthSegment);
- Assert.Single(sixthSegment.Matches);
- }
+ var fifthSegment = Assert.Single(fourthSegment.Literals);
+ Assert.Equal("e", fifthSegment.Key);
+ Assert.Single(fifthSegment.Value.Matches);
- [Fact]
- public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithOptionalValues()
- {
- // Arrange
- var builder = CreateBuilder();
+ var sixthSegment = fifthSegment.Value.CatchAlls;
+ Assert.NotNull(sixthSegment);
+ Assert.Single(sixthSegment.Matches);
+ }
- builder.MapInbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("a/{b?}/c"),
- "Intermediate",
- order: 0);
+ [Fact]
+ public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithOptionalValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var tree = builder.Build();
+ builder.MapInbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("a/{b?}/c"),
+ "Intermediate",
+ order: 0);
- // Assert
- Assert.NotNull(tree);
- Assert.NotNull(tree.MatchingTrees);
- var matchingTree = Assert.Single(tree.MatchingTrees);
+ // Act
+ var tree = builder.Build();
- var firstSegment = Assert.Single(matchingTree.Root.Literals);
- Assert.Equal("a", firstSegment.Key);
- Assert.NotNull(firstSegment.Value.Parameters);
+ // Assert
+ Assert.NotNull(tree);
+ Assert.NotNull(tree.MatchingTrees);
+ var matchingTree = Assert.Single(tree.MatchingTrees);
- var secondSegment = firstSegment.Value.Parameters;
- Assert.Empty(secondSegment.Matches);
+ var firstSegment = Assert.Single(matchingTree.Root.Literals);
+ Assert.Equal("a", firstSegment.Key);
+ Assert.NotNull(firstSegment.Value.Parameters);
- var thirdSegment = Assert.Single(secondSegment.Literals);
- Assert.Equal("c", thirdSegment.Key);
- Assert.Single(thirdSegment.Value.Matches);
- }
+ var secondSegment = firstSegment.Value.Parameters;
+ Assert.Empty(secondSegment.Matches);
- [Fact]
- public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithConstrainedDefaultValues()
- {
- // Arrange
- var builder = CreateBuilder();
+ var thirdSegment = Assert.Single(secondSegment.Literals);
+ Assert.Equal("c", thirdSegment.Key);
+ Assert.Single(thirdSegment.Value.Matches);
+ }
- builder.MapInbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("a/{b:int=3}/c"),
- "Intermediate",
- order: 0);
+ [Fact]
+ public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithConstrainedDefaultValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var tree = builder.Build();
+ builder.MapInbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("a/{b:int=3}/c"),
+ "Intermediate",
+ order: 0);
- // Assert
- Assert.NotNull(tree);
- Assert.NotNull(tree.MatchingTrees);
- var matchingTree = Assert.Single(tree.MatchingTrees);
+ // Act
+ var tree = builder.Build();
- var firstSegment = Assert.Single(matchingTree.Root.Literals);
- Assert.Equal("a", firstSegment.Key);
- Assert.NotNull(firstSegment.Value.ConstrainedParameters);
+ // Assert
+ Assert.NotNull(tree);
+ Assert.NotNull(tree.MatchingTrees);
+ var matchingTree = Assert.Single(tree.MatchingTrees);
- var secondSegment = firstSegment.Value.ConstrainedParameters;
- Assert.Empty(secondSegment.Matches);
+ var firstSegment = Assert.Single(matchingTree.Root.Literals);
+ Assert.Equal("a", firstSegment.Key);
+ Assert.NotNull(firstSegment.Value.ConstrainedParameters);
- var thirdSegment = Assert.Single(secondSegment.Literals);
- Assert.Equal("c", thirdSegment.Key);
- Assert.Single(thirdSegment.Value.Matches);
- }
+ var secondSegment = firstSegment.Value.ConstrainedParameters;
+ Assert.Empty(secondSegment.Matches);
- [Fact]
- public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithConstrainedOptionalValues()
- {
- // Arrange
- var builder = CreateBuilder();
+ var thirdSegment = Assert.Single(secondSegment.Literals);
+ Assert.Equal("c", thirdSegment.Key);
+ Assert.Single(thirdSegment.Value.Matches);
+ }
- builder.MapInbound(
- Mock.Of<IRouter>(),
- TemplateParser.Parse("a/{b:int?}/c"),
- "Intermediate",
- order: 0);
+ [Fact]
+ public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithConstrainedOptionalValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act
- var tree = builder.Build();
+ builder.MapInbound(
+ Mock.Of<IRouter>(),
+ TemplateParser.Parse("a/{b:int?}/c"),
+ "Intermediate",
+ order: 0);
- // Assert
- Assert.NotNull(tree);
- Assert.NotNull(tree.MatchingTrees);
- var matchingTree = Assert.Single(tree.MatchingTrees);
+ // Act
+ var tree = builder.Build();
- var firstSegment = Assert.Single(matchingTree.Root.Literals);
- Assert.Equal("a", firstSegment.Key);
- Assert.NotNull(firstSegment.Value.ConstrainedParameters);
+ // Assert
+ Assert.NotNull(tree);
+ Assert.NotNull(tree.MatchingTrees);
+ var matchingTree = Assert.Single(tree.MatchingTrees);
- var secondSegment = firstSegment.Value.ConstrainedParameters;
- Assert.Empty(secondSegment.Matches);
+ var firstSegment = Assert.Single(matchingTree.Root.Literals);
+ Assert.Equal("a", firstSegment.Key);
+ Assert.NotNull(firstSegment.Value.ConstrainedParameters);
- var thirdSegment = Assert.Single(secondSegment.Literals);
- Assert.Equal("c", thirdSegment.Key);
- Assert.Single(thirdSegment.Value.Matches);
- }
+ var secondSegment = firstSegment.Value.ConstrainedParameters;
+ Assert.Empty(secondSegment.Matches);
- private static TreeRouteBuilder CreateBuilder()
- {
- var objectPoolProvider = new DefaultObjectPoolProvider();
- var objectPolicy = new UriBuilderContextPooledObjectPolicy();
- var objectPool = objectPoolProvider.Create(objectPolicy);
-
- var constraintResolver = GetInlineConstraintResolver();
- var builder = new TreeRouteBuilder(
- NullLoggerFactory.Instance,
- objectPool,
- constraintResolver);
- return builder;
- }
-
- private static IInlineConstraintResolver GetInlineConstraintResolver()
- {
- var services = new ServiceCollection().AddOptions();
- var serviceProvider = services.BuildServiceProvider();
- var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
- return new DefaultInlineConstraintResolver(accessor, serviceProvider);
- }
+ var thirdSegment = Assert.Single(secondSegment.Literals);
+ Assert.Equal("c", thirdSegment.Key);
+ Assert.Single(thirdSegment.Value.Matches);
+ }
+
+ private static TreeRouteBuilder CreateBuilder()
+ {
+ var objectPoolProvider = new DefaultObjectPoolProvider();
+ var objectPolicy = new UriBuilderContextPooledObjectPolicy();
+ var objectPool = objectPoolProvider.Create(objectPolicy);
+
+ var constraintResolver = GetInlineConstraintResolver();
+ var builder = new TreeRouteBuilder(
+ NullLoggerFactory.Instance,
+ objectPool,
+ constraintResolver);
+ return builder;
+ }
+
+ private static IInlineConstraintResolver GetInlineConstraintResolver()
+ {
+ var services = new ServiceCollection().AddOptions();
+ var serviceProvider = services.BuildServiceProvider();
+ var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
+ return new DefaultInlineConstraintResolver(accessor, serviceProvider);
}
}
diff --git a/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs b/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs
index 3a20de866f..a94da3a18c 100644
--- a/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs
+++ b/src/Http/Routing/test/UnitTests/Tree/TreeRouterTest.cs
@@ -16,70 +16,70 @@ using Microsoft.Extensions.Options;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Routing.Tree
+namespace Microsoft.AspNetCore.Routing.Tree;
+
+public class TreeRouterTest
{
- public class TreeRouterTest
- {
- private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
-
- [Theory]
- [InlineData("template/5", "template/{parameter:int}")]
- [InlineData("template/5", "template/{parameter}")]
- [InlineData("template/5", "template/{*parameter:int}")]
- [InlineData("template/5", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{parameter:alpha}")] // constraint doesn't match
- [InlineData("template/{parameter:int}", "template/{parameter}")]
- [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{parameter:int}", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{*parameter:int}")]
- [InlineData("template/{parameter}", "template/{*parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter}")]
- public async Task TreeRouter_RouteAsync_RespectsPrecedence(
- string firstTemplate,
- string secondTemplate)
- {
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, firstTemplate);
+ private static readonly RequestDelegate NullHandler = (c) => Task.CompletedTask;
+
+ [Theory]
+ [InlineData("template/5", "template/{parameter:int}")]
+ [InlineData("template/5", "template/{parameter}")]
+ [InlineData("template/5", "template/{*parameter:int}")]
+ [InlineData("template/5", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{parameter:alpha}")] // constraint doesn't match
+ [InlineData("template/{parameter:int}", "template/{parameter}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter}", "template/{*parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter}")]
+ public async Task TreeRouter_RouteAsync_RespectsPrecedence(
+ string firstTemplate,
+ string secondTemplate)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, firstTemplate);
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to route the request, the route with a higher precedence gets tried first.
- MapInboundEntry(builder, secondTemplate);
- MapInboundEntry(builder, firstTemplate);
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to route the request, the route with a higher precedence gets tried first.
+ MapInboundEntry(builder, secondTemplate);
+ MapInboundEntry(builder, firstTemplate);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext("/template/5");
+ var context = CreateRouteContext("/template/5");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ }
- [Theory]
- [InlineData("/", "")]
- [InlineData("/Literal1", "Literal1")]
- [InlineData("/Literal1/Literal2", "Literal1/Literal2")]
- [InlineData("/Literal1/Literal2/Literal3", "Literal1/Literal2/Literal3")]
- [InlineData("/Literal1/Literal2/Literal3/4", "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}")]
- [InlineData("/Literal1/Literal2/Literal3/Literal4", "Literal1/Literal2/Literal3/{*catchAll}")]
- [InlineData("/1", "{constrained1:int}")]
- [InlineData("/1/2", "{constrained1:int}/{constrained2:int}")]
- [InlineData("/1/2/3", "{constrained1:int}/{constrained2:int}/{constrained3:int}")]
- [InlineData("/1/2/3/4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}")]
- [InlineData("/1/2/3/CatchAll4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}")]
- [InlineData("/parameter1", "{parameter1}")]
- [InlineData("/parameter1/parameter2", "{parameter1}/{parameter2}")]
- [InlineData("/parameter1/parameter2/parameter3", "{parameter1}/{parameter2}/{parameter3}")]
- [InlineData("/parameter1/parameter2/parameter3/4", "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}")]
- [InlineData("/parameter1/parameter2/parameter3/CatchAll4", "{parameter1}/{parameter2}/{parameter3}/{*catchAll}")]
- public async Task TreeRouter_RouteAsync_MatchesRouteWithTheRightLength(string url, string expected)
- {
- // Arrange
- var routes = new[] {
+ [Theory]
+ [InlineData("/", "")]
+ [InlineData("/Literal1", "Literal1")]
+ [InlineData("/Literal1/Literal2", "Literal1/Literal2")]
+ [InlineData("/Literal1/Literal2/Literal3", "Literal1/Literal2/Literal3")]
+ [InlineData("/Literal1/Literal2/Literal3/4", "Literal1/Literal2/Literal3/{*constrainedCatchAll:int}")]
+ [InlineData("/Literal1/Literal2/Literal3/Literal4", "Literal1/Literal2/Literal3/{*catchAll}")]
+ [InlineData("/1", "{constrained1:int}")]
+ [InlineData("/1/2", "{constrained1:int}/{constrained2:int}")]
+ [InlineData("/1/2/3", "{constrained1:int}/{constrained2:int}/{constrained3:int}")]
+ [InlineData("/1/2/3/4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*constrainedCatchAll:int}")]
+ [InlineData("/1/2/3/CatchAll4", "{constrained1:int}/{constrained2:int}/{constrained3:int}/{*catchAll}")]
+ [InlineData("/parameter1", "{parameter1}")]
+ [InlineData("/parameter1/parameter2", "{parameter1}/{parameter2}")]
+ [InlineData("/parameter1/parameter2/parameter3", "{parameter1}/{parameter2}/{parameter3}")]
+ [InlineData("/parameter1/parameter2/parameter3/4", "{parameter1}/{parameter2}/{parameter3}/{*constrainedCatchAll:int}")]
+ [InlineData("/parameter1/parameter2/parameter3/CatchAll4", "{parameter1}/{parameter2}/{parameter3}/{*catchAll}")]
+ public async Task TreeRouter_RouteAsync_MatchesRouteWithTheRightLength(string url, string expected)
+ {
+ // Arrange
+ var routes = new[] {
"",
"Literal1",
"Literal1/Literal2",
@@ -98,1516 +98,1516 @@ namespace Microsoft.AspNetCore.Routing.Tree
"{parameter1}/{parameter2}/{parameter3}/{*catchAll}",
};
- var expectedRouteGroup = CreateRouteGroup(0, expected);
+ var expectedRouteGroup = CreateRouteGroup(0, expected);
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to route the request, the route with a higher precedence gets tried first.
- foreach (var template in routes.Reverse())
- {
- MapInboundEntry(builder, template);
- }
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to route the request, the route with a higher precedence gets tried first.
+ foreach (var template in routes.Reverse())
+ {
+ MapInboundEntry(builder, template);
+ }
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ }
- public static TheoryData<string, object[]> MatchesRoutesWithDefaultsData =>
- new TheoryData<string, object[]>
- {
+ public static TheoryData<string, object[]> MatchesRoutesWithDefaultsData =>
+ new TheoryData<string, object[]>
+ {
{ "/", new object[] { "1", "2", "3", "4" } },
{ "/a", new object[] { "a", "2", "3", "4" } },
{ "/a/b", new object[] { "a", "b", "3", "4" } },
{ "/a/b/c", new object[] { "a", "b", "c", "4" } },
{ "/a/b/c/d", new object[] { "a", "b", "c", "d" } }
- };
+ };
- [Theory]
- [MemberData(nameof(MatchesRoutesWithDefaultsData))]
- public async Task TreeRouter_RouteAsync_MatchesRoutesWithDefaults(string url, object[] routeValues)
- {
- // Arrange
- var routes = new[] {
+ [Theory]
+ [MemberData(nameof(MatchesRoutesWithDefaultsData))]
+ public async Task TreeRouter_RouteAsync_MatchesRoutesWithDefaults(string url, object[] routeValues)
+ {
+ // Arrange
+ var routes = new[] {
"{parameter1=1}/{parameter2=2}/{parameter3=3}/{parameter4=4}",
};
- var expectedRouteGroup = CreateRouteGroup(0, "{parameter1=1}/{parameter2=2}/{parameter3=3}/{parameter4=4}");
- var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
- var expectedRouteValues = new RouteValueDictionary();
- for (var i = 0; i < routeValueKeys.Length; i++)
- {
- expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
- }
+ var expectedRouteGroup = CreateRouteGroup(0, "{parameter1=1}/{parameter2=2}/{parameter3=3}/{parameter4=4}");
+ var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
+ var expectedRouteValues = new RouteValueDictionary();
+ for (var i = 0; i < routeValueKeys.Length; i++)
+ {
+ expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
+ }
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to route the request, the route with a higher precedence gets tried first.
- foreach (var template in routes.Reverse())
- {
- MapInboundEntry(builder, template);
- }
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to route the request, the route with a higher precedence gets tried first.
+ foreach (var template in routes.Reverse())
+ {
+ MapInboundEntry(builder, template);
+ }
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- foreach (var entry in expectedRouteValues)
- {
- var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
- Assert.Equal(entry.Value, data.Value);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ foreach (var entry in expectedRouteValues)
+ {
+ var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
+ Assert.Equal(entry.Value, data.Value);
}
+ }
- public static TheoryData<string, object[]> MatchesConstrainedRoutesWithDefaultsData =>
- new TheoryData<string, object[]>
- {
+ public static TheoryData<string, object[]> MatchesConstrainedRoutesWithDefaultsData =>
+ new TheoryData<string, object[]>
+ {
{ "/", new object[] { "1", "2", "3", "4" } },
{ "/10", new object[] { "10", "2", "3", "4" } },
{ "/10/11", new object[] { "10", "11", "3", "4" } },
{ "/10/11/12", new object[] { "10", "11", "12", "4" } },
{ "/10/11/12/13", new object[] { "10", "11", "12", "13" } }
- };
+ };
- [Theory]
- [MemberData(nameof(MatchesConstrainedRoutesWithDefaultsData))]
- public async Task TreeRouter_RouteAsync_MatchesConstrainedRoutesWithDefaults(string url, object[] routeValues)
- {
- // Arrange
- var routes = new[] {
+ [Theory]
+ [MemberData(nameof(MatchesConstrainedRoutesWithDefaultsData))]
+ public async Task TreeRouter_RouteAsync_MatchesConstrainedRoutesWithDefaults(string url, object[] routeValues)
+ {
+ // Arrange
+ var routes = new[] {
"{parameter1:int=1}/{parameter2:int=2}/{parameter3:int=3}/{parameter4:int=4}",
};
- var expectedRouteGroup = CreateRouteGroup(0, "{parameter1:int=1}/{parameter2:int=2}/{parameter3:int=3}/{parameter4:int=4}");
- var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
- var expectedRouteValues = new RouteValueDictionary();
- for (var i = 0; i < routeValueKeys.Length; i++)
- {
- expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
- }
+ var expectedRouteGroup = CreateRouteGroup(0, "{parameter1:int=1}/{parameter2:int=2}/{parameter3:int=3}/{parameter4:int=4}");
+ var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
+ var expectedRouteValues = new RouteValueDictionary();
+ for (var i = 0; i < routeValueKeys.Length; i++)
+ {
+ expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
+ }
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to route the request, the route with a higher precedence gets tried first.
- foreach (var template in routes.Reverse())
- {
- MapInboundEntry(builder, template);
- }
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to route the request, the route with a higher precedence gets tried first.
+ foreach (var template in routes.Reverse())
+ {
+ MapInboundEntry(builder, template);
+ }
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- foreach (var entry in expectedRouteValues)
- {
- var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
- Assert.Equal(entry.Value, data.Value);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ foreach (var entry in expectedRouteValues)
+ {
+ var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
+ Assert.Equal(entry.Value, data.Value);
}
+ }
- [Fact]
- public async Task TreeRouter_RouteAsync_MatchesCatchAllRoutesWithDefaults()
- {
- // Arrange
- var routes = new[] {
+ [Fact]
+ public async Task TreeRouter_RouteAsync_MatchesCatchAllRoutesWithDefaults()
+ {
+ // Arrange
+ var routes = new[] {
"{parameter1=1}/{parameter2=2}/{parameter3=3}/{*parameter4=4}",
};
- var url = "/a/b/c";
- var routeValues = new[] { "a", "b", "c", "4" };
+ var url = "/a/b/c";
+ var routeValues = new[] { "a", "b", "c", "4" };
- var expectedRouteGroup = CreateRouteGroup(0, "{parameter1=1}/{parameter2=2}/{parameter3=3}/{*parameter4=4}");
- var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
- var expectedRouteValues = new RouteValueDictionary();
- for (var i = 0; i < routeValueKeys.Length; i++)
- {
- expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
- }
+ var expectedRouteGroup = CreateRouteGroup(0, "{parameter1=1}/{parameter2=2}/{parameter3=3}/{*parameter4=4}");
+ var routeValueKeys = new[] { "parameter1", "parameter2", "parameter3", "parameter4" };
+ var expectedRouteValues = new RouteValueDictionary();
+ for (var i = 0; i < routeValueKeys.Length; i++)
+ {
+ expectedRouteValues.Add(routeValueKeys[i], routeValues[i]);
+ }
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to route the request, the route with a higher precedence gets tried first.
- foreach (var template in routes.Reverse())
- {
- MapInboundEntry(builder, template);
- }
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to route the request, the route with a higher precedence gets tried first.
+ foreach (var template in routes.Reverse())
+ {
+ MapInboundEntry(builder, template);
+ }
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- foreach (var entry in expectedRouteValues)
- {
- var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
- Assert.Equal(entry.Value, data.Value);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ foreach (var entry in expectedRouteValues)
+ {
+ var data = Assert.Single(context.RouteData.Values, v => v.Key == entry.Key);
+ Assert.Equal(entry.Value, data.Value);
}
+ }
- [Fact]
- public async Task TreeRouter_RouteAsync_DoesNotMatchRoutesWithIntermediateDefaultRouteValues()
- {
- // Arrange
- var url = "/a/b";
+ [Fact]
+ public async Task TreeRouter_RouteAsync_DoesNotMatchRoutesWithIntermediateDefaultRouteValues()
+ {
+ // Arrange
+ var url = "/a/b";
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- MapInboundEntry(builder, "a/b/{parameter3=3}/d");
+ MapInboundEntry(builder, "a/b/{parameter3=3}/d");
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Theory]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d")]
- public async Task TreeRouter_RouteAsync_DoesNotMatchRoutesWithMultipleIntermediateDefaultOrOptionalRouteValues(string template, string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d")]
+ public async Task TreeRouter_RouteAsync_DoesNotMatchRoutesWithMultipleIntermediateDefaultOrOptionalRouteValues(string template, string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, template);
+ MapInboundEntry(builder, template);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Theory]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e")]
- [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e/f")]
- public async Task RouteAsync_MatchRoutesWithMultipleIntermediateDefaultOrOptionalRouteValues_WhenAllIntermediateValuesAreProvided(string template, string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e")]
+ [InlineData("a/{b=3}/c/{d?}/e/{*f}", "/a/b/c/d/e/f")]
+ public async Task RouteAsync_MatchRoutesWithMultipleIntermediateDefaultOrOptionalRouteValues_WhenAllIntermediateValuesAreProvided(string template, string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, template);
+ MapInboundEntry(builder, template);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ }
- [Fact]
- public async Task TreeRouter_RouteAsync_DoesNotMatchShorterUrl()
- {
- // Arrange
- var routes = new[] {
+ [Fact]
+ public async Task TreeRouter_RouteAsync_DoesNotMatchShorterUrl()
+ {
+ // Arrange
+ var routes = new[] {
"Literal1/Literal2/Literal3",
};
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to route the request, the route with a higher precedence gets tried first.
- foreach (var template in routes.Reverse())
- {
- MapInboundEntry(builder, template);
- }
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to route the request, the route with a higher precedence gets tried first.
+ foreach (var template in routes.Reverse())
+ {
+ MapInboundEntry(builder, template);
+ }
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext("/Literal1");
+ var context = CreateRouteContext("/Literal1");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Theory]
- [InlineData("template/5", "template/{parameter:int}")]
- [InlineData("template/5", "template/{parameter}")]
- [InlineData("template/5", "template/{*parameter:int}")]
- [InlineData("template/5", "template/{*parameter}")]
- [InlineData("template/{parameter:int}", "template/{parameter}")]
- [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{parameter:int}", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{*parameter:int}")]
- [InlineData("template/{parameter}", "template/{*parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter}")]
- public async Task TreeRouter_RouteAsync_RespectsOrderOverPrecedence(
- string firstTemplate,
- string secondTemplate)
- {
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, secondTemplate);
+ [Theory]
+ [InlineData("template/5", "template/{parameter:int}")]
+ [InlineData("template/5", "template/{parameter}")]
+ [InlineData("template/5", "template/{*parameter:int}")]
+ [InlineData("template/5", "template/{*parameter}")]
+ [InlineData("template/{parameter:int}", "template/{parameter}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter}", "template/{*parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter}")]
+ public async Task TreeRouter_RouteAsync_RespectsOrderOverPrecedence(
+ string firstTemplate,
+ string secondTemplate)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, secondTemplate);
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries with a lower relative order and higher relative precedence
- // first to ensure that when we try to route the request, the route with the higher
- // relative order gets tried first.
- MapInboundEntry(builder, firstTemplate, order: 1);
- MapInboundEntry(builder, secondTemplate, order: 0);
+ // We setup the route entries with a lower relative order and higher relative precedence
+ // first to ensure that when we try to route the request, the route with the higher
+ // relative order gets tried first.
+ MapInboundEntry(builder, firstTemplate, order: 1);
+ MapInboundEntry(builder, secondTemplate, order: 0);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext("/template/5");
+ var context = CreateRouteContext("/template/5");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ }
- [Theory]
- [InlineData("///")]
- [InlineData("/a//")]
- [InlineData("/a/b//")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public async Task TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("///")]
+ [InlineData("/a//")]
+ [InlineData("/a/b//")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public async Task TryMatch_MultipleOptionalParameters_WithEmptyIntermediateSegmentsDoesNotMatch(string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
+ MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Theory]
- [InlineData("")]
- [InlineData("/")]
- [InlineData("/a")]
- [InlineData("/a/")]
- [InlineData("/a/b")]
- [InlineData("/a/b/")]
- [InlineData("/a/b/c")]
- [InlineData("/a/b/c/")]
- public async Task TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("")]
+ [InlineData("/")]
+ [InlineData("/a")]
+ [InlineData("/a/")]
+ [InlineData("/a/b")]
+ [InlineData("/a/b/")]
+ [InlineData("/a/b/c")]
+ [InlineData("/a/b/c/")]
+ public async Task TryMatch_MultipleOptionalParameters_WithIncrementalOptionalValues(string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
+ MapInboundEntry(builder, "{controller?}/{action?}/{id?}");
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ }
- [Theory]
- [InlineData("///")]
- [InlineData("////")]
- [InlineData("/a//")]
- [InlineData("/a///")]
- [InlineData("//b/")]
- [InlineData("//b//")]
- [InlineData("///c")]
- [InlineData("///c/")]
- public async Task TryMatch_MultipleParameters_WithEmptyValues(string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("///")]
+ [InlineData("////")]
+ [InlineData("/a//")]
+ [InlineData("/a///")]
+ [InlineData("//b/")]
+ [InlineData("//b//")]
+ [InlineData("///c")]
+ [InlineData("///c/")]
+ public async Task TryMatch_MultipleParameters_WithEmptyValues(string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, "{controller}/{action}/{id}");
+ MapInboundEntry(builder, "{controller}/{action}/{id}");
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Theory]
- [InlineData("/a/b/c//")]
- [InlineData("/a/b/c/////")]
- public async Task TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("/a/b/c//")]
+ [InlineData("/a/b/c/////")]
+ public async Task TryMatch_CatchAllParameters_WithEmptyValuesAtTheEnd(string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, "{controller}/{action}/{*id}");
+ MapInboundEntry(builder, "{controller}/{action}/{*id}");
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ }
- [Theory]
- [InlineData("/a/b//")]
- [InlineData("/a/b///c")]
- public async Task TryMatch_CatchAllParameters_WithEmptyValues(string url)
- {
- // Arrange
- var builder = CreateBuilder();
+ [Theory]
+ [InlineData("/a/b//")]
+ [InlineData("/a/b///c")]
+ public async Task TryMatch_CatchAllParameters_WithEmptyValues(string url)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- MapInboundEntry(builder, "{controller}/{action}/{*id}");
+ MapInboundEntry(builder, "{controller}/{action}/{*id}");
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext(url);
+ var context = CreateRouteContext(url);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Assert
+ Assert.Null(context.Handler);
+ }
- [Theory]
- [InlineData("{*path}", "/a", "a")]
- [InlineData("{*path}", "/a/b/c", "a/b/c")]
- [InlineData("a/{*path}", "/a/b", "b")]
- [InlineData("a/{*path}", "/a/b/c/d", "b/c/d")]
- [InlineData("a/{*path:regex(10/20/30)}", "/a/10/20/30", "10/20/30")]
- public async Task TreeRouter_RouteAsync_MatchesWildCard_ForLargerPathSegments(
- string template,
- string requestPath,
- string expectedResult)
- {
- // Arrange
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [InlineData("{*path}", "/a", "a")]
+ [InlineData("{*path}", "/a/b/c", "a/b/c")]
+ [InlineData("a/{*path}", "/a/b", "b")]
+ [InlineData("a/{*path}", "/a/b/c/d", "b/c/d")]
+ [InlineData("a/{*path:regex(10/20/30)}", "/a/10/20/30", "10/20/30")]
+ public async Task TreeRouter_RouteAsync_MatchesWildCard_ForLargerPathSegments(
+ string template,
+ string requestPath,
+ string expectedResult)
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- var context = CreateRouteContext(requestPath);
+ var context = CreateRouteContext(requestPath);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal(expectedResult, context.RouteData.Values["path"]);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal(expectedResult, context.RouteData.Values["path"]);
+ }
- [Theory]
- [InlineData("a/{*path}", "/a")]
- [InlineData("a/{*path}", "/a/")]
- public async Task TreeRouter_RouteAsync_MatchesCatchAll_NullValue(
- string template,
- string requestPath)
- {
- // Arrange
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [InlineData("a/{*path}", "/a")]
+ [InlineData("a/{*path}", "/a/")]
+ public async Task TreeRouter_RouteAsync_MatchesCatchAll_NullValue(
+ string template,
+ string requestPath)
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- var context = CreateRouteContext(requestPath);
+ var context = CreateRouteContext(requestPath);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Null(context.RouteData.Values["path"]);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Null(context.RouteData.Values["path"]);
+ }
- [Theory]
- [InlineData("a/{*path}", "/a")]
- [InlineData("a/{*path}", "/a/")]
- public async Task TreeRouter_RouteAsync_MatchesCatchAll_NullValue_DoesNotReplaceExistingValue(
- string template,
- string requestPath)
- {
- // Arrange
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [InlineData("a/{*path}", "/a")]
+ [InlineData("a/{*path}", "/a/")]
+ public async Task TreeRouter_RouteAsync_MatchesCatchAll_NullValue_DoesNotReplaceExistingValue(
+ string template,
+ string requestPath)
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- var context = CreateRouteContext(requestPath);
- context.RouteData.Values["path"] = "existing-value";
+ var context = CreateRouteContext(requestPath);
+ context.RouteData.Values["path"] = "existing-value";
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal("existing-value", context.RouteData.Values["path"]);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal("existing-value", context.RouteData.Values["path"]);
+ }
- [Theory]
- [InlineData("a/{*path=default}", "/a")]
- [InlineData("a/{*path=default}", "/a/")]
- public async Task TreeRouter_RouteAsync_MatchesCatchAll_UsesDefaultValue(
- string template,
- string requestPath)
- {
- // Arrange
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [InlineData("a/{*path=default}", "/a")]
+ [InlineData("a/{*path=default}", "/a/")]
+ public async Task TreeRouter_RouteAsync_MatchesCatchAll_UsesDefaultValue(
+ string template,
+ string requestPath)
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- var context = CreateRouteContext(requestPath);
- context.RouteData.Values["path"] = "existing-value";
+ var context = CreateRouteContext(requestPath);
+ context.RouteData.Values["path"] = "existing-value";
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotNull(context.Handler);
- Assert.Equal("default", context.RouteData.Values["path"]);
- }
+ // Assert
+ Assert.NotNull(context.Handler);
+ Assert.Equal("default", context.RouteData.Values["path"]);
+ }
- [Theory]
- [InlineData("template/5")]
- [InlineData("template/{parameter:int}")]
- [InlineData("template/{parameter}")]
- [InlineData("template/{*parameter:int}")]
- [InlineData("template/{*parameter}")]
- public async Task TreeRouter_RouteAsync_RespectsOrder(string template)
- {
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, template);
+ [Theory]
+ [InlineData("template/5")]
+ [InlineData("template/{parameter:int}")]
+ [InlineData("template/{parameter}")]
+ [InlineData("template/{*parameter:int}")]
+ [InlineData("template/{*parameter}")]
+ public async Task TreeRouter_RouteAsync_RespectsOrder(string template)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, template);
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries with a lower relative order first to ensure that when
- // we try to route the request, the route with the higher relative order gets tried first.
- MapInboundEntry(builder, template, order: 1);
- MapInboundEntry(builder, template, order: 0);
+ // We setup the route entries with a lower relative order first to ensure that when
+ // we try to route the request, the route with the higher relative order gets tried first.
+ MapInboundEntry(builder, template, order: 1);
+ MapInboundEntry(builder, template, order: 0);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext("/template/5");
+ var context = CreateRouteContext("/template/5");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ }
- [Theory]
- [InlineData("template/{first:int}", "template/{second:int}")]
- [InlineData("template/{first}", "template/{second}")]
- [InlineData("template/{*first:int}", "template/{*second:int}")]
- [InlineData("template/{*first}", "template/{*second}")]
- public async Task TreeRouter_RouteAsync_EnsuresStableOrdering(string first, string second)
- {
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, first);
+ [Theory]
+ [InlineData("template/{first:int}", "template/{second:int}")]
+ [InlineData("template/{first}", "template/{second}")]
+ [InlineData("template/{*first:int}", "template/{*second:int}")]
+ [InlineData("template/{*first}", "template/{*second}")]
+ public async Task TreeRouter_RouteAsync_EnsuresStableOrdering(string first, string second)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, first);
- var builder = CreateBuilder();
+ var builder = CreateBuilder();
- // We setup the route entries with a lower relative template order first to ensure that when
- // we try to route the request, the route with the higher template order gets tried first.
- MapInboundEntry(builder, first);
- MapInboundEntry(builder, second);
+ // We setup the route entries with a lower relative template order first to ensure that when
+ // we try to route the request, the route with the higher template order gets tried first.
+ MapInboundEntry(builder, first);
+ MapInboundEntry(builder, second);
- var route = builder.Build();
+ var route = builder.Build();
- var context = CreateRouteContext("/template/5");
+ var context = CreateRouteContext("/template/5");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- }
+ // Assert
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
+ }
- [Theory]
- [InlineData("template/{parameter:int}", "/template/5", true)]
- [InlineData("template/{parameter:int?}", "/template/5", true)]
- [InlineData("template/{parameter:int?}", "/template", true)]
- [InlineData("template/{parameter:int?}", "/template/qwer", false)]
- public async Task TreeRouter_WithOptionalInlineConstraint(
- string template,
- string request,
- bool expectedResult)
- {
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, template);
+ [Theory]
+ [InlineData("template/{parameter:int}", "/template/5", true)]
+ [InlineData("template/{parameter:int?}", "/template/5", true)]
+ [InlineData("template/{parameter:int?}", "/template", true)]
+ [InlineData("template/{parameter:int?}", "/template/qwer", false)]
+ public async Task TreeRouter_WithOptionalInlineConstraint(
+ string template,
+ string request,
+ bool expectedResult)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, template);
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- var context = CreateRouteContext(request);
+ var context = CreateRouteContext(request);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- if (expectedResult)
- {
- Assert.NotNull(context.Handler);
- Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
- }
- else
- {
- Assert.Null(context.Handler);
- }
+ // Assert
+ if (expectedResult)
+ {
+ Assert.NotNull(context.Handler);
+ Assert.Equal(expectedRouteGroup, context.RouteData.Values["test_route_group"]);
}
-
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", "foo", "bar", null)]
- [InlineData("moo/{p1?}", "/moo/foo", "foo", null, null)]
- [InlineData("moo/{p1?}", "/moo", null, null, null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo", "foo", null, null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", "foo.", "bar", null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", "foo.moo", "bar", null)]
- [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", "foo", "bar", null)]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", "moo", "bar", null)]
- [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", "moo", null, null)]
- [InlineData("moo/.{p2?}", "/moo/.foo", null, "foo", null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/....", "..", ".", null)]
- [InlineData("moo/{p1}.{p2?}", "/moo/.bar", ".bar", null, null)]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
- [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
- [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
- public async Task TreeRouter_WithOptionalCompositeParameter_Valid(
- string template,
- string request,
- string p1,
- string p2,
- string p3)
+ else
{
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, template);
+ Assert.Null(context.Handler);
+ }
+ }
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.bar", "foo", "bar", null)]
+ [InlineData("moo/{p1?}", "/moo/foo", "foo", null, null)]
+ [InlineData("moo/{p1?}", "/moo", null, null, null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo", "foo", null, null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo..bar", "foo.", "bar", null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.moo.bar", "foo.moo", "bar", null)]
+ [InlineData("moo/{p1}.{p2}", "/moo/foo.bar", "foo", "bar", null)]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo.bar", "moo", "bar", null)]
+ [InlineData("moo/foo.{p1}.{p2?}", "/moo/foo.moo", "moo", null, null)]
+ [InlineData("moo/.{p2?}", "/moo/.foo", null, "foo", null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/....", "..", ".", null)]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.bar", ".bar", null, null)]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo", "foo", "moo", null)]
+ [InlineData("moo/{p1}.{p2}.{p3}.{p4?}", "/moo/foo.moo.bar", "foo", "moo", "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo.moo/bar", "foo", "moo", "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo/bar", "foo", null, "bar")]
+ [InlineData("{p1}.{p2?}/{p3}", "/.foo/bar", ".foo", null, "bar")]
+ public async Task TreeRouter_WithOptionalCompositeParameter_Valid(
+ string template,
+ string request,
+ string p1,
+ string p2,
+ string p3)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, template);
- var context = CreateRouteContext(request);
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- // Act
- await route.RouteAsync(context);
+ var context = CreateRouteContext(request);
- // Assert
- Assert.NotNull(context.Handler);
- if (p1 != null)
- {
- Assert.Equal(p1, context.RouteData.Values["p1"]);
- }
- if (p2 != null)
- {
- Assert.Equal(p2, context.RouteData.Values["p2"]);
- }
- if (p3 != null)
- {
- Assert.Equal(p3, context.RouteData.Values["p3"]);
- }
- }
+ // Act
+ await route.RouteAsync(context);
- [Theory]
- [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
- [InlineData("moo/{p1}.{p2?}", "/moo/.")]
- [InlineData("moo/{p1}.{p2}", "/foo.")]
- [InlineData("moo/{p1}.{p2}", "/foo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
- [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
- [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
- [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
- [InlineData("moo/.{p2?}", "/moo/.")]
- [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
- public async Task TreeRouter_WithOptionalCompositeParameter_Invalid(
- string template,
- string request)
+ // Assert
+ Assert.NotNull(context.Handler);
+ if (p1 != null)
+ {
+ Assert.Equal(p1, context.RouteData.Values["p1"]);
+ }
+ if (p2 != null)
{
- // Arrange
- var expectedRouteGroup = CreateRouteGroup(0, template);
+ Assert.Equal(p2, context.RouteData.Values["p2"]);
+ }
+ if (p3 != null)
+ {
+ Assert.Equal(p3, context.RouteData.Values["p3"]);
+ }
+ }
- var builder = CreateBuilder();
- MapInboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [InlineData("moo/{p1}.{p2?}", "/moo/foo.")]
+ [InlineData("moo/{p1}.{p2?}", "/moo/.")]
+ [InlineData("moo/{p1}.{p2}", "/foo.")]
+ [InlineData("moo/{p1}.{p2}", "/foo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo.moo.")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/bar.foo.moo")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo.bar")]
+ [InlineData("moo/foo.{p2}.{p3?}", "/moo/kungfoo.moo")]
+ [InlineData("moo/{p1}.{p2}.{p3?}", "/moo/foo")]
+ [InlineData("{p1}.{p2?}/{p3}", "/foo./bar")]
+ [InlineData("moo/.{p2?}", "/moo/.")]
+ [InlineData("{p1}.{p2}/{p3}", "/.foo/bar")]
+ public async Task TreeRouter_WithOptionalCompositeParameter_Invalid(
+ string template,
+ string request)
+ {
+ // Arrange
+ var expectedRouteGroup = CreateRouteGroup(0, template);
- var context = CreateRouteContext(request);
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, template);
+ var route = builder.Build();
- // Act
- await route.RouteAsync(context);
+ var context = CreateRouteContext(request);
- // Assert
- Assert.Null(context.Handler);
- }
+ // Act
+ await route.RouteAsync(context);
- [Theory]
- [InlineData("template", "{*url:alpha}", "/template?url=dingo&id=5")]
- [InlineData("{*url:alpha}", "{*url}", "/dingo?id=5")]
- [InlineData("{id}", "{*url}", "/5?url=dingo")]
- [InlineData("{id}", "{*url:alpha}", "/5?url=dingo")]
- [InlineData("{id:int}", "{id}", "/5?url=dingo")]
- [InlineData("{id}", "{id:alpha}/{url}", "/5?url=dingo")] // constraint doesn't match
- [InlineData("template/api/{*url}", "template/api", "/template/api/dingo?id=5")]
- [InlineData("template/api", "template/{*url}", "/template/api?url=dingo&id=5")]
- [InlineData("template/api", "template/api{id}location", "/template/api?url=dingo&id=5")]
- [InlineData("template/api{id}location", "template/{id:int}", "/template/api5location?url=dingo")]
- public void TreeRouter_GenerateLink(string firstTemplate, string secondTemplate, string expectedPath)
- {
- // Arrange
- var values = new Dictionary<string, object>
+ // Assert
+ Assert.Null(context.Handler);
+ }
+
+ [Theory]
+ [InlineData("template", "{*url:alpha}", "/template?url=dingo&id=5")]
+ [InlineData("{*url:alpha}", "{*url}", "/dingo?id=5")]
+ [InlineData("{id}", "{*url}", "/5?url=dingo")]
+ [InlineData("{id}", "{*url:alpha}", "/5?url=dingo")]
+ [InlineData("{id:int}", "{id}", "/5?url=dingo")]
+ [InlineData("{id}", "{id:alpha}/{url}", "/5?url=dingo")] // constraint doesn't match
+ [InlineData("template/api/{*url}", "template/api", "/template/api/dingo?id=5")]
+ [InlineData("template/api", "template/{*url}", "/template/api?url=dingo&id=5")]
+ [InlineData("template/api", "template/api{id}location", "/template/api?url=dingo&id=5")]
+ [InlineData("template/api{id}location", "template/{id:int}", "/template/api5location?url=dingo")]
+ public void TreeRouter_GenerateLink(string firstTemplate, string secondTemplate, string expectedPath)
+ {
+ // Arrange
+ var values = new Dictionary<string, object>
{
{"url", "dingo" },
{"id", 5 }
};
- var route = CreateTreeRouter(firstTemplate, secondTemplate);
- var context = CreateVirtualPathContext(
- values: values,
- ambientValues: null);
+ var route = CreateTreeRouter(firstTemplate, secondTemplate);
+ var context = CreateVirtualPathContext(
+ values: values,
+ ambientValues: null);
- // Act
- var result = route.GetVirtualPath(context);
+ // Act
+ var result = route.GetVirtualPath(context);
- // Assert
- Assert.NotNull(result);
- Assert.Equal(expectedPath, result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
-
- [Fact]
- public void TreeRouter_GenerateLink_LongerTemplateWithDefaultIsMoreSpecific()
- {
- // Arrange
- var firstTemplate = "template";
- var secondTemplate = "template/{parameter:int=1003}";
-
- var route = CreateTreeRouter(firstTemplate, secondTemplate);
- var context = CreateVirtualPathContext(
- values: null,
- ambientValues: null);
-
- // Act
- var result = route.GetVirtualPath(context);
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expectedPath, result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- // Assert
- Assert.NotNull(result);
- // The Binder binds to /template
- Assert.Equal("/template", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ [Fact]
+ public void TreeRouter_GenerateLink_LongerTemplateWithDefaultIsMoreSpecific()
+ {
+ // Arrange
+ var firstTemplate = "template";
+ var secondTemplate = "template/{parameter:int=1003}";
+
+ var route = CreateTreeRouter(firstTemplate, secondTemplate);
+ var context = CreateVirtualPathContext(
+ values: null,
+ ambientValues: null);
+
+ // Act
+ var result = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(result);
+ // The Binder binds to /template
+ Assert.Equal("/template", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- [Theory]
- [InlineData("template/{parameter:int=5}", "template", "/template/5")]
- [InlineData("template/{parameter}", "template", "/template/5")]
- [InlineData("template/{parameter}/{id}", "template/{parameter}", "/template/5/1234")]
- public void TreeRouter_GenerateLink_OrderingAgnostic(
- string firstTemplate,
- string secondTemplate,
- string expectedPath)
- {
- // Arrange
- var route = CreateTreeRouter(firstTemplate, secondTemplate);
- var parameter = 5;
- var id = 1234;
- var values = new Dictionary<string, object>
+ [Theory]
+ [InlineData("template/{parameter:int=5}", "template", "/template/5")]
+ [InlineData("template/{parameter}", "template", "/template/5")]
+ [InlineData("template/{parameter}/{id}", "template/{parameter}", "/template/5/1234")]
+ public void TreeRouter_GenerateLink_OrderingAgnostic(
+ string firstTemplate,
+ string secondTemplate,
+ string expectedPath)
+ {
+ // Arrange
+ var route = CreateTreeRouter(firstTemplate, secondTemplate);
+ var parameter = 5;
+ var id = 1234;
+ var values = new Dictionary<string, object>
{
{ nameof(parameter) , parameter},
{ nameof(id), id }
};
- var context = CreateVirtualPathContext(
- values: null,
- ambientValues: values);
-
- // Act
- var result = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(result);
- Assert.Equal(expectedPath, result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ var context = CreateVirtualPathContext(
+ values: null,
+ ambientValues: values);
+
+ // Act
+ var result = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expectedPath, result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- [Theory]
- [InlineData("template", "template/{parameter}", "/template/5")]
- [InlineData("template/{parameter}", "template/{parameter}/{id}", "/template/5/1234")]
- [InlineData("template", "template/{parameter:int=5}", "/template/5")]
- public void TreeRouter_GenerateLink_UseAvailableVariables(
- string firstTemplate,
- string secondTemplate,
- string expectedPath)
- {
- // Arrange
- var route = CreateTreeRouter(firstTemplate, secondTemplate);
- var parameter = 5;
- var id = 1234;
- var values = new Dictionary<string, object>
+ [Theory]
+ [InlineData("template", "template/{parameter}", "/template/5")]
+ [InlineData("template/{parameter}", "template/{parameter}/{id}", "/template/5/1234")]
+ [InlineData("template", "template/{parameter:int=5}", "/template/5")]
+ public void TreeRouter_GenerateLink_UseAvailableVariables(
+ string firstTemplate,
+ string secondTemplate,
+ string expectedPath)
+ {
+ // Arrange
+ var route = CreateTreeRouter(firstTemplate, secondTemplate);
+ var parameter = 5;
+ var id = 1234;
+ var values = new Dictionary<string, object>
{
{ nameof(parameter) , parameter},
{ nameof(id), id }
};
- var context = CreateVirtualPathContext(
- values: null,
- ambientValues: values);
+ var context = CreateVirtualPathContext(
+ values: null,
+ ambientValues: values);
+
+ // Act
+ var result = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expectedPath, result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- // Act
- var result = route.GetVirtualPath(context);
+ [Theory]
+ [InlineData("template/5", "template/{parameter:int}")]
+ [InlineData("template/5", "template/{parameter}")]
+ [InlineData("template/5", "template/{*parameter:int}")]
+ [InlineData("template/5", "template/{*parameter}")]
+ [InlineData("template/{parameter:int}", "template/{parameter}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter}", "template/{*parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter}")]
+ public void TreeRouter_GenerateLink_RespectsPrecedence(string firstTemplate, string secondTemplate)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Assert
- Assert.NotNull(result);
- Assert.Equal(expectedPath, result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // We setup the route entries in reverse order of precedence to ensure that when we
+ // try to generate a link, the route with a higher precedence gets tried first.
+ MapOutboundEntry(builder, secondTemplate);
+ MapOutboundEntry(builder, firstTemplate);
- [Theory]
- [InlineData("template/5", "template/{parameter:int}")]
- [InlineData("template/5", "template/{parameter}")]
- [InlineData("template/5", "template/{*parameter:int}")]
- [InlineData("template/5", "template/{*parameter}")]
- [InlineData("template/{parameter:int}", "template/{parameter}")]
- [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{parameter:int}", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{*parameter:int}")]
- [InlineData("template/{parameter}", "template/{*parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter}")]
- public void TreeRouter_GenerateLink_RespectsPrecedence(string firstTemplate, string secondTemplate)
- {
- // Arrange
- var builder = CreateBuilder();
+ var route = builder.Build();
- // We setup the route entries in reverse order of precedence to ensure that when we
- // try to generate a link, the route with a higher precedence gets tried first.
- MapOutboundEntry(builder, secondTemplate);
- MapOutboundEntry(builder, firstTemplate);
+ var context = CreateVirtualPathContext(values: null, ambientValues: new { parameter = 5 });
- var route = builder.Build();
+ // Act
+ var result = route.GetVirtualPath(context);
- var context = CreateVirtualPathContext(values: null, ambientValues: new { parameter = 5 });
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/template/5", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- // Act
- var result = route.GetVirtualPath(context);
+ [Theory]
+ [InlineData("template/{parameter:int}", "/template/5", 5)]
+ [InlineData("template/{parameter:int?}", "/template/5", 5)]
+ [InlineData("template/{parameter:int?}", "/template", null)]
+ [InlineData("template/{parameter:int?}", null, "asdf")]
+ [InlineData("template/{parameter:alpha?}", "/template/asdf", "asdf")]
+ [InlineData("template/{parameter:alpha?}", "/template", null)]
+ [InlineData("template/{parameter:int:range(1,20)?}", "/template", null)]
+ [InlineData("template/{parameter:int:range(1,20)?}", "/template/5", 5)]
+ [InlineData("template/{parameter:int:range(1,20)?}", null, 21)]
+ public void TreeRouter_GenerateLink_OptionalInlineParameter(
+ string template,
+ string expectedPath,
+ object parameter)
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, template);
+ var route = builder.Build();
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/template/5", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
+ VirtualPathContext context;
+ if (parameter != null)
+ {
+ context = CreateVirtualPathContext(values: null, ambientValues: new { parameter = parameter });
}
-
- [Theory]
- [InlineData("template/{parameter:int}", "/template/5", 5)]
- [InlineData("template/{parameter:int?}", "/template/5", 5)]
- [InlineData("template/{parameter:int?}", "/template", null)]
- [InlineData("template/{parameter:int?}", null, "asdf")]
- [InlineData("template/{parameter:alpha?}", "/template/asdf", "asdf")]
- [InlineData("template/{parameter:alpha?}", "/template", null)]
- [InlineData("template/{parameter:int:range(1,20)?}", "/template", null)]
- [InlineData("template/{parameter:int:range(1,20)?}", "/template/5", 5)]
- [InlineData("template/{parameter:int:range(1,20)?}", null, 21)]
- public void TreeRouter_GenerateLink_OptionalInlineParameter(
- string template,
- string expectedPath,
- object parameter)
+ else
{
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, template);
- var route = builder.Build();
-
- VirtualPathContext context;
- if (parameter != null)
- {
- context = CreateVirtualPathContext(values: null, ambientValues: new { parameter = parameter });
- }
- else
- {
- context = CreateVirtualPathContext(values: null, ambientValues: null);
- }
+ context = CreateVirtualPathContext(values: null, ambientValues: null);
+ }
- // Act
- var result = route.GetVirtualPath(context);
+ // Act
+ var result = route.GetVirtualPath(context);
- // Assert
- if (expectedPath == null)
- {
- Assert.Null(result);
- }
- else
- {
- Assert.NotNull(result);
- Assert.Equal(expectedPath, result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // Assert
+ if (expectedPath == null)
+ {
+ Assert.Null(result);
}
-
- [Theory]
- [InlineData("template/5", "template/{parameter:int}")]
- [InlineData("template/5", "template/{parameter}")]
- [InlineData("template/5", "template/{*parameter:int}")]
- [InlineData("template/5", "template/{*parameter}")]
- [InlineData("template/{parameter:int}", "template/{parameter}")]
- [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
- [InlineData("template/{parameter:int}", "template/{*parameter}")]
- [InlineData("template/{parameter}", "template/{*parameter:int}")]
- [InlineData("template/{parameter}", "template/{*parameter}")]
- [InlineData("template/{*parameter:int}", "template/{*parameter}")]
- public void TreeRouter_GenerateLink_RespectsOrderOverPrecedence(string firstTemplate, string secondTemplate)
+ else
{
- // Arrange
- var builder = CreateBuilder();
+ Assert.NotNull(result);
+ Assert.Equal(expectedPath, result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
+ }
- // We setup the route entries with a lower relative order and higher relative precedence
- // first to ensure that when we try to generate a link, the route with the higher
- // relative order gets tried first.
- MapOutboundEntry(builder, firstTemplate, order: 1);
- MapOutboundEntry(builder, secondTemplate, order: 0);
+ [Theory]
+ [InlineData("template/5", "template/{parameter:int}")]
+ [InlineData("template/5", "template/{parameter}")]
+ [InlineData("template/5", "template/{*parameter:int}")]
+ [InlineData("template/5", "template/{*parameter}")]
+ [InlineData("template/{parameter:int}", "template/{parameter}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter:int}", "template/{*parameter}")]
+ [InlineData("template/{parameter}", "template/{*parameter:int}")]
+ [InlineData("template/{parameter}", "template/{*parameter}")]
+ [InlineData("template/{*parameter:int}", "template/{*parameter}")]
+ public void TreeRouter_GenerateLink_RespectsOrderOverPrecedence(string firstTemplate, string secondTemplate)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- var route = builder.Build();
+ // We setup the route entries with a lower relative order and higher relative precedence
+ // first to ensure that when we try to generate a link, the route with the higher
+ // relative order gets tried first.
+ MapOutboundEntry(builder, firstTemplate, order: 1);
+ MapOutboundEntry(builder, secondTemplate, order: 0);
- var context = CreateVirtualPathContext(null, ambientValues: new { parameter = 5 });
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(null, ambientValues: new { parameter = 5 });
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/template/5", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Theory]
- [InlineData("template/5", "template/5")]
- [InlineData("template/{first:int}", "template/{second:int}")]
- [InlineData("template/{first}", "template/{second}")]
- [InlineData("template/{*first:int}", "template/{*second:int}")]
- [InlineData("template/{*first}", "template/{*second}")]
- public void TreeRouter_GenerateLink_RespectsOrder(string firstTemplate, string secondTemplate)
- {
- // Arrange
- var builder = CreateBuilder();
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/template/5", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- // We setup the route entries with a lower relative order first to ensure that when
- // we try to generate a link, the route with the higher relative order gets tried first.
- MapOutboundEntry(builder, firstTemplate, requiredValues: null, order: 1);
- MapOutboundEntry(builder, secondTemplate, requiredValues: null, order: 0);
+ [Theory]
+ [InlineData("template/5", "template/5")]
+ [InlineData("template/{first:int}", "template/{second:int}")]
+ [InlineData("template/{first}", "template/{second}")]
+ [InlineData("template/{*first:int}", "template/{*second:int}")]
+ [InlineData("template/{*first}", "template/{*second}")]
+ public void TreeRouter_GenerateLink_RespectsOrder(string firstTemplate, string secondTemplate)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- var route = builder.Build();
+ // We setup the route entries with a lower relative order first to ensure that when
+ // we try to generate a link, the route with the higher relative order gets tried first.
+ MapOutboundEntry(builder, firstTemplate, requiredValues: null, order: 1);
+ MapOutboundEntry(builder, secondTemplate, requiredValues: null, order: 0);
- var context = CreateVirtualPathContext(values: null, ambientValues: new { first = 5, second = 5 });
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(values: null, ambientValues: new { first = 5, second = 5 });
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/template/5", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Theory]
- [InlineData("first/5", "second/5")]
- [InlineData("first/{first:int}", "second/{second:int}")]
- [InlineData("first/{first}", "second/{second}")]
- [InlineData("first/{*first:int}", "second/{*second:int}")]
- [InlineData("first/{*first}", "second/{*second}")]
- public void TreeRouter_GenerateLink_EnsuresStableOrder(string firstTemplate, string secondTemplate)
- {
- // Arrange
- var builder = CreateBuilder();
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/template/5", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- // We setup the route entries with a lower relative template order first to ensure that when
- // we try to generate a link, the route with the higher template order gets tried first.
- MapOutboundEntry(builder, secondTemplate, requiredValues: null, order: 0);
- MapOutboundEntry(builder, firstTemplate, requiredValues: null, order: 0);
+ [Theory]
+ [InlineData("first/5", "second/5")]
+ [InlineData("first/{first:int}", "second/{second:int}")]
+ [InlineData("first/{first}", "second/{second}")]
+ [InlineData("first/{*first:int}", "second/{*second:int}")]
+ [InlineData("first/{*first}", "second/{*second}")]
+ public void TreeRouter_GenerateLink_EnsuresStableOrder(string firstTemplate, string secondTemplate)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- var route = builder.Build();
+ // We setup the route entries with a lower relative template order first to ensure that when
+ // we try to generate a link, the route with the higher template order gets tried first.
+ MapOutboundEntry(builder, secondTemplate, requiredValues: null, order: 0);
+ MapOutboundEntry(builder, firstTemplate, requiredValues: null, order: 0);
- var context = CreateVirtualPathContext(values: null, ambientValues: new { first = 5, second = 5 });
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(values: null, ambientValues: new { first = 5, second = 5 });
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/first/5", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_CreatesLinksForRoutesWithIntermediateDefaultRouteValues()
- {
- // Arrange
- var builder = CreateBuilder();
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/first/5", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- MapOutboundEntry(builder, template: "a/b/{parameter3=3}/d", requiredValues: null, order: 0);
+ [Fact]
+ public void TreeRouter_GenerateLink_CreatesLinksForRoutesWithIntermediateDefaultRouteValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- var route = builder.Build();
+ MapOutboundEntry(builder, template: "a/b/{parameter3=3}/d", requiredValues: null, order: 0);
- var context = CreateVirtualPathContext(values: null, ambientValues: null);
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(values: null, ambientValues: null);
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/a/b/3/d", result.VirtualPath);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/a/b/3/d", result.VirtualPath);
+ }
- [Fact]
- public void TreeRouter_GeneratesLink_ForMultipleNamedEntriesWithTheSameTemplate()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "Template", name: "NamedEntry", order: 1);
- MapOutboundEntry(builder, "TEMPLATE", name: "NamedEntry", order: 2);
+ [Fact]
+ public void TreeRouter_GeneratesLink_ForMultipleNamedEntriesWithTheSameTemplate()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Act & Assert (does not throw)
- builder.Build();
- }
+ MapOutboundEntry(builder, "Template", name: "NamedEntry", order: 1);
+ MapOutboundEntry(builder, "TEMPLATE", name: "NamedEntry", order: 2);
- [Fact]
- public void TreeRouter_GenerateLink_WithName()
- {
- // Arrange
- var builder = CreateBuilder();
+ // Act & Assert (does not throw)
+ builder.Build();
+ }
- // The named route has a lower order which will ensure that we aren't trying the route as
- // if it were an unnamed route.
- MapOutboundEntry(builder, "named", requiredValues: null, order: 1, name: "NamedRoute");
- MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
+ [Fact]
+ public void TreeRouter_GenerateLink_WithName()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- var route = builder.Build();
+ // The named route has a lower order which will ensure that we aren't trying the route as
+ // if it were an unnamed route.
+ MapOutboundEntry(builder, "named", requiredValues: null, order: 1, name: "NamedRoute");
+ MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
- var context = CreateVirtualPathContext(values: null, ambientValues: null, name: "NamedRoute");
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(values: null, ambientValues: null, name: "NamedRoute");
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/named", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_DoesNotGenerateLink_IfThereIsNoRouteForAGivenName()
- {
- // Arrange
- var builder = CreateBuilder();
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/named", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- // The named route has a lower order which will ensure that we aren't trying the route as
- // if it were an unnamed route.
- MapOutboundEntry(builder, "named", requiredValues: null, order: 1, name: "NamedRoute");
+ [Fact]
+ public void TreeRouter_DoesNotGenerateLink_IfThereIsNoRouteForAGivenName()
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
- MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
+ // The named route has a lower order which will ensure that we aren't trying the route as
+ // if it were an unnamed route.
+ MapOutboundEntry(builder, "named", requiredValues: null, order: 1, name: "NamedRoute");
- var route = builder.Build();
+ // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
+ MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
- var context = CreateVirtualPathContext(values: null, ambientValues: null, name: "NonExistingNamedRoute");
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(values: null, ambientValues: null, name: "NonExistingNamedRoute");
- // Assert
- Assert.Null(result);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Theory]
- [InlineData("template/{parameter:int}", null)]
- [InlineData("template/{parameter:int}", "NaN")]
- [InlineData("template/{parameter}", null)]
- [InlineData("template/{*parameter:int}", null)]
- [InlineData("template/{*parameter:int}", "NaN")]
- public void TreeRouter_DoesNotGenerateLink_IfValuesDoNotMatchNamedEntry(string template, string value)
- {
- // Arrange
- var builder = CreateBuilder();
+ // Assert
+ Assert.Null(result);
+ }
- // The named route has a lower order which will ensure that we aren't trying the route as
- // if it were an unnamed route.
- MapOutboundEntry(builder, template, requiredValues: null, order: 1, name: "NamedRoute");
+ [Theory]
+ [InlineData("template/{parameter:int}", null)]
+ [InlineData("template/{parameter:int}", "NaN")]
+ [InlineData("template/{parameter}", null)]
+ [InlineData("template/{*parameter:int}", null)]
+ [InlineData("template/{*parameter:int}", "NaN")]
+ public void TreeRouter_DoesNotGenerateLink_IfValuesDoNotMatchNamedEntry(string template, string value)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
- MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
+ // The named route has a lower order which will ensure that we aren't trying the route as
+ // if it were an unnamed route.
+ MapOutboundEntry(builder, template, requiredValues: null, order: 1, name: "NamedRoute");
- var route = builder.Build();
+ // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
+ MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
- var ambientValues = value == null ? null : new { parameter = value };
- var context = CreateVirtualPathContext(values: null, ambientValues: ambientValues, name: "NamedRoute");
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var ambientValues = value == null ? null : new { parameter = value };
+ var context = CreateVirtualPathContext(values: null, ambientValues: ambientValues, name: "NamedRoute");
- // Assert
- Assert.Null(result);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Theory]
- [InlineData("template/{parameter:int}", "5")]
- [InlineData("template/{parameter}", "5")]
- [InlineData("template/{*parameter:int}", "5")]
- [InlineData("template/{*parameter}", "5")]
- public void TreeRouter_GeneratesLink_IfValuesMatchNamedEntry(string template, string value)
- {
- // Arrange
- var builder = CreateBuilder();
+ // Assert
+ Assert.Null(result);
+ }
- // The named route has a lower order which will ensure that we aren't trying the route as
- // if it were an unnamed route.
- MapOutboundEntry(builder, template, requiredValues: null, order: 1, name: "NamedRoute");
+ [Theory]
+ [InlineData("template/{parameter:int}", "5")]
+ [InlineData("template/{parameter}", "5")]
+ [InlineData("template/{*parameter:int}", "5")]
+ [InlineData("template/{*parameter}", "5")]
+ public void TreeRouter_GeneratesLink_IfValuesMatchNamedEntry(string template, string value)
+ {
+ // Arrange
+ var builder = CreateBuilder();
- // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
- MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
+ // The named route has a lower order which will ensure that we aren't trying the route as
+ // if it were an unnamed route.
+ MapOutboundEntry(builder, template, requiredValues: null, order: 1, name: "NamedRoute");
- var route = builder.Build();
+ // Add an unnamed entry to ensure we don't fall back to generating a link for an unnamed route.
+ MapOutboundEntry(builder, "unnamed", requiredValues: null, order: 0);
- var ambientValues = value == null ? null : new { parameter = value };
- var context = CreateVirtualPathContext(values: null, ambientValues: ambientValues, name: "NamedRoute");
+ var route = builder.Build();
- // Act
- var result = route.GetVirtualPath(context);
+ var ambientValues = value == null ? null : new { parameter = value };
+ var context = CreateVirtualPathContext(values: null, ambientValues: ambientValues, name: "NamedRoute");
- // Assert
- Assert.NotNull(result);
- Assert.Equal("/template/5", result.VirtualPath);
- Assert.Same(route, result.Router);
- Assert.Empty(result.DataTokens);
- }
+ // Act
+ var result = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_NoRequiredValues()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal("/template/5", result.VirtualPath);
+ Assert.Same(route, result.Router);
+ Assert.Empty(result.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { });
+ [Fact]
+ public void TreeRouter_GenerateLink_NoRequiredValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_NoMatch()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { action = "Details", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_NoMatch()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { action = "Details", controller = "Store" });
+ var route = builder.Build();
- // Act
- var path = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
- // Assert
- Assert.Null(path);
- }
+ // Act
+ var path = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithAmbientValues()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.Null(path);
+ }
- var context = CreateVirtualPathContext(new { }, new { action = "Index", controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithAmbientValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { }, new { action = "Index", controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_HasTwoOptionalParametersWithoutValues()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "Customers/SeparatePageModels/{handler?}/{id?}", new { page = "/Customers/SeparatePageModels/Index" });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { page = "/Customers/SeparatePageModels/Index" }, new { page = "/Customers/SeparatePageModels/Edit", id = "17" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_HasTwoOptionalParametersWithoutValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "Customers/SeparatePageModels/{handler?}/{id?}", new { page = "/Customers/SeparatePageModels/Index" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { page = "/Customers/SeparatePageModels/Index" }, new { page = "/Customers/SeparatePageModels/Edit", id = "17" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Customers/SeparatePageModels", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithParameters()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store/{action}", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Customers/SeparatePageModels", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithParameters()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store/{action}", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store/Index", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithMoreParameters()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder,
- "api/{area}/dosomething/{controller}/{action}",
- new { action = "Index", controller = "Store", area = "AwesomeCo" });
-
- var route = builder.Build();
-
- var context = CreateVirtualPathContext(
- new { action = "Index", controller = "Store" },
- new { area = "AwesomeCo" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/AwesomeCo/dosomething/Store/Index", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store/Index", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithDefault()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store/{action=Index}", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithMoreParameters()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder,
+ "api/{area}/dosomething/{controller}/{action}",
+ new { action = "Index", controller = "Store", area = "AwesomeCo" });
+
+ var route = builder.Build();
+
+ var context = CreateVirtualPathContext(
+ new { action = "Index", controller = "Store" },
+ new { area = "AwesomeCo" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/AwesomeCo/dosomething/Store/Index", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithDefault()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store/{action=Index}", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithConstraint()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store/{action}/{id:int}", new { action = "Index", controller = "Store" });
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var route = builder.Build();
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithConstraint()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store/{action}/{id:int}", new { action = "Index", controller = "Store" });
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Store", id = 5 });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Store", id = 5 });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store/Index/5", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_NoMatch_WithConstraint()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store/{action}/{id:int}", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store/Index/5", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var next = new StubRouter();
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Store", id = "heyyyy" });
+ [Fact]
+ public void TreeRouter_GenerateLink_NoMatch_WithConstraint()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store/{action}/{id:int}", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var path = route.GetVirtualPath(context);
+ var next = new StubRouter();
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Store", id = "heyyyy" });
- // Assert
- Assert.Null(path);
- }
+ // Act
+ var path = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithMixedAmbientValues()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.Null(path);
+ }
- var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithMixedAmbientValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_Match_WithQueryString()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
- var route = builder.Build();
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateVirtualPathContext(new { action = "Index", id = 5 }, new { controller = "Store" });
+ [Fact]
+ public void TreeRouter_GenerateLink_Match_WithQueryString()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", id = 5 }, new { controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api/Store?id=5", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_RejectedByFirstRoute()
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
- MapOutboundEntry(builder, "api2/{controller}", new { action = "Index", controller = "Blog" });
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api/Store?id=5", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var route = builder.Build();
+ [Fact]
+ public void TreeRouter_GenerateLink_RejectedByFirstRoute()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, "api/Store", new { action = "Index", controller = "Store" });
+ MapOutboundEntry(builder, "api2/{controller}", new { action = "Index", controller = "Blog" });
- var context = CreateVirtualPathContext(new { action = "Index", controller = "Blog" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { action = "Index", controller = "Blog" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/api2/Blog", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_ToArea()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
- entry1.Precedence = 2;
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/api2/Blog", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
- entry2.Precedence = 1;
+ [Fact]
+ public void TreeRouter_GenerateLink_ToArea()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
+ entry1.Precedence = 2;
- var route = builder.Build();
+ var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
+ entry2.Precedence = 1;
- var context = CreateVirtualPathContext(new { area = "Help", action = "Edit", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { area = "Help", action = "Edit", controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_ToArea_PredecedenceReversed()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
- entry1.Precedence = 1;
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
- entry2.Precedence = 2;
+ [Fact]
+ public void TreeRouter_GenerateLink_ToArea_PredecedenceReversed()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
+ entry1.Precedence = 1;
- var route = builder.Build();
+ var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
+ entry2.Precedence = 2;
- var context = CreateVirtualPathContext(new { area = "Help", action = "Edit", controller = "Store" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(new { area = "Help", action = "Edit", controller = "Store" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_ToArea_WithAmbientValues()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
- entry1.Precedence = 2;
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
- entry2.Precedence = 1;
+ [Fact]
+ public void TreeRouter_GenerateLink_ToArea_WithAmbientValues()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
+ entry1.Precedence = 2;
- var route = builder.Build();
+ var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
+ entry2.Precedence = 1;
- var context = CreateVirtualPathContext(
- values: new { action = "Edit", controller = "Store" },
- ambientValues: new { area = "Help" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(
+ values: new { action = "Edit", controller = "Store" },
+ ambientValues: new { area = "Help" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- [Fact]
- public void TreeRouter_GenerateLink_OutOfArea_IgnoresAmbientValue()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
- entry1.Precedence = 2;
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
- entry2.Precedence = 1;
+ [Fact]
+ public void TreeRouter_GenerateLink_OutOfArea_IgnoresAmbientValue()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry1 = MapOutboundEntry(builder, "Help/Store", new { area = "Help", action = "Edit", controller = "Store" });
+ entry1.Precedence = 2;
- var route = builder.Build();
+ var entry2 = MapOutboundEntry(builder, "Store", new { area = (string)null, action = "Edit", controller = "Store" });
+ entry2.Precedence = 1;
- var context = CreateVirtualPathContext(
- values: new { action = "Edit", controller = "Store" },
- ambientValues: new { area = "Blog" });
+ var route = builder.Build();
- // Act
- var pathData = route.GetVirtualPath(context);
+ var context = CreateVirtualPathContext(
+ values: new { action = "Edit", controller = "Store" },
+ ambientValues: new { area = "Blog" });
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Act
+ var pathData = route.GetVirtualPath(context);
- public static IEnumerable<object[]> OptionalParamValues
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
+
+ public static IEnumerable<object[]> OptionalParamValues
+ {
+ get
{
- get
+ return new object[][]
{
- return new object[][]
- {
// defaults
// ambient values
// values
@@ -1667,459 +1667,458 @@ namespace Microsoft.AspNetCore.Routing.Tree
new {val3 = "someval3" },
"/Test/someval1.someval2?val3=someval3",
},
- };
- }
+ };
}
+ }
- [Theory]
- [MemberData(nameof(OptionalParamValues))]
- public void TreeRouter_GenerateLink_Match_WithOptionalParameters(
- string template,
- object ambientValues,
- object values,
- string expected)
- {
- // Arrange
- var builder = CreateBuilder();
- MapOutboundEntry(builder, template);
- var route = builder.Build();
+ [Theory]
+ [MemberData(nameof(OptionalParamValues))]
+ public void TreeRouter_GenerateLink_Match_WithOptionalParameters(
+ string template,
+ object ambientValues,
+ object values,
+ string expected)
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, template);
+ var route = builder.Build();
- var context = CreateVirtualPathContext(values, ambientValues);
+ var context = CreateVirtualPathContext(values, ambientValues);
- // Act
- var pathData = route.GetVirtualPath(context);
+ // Act
+ var pathData = route.GetVirtualPath(context);
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal(expected, pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal(expected, pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public async Task TreeRouter_ReplacesExistingRouteValues_IfNotNull()
- {
- // Arrange
- var builder = CreateBuilder();
- MapInboundEntry(builder, "Foo/{*path}");
- var route = builder.Build();
+ [Fact]
+ public async Task TreeRouter_ReplacesExistingRouteValues_IfNotNull()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, "Foo/{*path}");
+ var route = builder.Build();
- var context = CreateRouteContext("/Foo/Bar");
+ var context = CreateRouteContext("/Foo/Bar");
- var originalRouteData = context.RouteData;
- originalRouteData.Values.Add("path", "default");
+ var originalRouteData = context.RouteData;
+ originalRouteData.Values.Add("path", "default");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal("Bar", context.RouteData.Values["path"]);
- }
+ // Assert
+ Assert.Equal("Bar", context.RouteData.Values["path"]);
+ }
- [Fact]
- public async Task TreeRouter_DoesNotReplaceExistingRouteValues_IfNull()
- {
- // Arrange
- var builder = CreateBuilder();
- MapInboundEntry(builder, "Foo/{*path}");
- var route = builder.Build();
+ [Fact]
+ public async Task TreeRouter_DoesNotReplaceExistingRouteValues_IfNull()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, "Foo/{*path}");
+ var route = builder.Build();
- var context = CreateRouteContext("/Foo/");
+ var context = CreateRouteContext("/Foo/");
- var originalRouteData = context.RouteData;
- originalRouteData.Values.Add("path", "default");
+ var originalRouteData = context.RouteData;
+ originalRouteData.Values.Add("path", "default");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal("default", context.RouteData.Values["path"]);
- }
+ // Assert
+ Assert.Equal("default", context.RouteData.Values["path"]);
+ }
- [Fact]
- public async Task TreeRouter_SnapshotsRouteData()
- {
- // Arrange
- RouteValueDictionary nestedValues = null;
- List<IRouter> nestedRouters = null;
-
- var next = new Mock<IRouter>();
- next
- .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(c =>
- {
- nestedValues = new RouteValueDictionary(c.RouteData.Values);
- nestedRouters = new List<IRouter>(c.RouteData.Routers);
- c.Handler = null; // Not a match
+ [Fact]
+ public async Task TreeRouter_SnapshotsRouteData()
+ {
+ // Arrange
+ RouteValueDictionary nestedValues = null;
+ List<IRouter> nestedRouters = null;
+
+ var next = new Mock<IRouter>();
+ next
+ .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(c =>
+ {
+ nestedValues = new RouteValueDictionary(c.RouteData.Values);
+ nestedRouters = new List<IRouter>(c.RouteData.Routers);
+ c.Handler = null; // Not a match
})
- .Returns(Task.CompletedTask);
+ .Returns(Task.CompletedTask);
- var builder = CreateBuilder();
- MapInboundEntry(builder, "api/Store", handler: next.Object);
- var route = builder.Build();
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, "api/Store", handler: next.Object);
+ var route = builder.Build();
- var context = CreateRouteContext("/api/Store");
+ var context = CreateRouteContext("/api/Store");
- var routeData = context.RouteData;
- routeData.Values.Add("action", "Index");
+ var routeData = context.RouteData;
+ routeData.Values.Add("action", "Index");
- var originalValues = new RouteValueDictionary(context.RouteData.Values);
+ var originalValues = new RouteValueDictionary(context.RouteData.Values);
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.Equal(originalValues, context.RouteData.Values);
- Assert.NotEqual(nestedValues, context.RouteData.Values);
- }
+ // Assert
+ Assert.Equal(originalValues, context.RouteData.Values);
+ Assert.NotEqual(nestedValues, context.RouteData.Values);
+ }
- [Fact]
- public async Task TreeRouter_SnapshotsRouteData_ResetsWhenNotMatched()
- {
- // Arrange
- RouteValueDictionary nestedValues = null;
- List<IRouter> nestedRouters = null;
-
- var next = new Mock<IRouter>();
- next
- .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(c =>
- {
- nestedValues = new RouteValueDictionary(c.RouteData.Values);
- nestedRouters = new List<IRouter>(c.RouteData.Routers);
- c.Handler = null; // Not a match
+ [Fact]
+ public async Task TreeRouter_SnapshotsRouteData_ResetsWhenNotMatched()
+ {
+ // Arrange
+ RouteValueDictionary nestedValues = null;
+ List<IRouter> nestedRouters = null;
+
+ var next = new Mock<IRouter>();
+ next
+ .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(c =>
+ {
+ nestedValues = new RouteValueDictionary(c.RouteData.Values);
+ nestedRouters = new List<IRouter>(c.RouteData.Routers);
+ c.Handler = null; // Not a match
})
- .Returns(Task.CompletedTask);
+ .Returns(Task.CompletedTask);
- var builder = CreateBuilder();
- MapInboundEntry(builder, "api/Store", handler: next.Object);
- var route = builder.Build();
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, "api/Store", handler: next.Object);
+ var route = builder.Build();
- var context = CreateRouteContext("/api/Store");
+ var context = CreateRouteContext("/api/Store");
- context.RouteData.Values.Add("action", "Index");
+ context.RouteData.Values.Add("action", "Index");
- // Act
- await route.RouteAsync(context);
+ // Act
+ await route.RouteAsync(context);
- // Assert
- Assert.NotEqual(nestedValues, context.RouteData.Values);
+ // Assert
+ Assert.NotEqual(nestedValues, context.RouteData.Values);
- // The new routedata is a copy
- Assert.Equal("Index", context.RouteData.Values["action"]);
- Assert.Equal("Index", nestedValues["action"]);
- Assert.DoesNotContain(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
- Assert.Single(nestedValues, kvp => kvp.Key == "test_route_group");
+ // The new routedata is a copy
+ Assert.Equal("Index", context.RouteData.Values["action"]);
+ Assert.Equal("Index", nestedValues["action"]);
+ Assert.DoesNotContain(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
+ Assert.Single(nestedValues, kvp => kvp.Key == "test_route_group");
- Assert.Empty(context.RouteData.Routers);
+ Assert.Empty(context.RouteData.Routers);
- Assert.Single(nestedRouters);
- Assert.Equal(next.Object.GetType(), nestedRouters[0].GetType());
- }
+ Assert.Single(nestedRouters);
+ Assert.Equal(next.Object.GetType(), nestedRouters[0].GetType());
+ }
- [Fact]
- public async Task TreeRouter_SnapshotsRouteData_ResetsWhenThrows()
- {
- // Arrange
- RouteValueDictionary nestedValues = null;
- List<IRouter> nestedRouters = null;
-
- var next = new Mock<IRouter>();
- next
- .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(c =>
- {
- nestedValues = new RouteValueDictionary(c.RouteData.Values);
- nestedRouters = new List<IRouter>(c.RouteData.Routers);
- throw new Exception();
- })
- .Returns(Task.CompletedTask);
+ [Fact]
+ public async Task TreeRouter_SnapshotsRouteData_ResetsWhenThrows()
+ {
+ // Arrange
+ RouteValueDictionary nestedValues = null;
+ List<IRouter> nestedRouters = null;
+
+ var next = new Mock<IRouter>();
+ next
+ .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(c =>
+ {
+ nestedValues = new RouteValueDictionary(c.RouteData.Values);
+ nestedRouters = new List<IRouter>(c.RouteData.Routers);
+ throw new Exception();
+ })
+ .Returns(Task.CompletedTask);
- var builder = CreateBuilder();
- MapInboundEntry(builder, "api/Store", handler: next.Object);
- var route = builder.Build();
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, "api/Store", handler: next.Object);
+ var route = builder.Build();
- var context = CreateRouteContext("/api/Store");
- context.RouteData.Values.Add("action", "Index");
+ var context = CreateRouteContext("/api/Store");
+ context.RouteData.Values.Add("action", "Index");
- // Act
- await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
+ // Act
+ await Assert.ThrowsAsync<Exception>(() => route.RouteAsync(context));
- // Assert
- Assert.NotEqual(nestedValues, context.RouteData.Values);
+ // Assert
+ Assert.NotEqual(nestedValues, context.RouteData.Values);
- Assert.Equal("Index", context.RouteData.Values["action"]);
- Assert.Equal("Index", nestedValues["action"]);
- Assert.DoesNotContain(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
- Assert.Single(nestedValues, kvp => kvp.Key == "test_route_group");
+ Assert.Equal("Index", context.RouteData.Values["action"]);
+ Assert.Equal("Index", nestedValues["action"]);
+ Assert.DoesNotContain(context.RouteData.Values, kvp => kvp.Key == "test_route_group");
+ Assert.Single(nestedValues, kvp => kvp.Key == "test_route_group");
- Assert.Empty(context.RouteData.Routers);
+ Assert.Empty(context.RouteData.Routers);
- Assert.Single(nestedRouters);
- Assert.Equal(next.Object.GetType(), nestedRouters[0].GetType());
- }
+ Assert.Single(nestedRouters);
+ Assert.Equal(next.Object.GetType(), nestedRouters[0].GetType());
+ }
- [Fact]
- public async Task TreeRouter_SnapshotsRouteData_ResetsBeforeMatchingEachRouteEntry()
- {
- // This test replicates a scenario raised as issue https://github.com/aspnet/Routing/issues/394
- // The RouteValueDictionary entries populated while matching route entries should not be left
- // in place if the route entry turns out not to match, because that would leak unwanted state
- // to subsequent route entries and might cause "An element with the key ... already exists"
- // exceptions.
-
- // Arrange
- RouteValueDictionary nestedValues = null;
- var next = new Mock<IRouter>();
- next
- .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
- .Callback<RouteContext>(c =>
- {
- nestedValues = new RouteValueDictionary(c.RouteData.Values);
- c.Handler = NullHandler;
- })
- .Returns(Task.CompletedTask);
+ [Fact]
+ public async Task TreeRouter_SnapshotsRouteData_ResetsBeforeMatchingEachRouteEntry()
+ {
+ // This test replicates a scenario raised as issue https://github.com/aspnet/Routing/issues/394
+ // The RouteValueDictionary entries populated while matching route entries should not be left
+ // in place if the route entry turns out not to match, because that would leak unwanted state
+ // to subsequent route entries and might cause "An element with the key ... already exists"
+ // exceptions.
+
+ // Arrange
+ RouteValueDictionary nestedValues = null;
+ var next = new Mock<IRouter>();
+ next
+ .Setup(r => r.RouteAsync(It.IsAny<RouteContext>()))
+ .Callback<RouteContext>(c =>
+ {
+ nestedValues = new RouteValueDictionary(c.RouteData.Values);
+ c.Handler = NullHandler;
+ })
+ .Returns(Task.CompletedTask);
+
+ var builder = CreateBuilder();
+ MapInboundEntry(builder, "cat_{category1}/prod1_{product}"); // Matches on first segment but not on second
+ MapInboundEntry(builder, "cat_{category2}/prod2_{product}", handler: next.Object);
+ var route = builder.Build();
+
+ var context = CreateRouteContext("/cat_examplecategory/prod2_exampleproduct");
+
+ // Act
+ await route.RouteAsync(context);
+
+ // Assert
+ Assert.NotNull(nestedValues);
+ Assert.Equal("examplecategory", nestedValues["category2"]);
+ Assert.Equal("exampleproduct", nestedValues["product"]);
+ Assert.DoesNotContain(nestedValues, kvp => kvp.Key == "category1");
+ }
- var builder = CreateBuilder();
- MapInboundEntry(builder, "cat_{category1}/prod1_{product}"); // Matches on first segment but not on second
- MapInboundEntry(builder, "cat_{category2}/prod2_{product}", handler: next.Object);
- var route = builder.Build();
+ [Fact]
+ public void TreeRouter_GenerateLink_MatchesNullRequiredValue_WithNullRequestValueString()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry = MapOutboundEntry(
+ builder,
+ "Help/Store",
+ requiredValues: new { area = (string)null, action = "Edit", controller = "Store" });
+ var route = builder.Build();
+ var context = CreateVirtualPathContext(new { area = (string)null, action = "Edit", controller = "Store" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- var context = CreateRouteContext("/cat_examplecategory/prod2_exampleproduct");
+ [Fact]
+ public void TreeRouter_GenerateLink_MatchesNullRequiredValue_WithEmptyRequestValueString()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry = MapOutboundEntry(
+ builder,
+ "Help/Store",
+ requiredValues: new { area = (string)null, action = "Edit", controller = "Store" });
+ var route = builder.Build();
+ var context = CreateVirtualPathContext(new { area = "", action = "Edit", controller = "Store" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Act
- await route.RouteAsync(context);
+ [Fact]
+ public void TreeRouter_GenerateLink_MatchesEmptyStringRequiredValue_WithNullRequestValueString()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry = MapOutboundEntry(
+ builder,
+ "Help/Store",
+ requiredValues: new { foo = "", action = "Edit", controller = "Store" });
+ var route = builder.Build();
+ var context = CreateVirtualPathContext(new { foo = (string)null, action = "Edit", controller = "Store" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- // Assert
- Assert.NotNull(nestedValues);
- Assert.Equal("examplecategory", nestedValues["category2"]);
- Assert.Equal("exampleproduct", nestedValues["product"]);
- Assert.DoesNotContain(nestedValues, kvp => kvp.Key == "category1");
- }
+ [Fact]
+ public void TreeRouter_GenerateLink_MatchesEmptyStringRequiredValue_WithEmptyRequestValueString()
+ {
+ // Arrange
+ var builder = CreateBuilder();
+ var entry = MapOutboundEntry(
+ builder,
+ "Help/Store",
+ requiredValues: new { foo = "", action = "Edit", controller = "Store" });
+ var route = builder.Build();
+ var context = CreateVirtualPathContext(new { foo = "", action = "Edit", controller = "Store" });
+
+ // Act
+ var pathData = route.GetVirtualPath(context);
+
+ // Assert
+ Assert.NotNull(pathData);
+ Assert.Equal("/Help/Store", pathData.VirtualPath);
+ Assert.Same(route, pathData.Router);
+ Assert.Empty(pathData.DataTokens);
+ }
- [Fact]
- public void TreeRouter_GenerateLink_MatchesNullRequiredValue_WithNullRequestValueString()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry = MapOutboundEntry(
- builder,
- "Help/Store",
- requiredValues: new { area = (string)null, action = "Edit", controller = "Store" });
- var route = builder.Build();
- var context = CreateVirtualPathContext(new { area = (string)null, action = "Edit", controller = "Store" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ private static RouteContext CreateRouteContext(string requestPath)
+ {
+ var request = new Mock<HttpRequest>(MockBehavior.Strict);
+ request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
- [Fact]
- public void TreeRouter_GenerateLink_MatchesNullRequiredValue_WithEmptyRequestValueString()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry = MapOutboundEntry(
- builder,
- "Help/Store",
- requiredValues: new { area = (string)null, action = "Edit", controller = "Store" });
- var route = builder.Build();
- var context = CreateVirtualPathContext(new { area = "", action = "Edit", controller = "Store" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ var context = new Mock<HttpContext>(MockBehavior.Strict);
+ context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
+ .Returns(NullLoggerFactory.Instance);
- [Fact]
- public void TreeRouter_GenerateLink_MatchesEmptyStringRequiredValue_WithNullRequestValueString()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry = MapOutboundEntry(
- builder,
- "Help/Store",
- requiredValues: new { foo = "", action = "Edit", controller = "Store" });
- var route = builder.Build();
- var context = CreateVirtualPathContext(new { foo = (string)null, action = "Edit", controller = "Store" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ context.SetupGet(c => c.Request).Returns(request.Object);
- [Fact]
- public void TreeRouter_GenerateLink_MatchesEmptyStringRequiredValue_WithEmptyRequestValueString()
- {
- // Arrange
- var builder = CreateBuilder();
- var entry = MapOutboundEntry(
- builder,
- "Help/Store",
- requiredValues: new { foo = "", action = "Edit", controller = "Store" });
- var route = builder.Build();
- var context = CreateVirtualPathContext(new { foo = "", action = "Edit", controller = "Store" });
-
- // Act
- var pathData = route.GetVirtualPath(context);
-
- // Assert
- Assert.NotNull(pathData);
- Assert.Equal("/Help/Store", pathData.VirtualPath);
- Assert.Same(route, pathData.Router);
- Assert.Empty(pathData.DataTokens);
- }
+ return new RouteContext(context.Object);
+ }
- private static RouteContext CreateRouteContext(string requestPath)
- {
- var request = new Mock<HttpRequest>(MockBehavior.Strict);
- request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
+ private static VirtualPathContext CreateVirtualPathContext(
+ object values,
+ object ambientValues = null,
+ string name = null)
+ {
+ var mockHttpContext = new Mock<HttpContext>();
+ mockHttpContext.Setup(h => h.RequestServices.GetService(typeof(ILoggerFactory)))
+ .Returns(NullLoggerFactory.Instance);
+
+ return new VirtualPathContext(
+ mockHttpContext.Object,
+ new RouteValueDictionary(ambientValues),
+ new RouteValueDictionary(values),
+ name);
+ }
- var context = new Mock<HttpContext>(MockBehavior.Strict);
- context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
- .Returns(NullLoggerFactory.Instance);
+ private static InboundRouteEntry MapInboundEntry(
+ TreeRouteBuilder builder,
+ string template,
+ int order = 0,
+ IRouter handler = null)
+ {
+ var entry = builder.MapInbound(
+ handler ?? new StubRouter(),
+ TemplateParser.Parse(template),
+ routeName: null,
+ order: order);
- context.SetupGet(c => c.Request).Returns(request.Object);
+ // Add a generated 'route group' so we can identify later which entry matched.
+ entry.Defaults["test_route_group"] = CreateRouteGroup(order, template);
- return new RouteContext(context.Object);
- }
+ return entry;
+ }
- private static VirtualPathContext CreateVirtualPathContext(
- object values,
- object ambientValues = null,
- string name = null)
- {
- var mockHttpContext = new Mock<HttpContext>();
- mockHttpContext.Setup(h => h.RequestServices.GetService(typeof(ILoggerFactory)))
- .Returns(NullLoggerFactory.Instance);
-
- return new VirtualPathContext(
- mockHttpContext.Object,
- new RouteValueDictionary(ambientValues),
- new RouteValueDictionary(values),
- name);
- }
+ private static OutboundRouteEntry MapOutboundEntry(
+ TreeRouteBuilder builder,
+ string template,
+ object requiredValues = null,
+ int order = 0,
+ string name = null,
+ IRouter handler = null)
+ {
+ var entry = builder.MapOutbound(
+ handler ?? new StubRouter(),
+ TemplateParser.Parse(template),
+ requiredLinkValues: new RouteValueDictionary(requiredValues),
+ routeName: name,
+ order: order);
- private static InboundRouteEntry MapInboundEntry(
- TreeRouteBuilder builder,
- string template,
- int order = 0,
- IRouter handler = null)
- {
- var entry = builder.MapInbound(
- handler ?? new StubRouter(),
- TemplateParser.Parse(template),
- routeName: null,
- order: order);
+ // Add a generated 'route group' so we can identify later which entry matched.
+ entry.Defaults["test_route_group"] = CreateRouteGroup(order, template);
- // Add a generated 'route group' so we can identify later which entry matched.
- entry.Defaults["test_route_group"] = CreateRouteGroup(order, template);
+ return entry;
+ }
- return entry;
- }
- private static OutboundRouteEntry MapOutboundEntry(
- TreeRouteBuilder builder,
- string template,
- object requiredValues = null,
- int order = 0,
- string name = null,
- IRouter handler = null)
- {
- var entry = builder.MapOutbound(
- handler ?? new StubRouter(),
- TemplateParser.Parse(template),
- requiredLinkValues: new RouteValueDictionary(requiredValues),
- routeName: name,
- order: order);
+ private static string CreateRouteGroup(int order, string template)
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0}&{1}", order, template);
+ }
- // Add a generated 'route group' so we can identify later which entry matched.
- entry.Defaults["test_route_group"] = CreateRouteGroup(order, template);
+ private static DefaultInlineConstraintResolver CreateConstraintResolver()
+ {
+ var options = new RouteOptions();
+ var optionsMock = new Mock<IOptions<RouteOptions>>();
+ optionsMock.SetupGet(o => o.Value).Returns(options);
- return entry;
- }
+ return new DefaultInlineConstraintResolver(optionsMock.Object, new TestServiceProvider());
+ }
+ private static TreeRouteBuilder CreateBuilder()
+ {
+ var objectPoolProvider = new DefaultObjectPoolProvider();
+ var objectPolicy = new UriBuilderContextPooledObjectPolicy();
+ var objectPool = objectPoolProvider.Create<UriBuildingContext>(objectPolicy);
+
+ var constraintResolver = CreateConstraintResolver();
+ var builder = new TreeRouteBuilder(
+ NullLoggerFactory.Instance,
+ objectPool,
+ constraintResolver);
+ return builder;
+ }
- private static string CreateRouteGroup(int order, string template)
- {
- return string.Format(CultureInfo.InvariantCulture, "{0}&{1}", order, template);
- }
+ private static TreeRouter CreateTreeRouter(
+ string firstTemplate,
+ string secondTemplate)
+ {
+ var builder = CreateBuilder();
+ MapOutboundEntry(builder, firstTemplate);
+ MapOutboundEntry(builder, secondTemplate);
+ return builder.Build();
+ }
- private static DefaultInlineConstraintResolver CreateConstraintResolver()
- {
- var options = new RouteOptions();
- var optionsMock = new Mock<IOptions<RouteOptions>>();
- optionsMock.SetupGet(o => o.Value).Returns(options);
+ private class StubRouter : IRouter
+ {
+ public VirtualPathContext GenerationContext { get; set; }
- return new DefaultInlineConstraintResolver(optionsMock.Object, new TestServiceProvider());
- }
+ public RouteContext MatchingContext { get; set; }
- private static TreeRouteBuilder CreateBuilder()
- {
- var objectPoolProvider = new DefaultObjectPoolProvider();
- var objectPolicy = new UriBuilderContextPooledObjectPolicy();
- var objectPool = objectPoolProvider.Create<UriBuildingContext>(objectPolicy);
-
- var constraintResolver = CreateConstraintResolver();
- var builder = new TreeRouteBuilder(
- NullLoggerFactory.Instance,
- objectPool,
- constraintResolver);
- return builder;
- }
+ public Func<RouteContext, bool> MatchingDelegate { get; set; }
- private static TreeRouter CreateTreeRouter(
- string firstTemplate,
- string secondTemplate)
+ public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
- var builder = CreateBuilder();
- MapOutboundEntry(builder, firstTemplate);
- MapOutboundEntry(builder, secondTemplate);
- return builder.Build();
+ GenerationContext = context;
+ return null;
}
- private class StubRouter : IRouter
+ public Task RouteAsync(RouteContext context)
{
- public VirtualPathContext GenerationContext { get; set; }
-
- public RouteContext MatchingContext { get; set; }
-
- public Func<RouteContext, bool> MatchingDelegate { get; set; }
-
- public VirtualPathData GetVirtualPath(VirtualPathContext context)
+ if (MatchingDelegate == null)
{
- GenerationContext = context;
- return null;
+ context.Handler = NullHandler;
}
-
- public Task RouteAsync(RouteContext context)
+ else
{
- if (MatchingDelegate == null)
- {
- context.Handler = NullHandler;
- }
- else
- {
- context.Handler = MatchingDelegate(context) ? NullHandler : null;
- }
-
- return Task.FromResult(true);
+ context.Handler = MatchingDelegate(context) ? NullHandler : null;
}
+
+ return Task.FromResult(true);
}
}
}
diff --git a/src/Http/Routing/test/UnitTests/UriBuildingContextTest.cs b/src/Http/Routing/test/UnitTests/UriBuildingContextTest.cs
index e24fd5306c..7c30aabb01 100644
--- a/src/Http/Routing/test/UnitTests/UriBuildingContextTest.cs
+++ b/src/Http/Routing/test/UnitTests/UriBuildingContextTest.cs
@@ -4,97 +4,96 @@
using Microsoft.Extensions.WebEncoders.Testing;
using Xunit;
-namespace Microsoft.AspNetCore.Routing
+namespace Microsoft.AspNetCore.Routing;
+
+public class UriBuildingContextTest
{
- public class UriBuildingContextTest
+ [Fact]
+ public void EncodeValue_EncodesEntireValue_WhenEncodeSlashes_IsFalse()
{
- [Fact]
- public void EncodeValue_EncodesEntireValue_WhenEncodeSlashes_IsFalse()
- {
- // Arrange
- var urlTestEncoder = new UrlTestEncoder();
- var value = "a/b b1/c";
- var expected = "/UrlEncode[[a/b b1/c]]";
- var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
+ // Arrange
+ var urlTestEncoder = new UrlTestEncoder();
+ var value = "a/b b1/c";
+ var expected = "/UrlEncode[[a/b b1/c]]";
+ var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
- // Act
- uriBuilldingContext.EncodeValue(value, 0, value.Length, encodeSlashes: true);
+ // Act
+ uriBuilldingContext.EncodeValue(value, 0, value.Length, encodeSlashes: true);
- // Assert
- Assert.Equal(expected, uriBuilldingContext.ToString());
- }
+ // Assert
+ Assert.Equal(expected, uriBuilldingContext.ToString());
+ }
- [Fact]
- public void EncodeValue_EncodesOnlySlashes_WhenEncodeSlashes_IsFalse()
- {
- // Arrange
- var urlTestEncoder = new UrlTestEncoder();
- var value = "a/b b1/c";
- var expected = "/UrlEncode[[a]]/UrlEncode[[b b1]]/UrlEncode[[c]]";
- var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
+ [Fact]
+ public void EncodeValue_EncodesOnlySlashes_WhenEncodeSlashes_IsFalse()
+ {
+ // Arrange
+ var urlTestEncoder = new UrlTestEncoder();
+ var value = "a/b b1/c";
+ var expected = "/UrlEncode[[a]]/UrlEncode[[b b1]]/UrlEncode[[c]]";
+ var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
- // Act
- uriBuilldingContext.EncodeValue(value, 0, value.Length, encodeSlashes: false);
+ // Act
+ uriBuilldingContext.EncodeValue(value, 0, value.Length, encodeSlashes: false);
- // Assert
- Assert.Equal(expected, uriBuilldingContext.ToString());
- }
+ // Assert
+ Assert.Equal(expected, uriBuilldingContext.ToString());
+ }
- [Theory]
- [InlineData("a/b b1/c", 0, 2, "/UrlEncode[[a]]/")]
- [InlineData("a/b b1/c", 3, 4, "/UrlEncode[[ b1]]/")]
- [InlineData("a/b b1/c", 3, 5, "/UrlEncode[[ b1]]/UrlEncode[[c]]")]
- [InlineData("a/b b1/c/", 8, 1, "/")]
- [InlineData("/", 0, 1, "/")]
- [InlineData("/a", 0, 2, "/UrlEncode[[a]]")]
- [InlineData("a", 0, 1, "/UrlEncode[[a]]")]
- [InlineData("a/", 0, 2, "/UrlEncode[[a]]/")]
- public void EncodeValue_EncodesOnlySlashes_WithinSubsegment_WhenEncodeSlashes_IsFalse(
- string value,
- int startIndex,
- int characterCount,
- string expected)
- {
- // Arrange
- var urlTestEncoder = new UrlTestEncoder();
- var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
+ [Theory]
+ [InlineData("a/b b1/c", 0, 2, "/UrlEncode[[a]]/")]
+ [InlineData("a/b b1/c", 3, 4, "/UrlEncode[[ b1]]/")]
+ [InlineData("a/b b1/c", 3, 5, "/UrlEncode[[ b1]]/UrlEncode[[c]]")]
+ [InlineData("a/b b1/c/", 8, 1, "/")]
+ [InlineData("/", 0, 1, "/")]
+ [InlineData("/a", 0, 2, "/UrlEncode[[a]]")]
+ [InlineData("a", 0, 1, "/UrlEncode[[a]]")]
+ [InlineData("a/", 0, 2, "/UrlEncode[[a]]/")]
+ public void EncodeValue_EncodesOnlySlashes_WithinSubsegment_WhenEncodeSlashes_IsFalse(
+ string value,
+ int startIndex,
+ int characterCount,
+ string expected)
+ {
+ // Arrange
+ var urlTestEncoder = new UrlTestEncoder();
+ var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
- // Act
- uriBuilldingContext.EncodeValue(value, startIndex, characterCount, encodeSlashes: false);
+ // Act
+ uriBuilldingContext.EncodeValue(value, startIndex, characterCount, encodeSlashes: false);
- // Assert
- Assert.Equal(expected, uriBuilldingContext.ToString());
- }
+ // Assert
+ Assert.Equal(expected, uriBuilldingContext.ToString());
+ }
- [Theory]
- [InlineData("/Author", false, false, "/UrlEncode[[Author]]")]
- [InlineData("/Author", false, true, "/UrlEncode[[Author]]")]
- [InlineData("/Author", true, false, "/UrlEncode[[Author]]/")]
- [InlineData("/Author", true, true, "/UrlEncode[[Author]]/")]
- [InlineData("/Author/", false, false, "/UrlEncode[[Author]]/")]
- [InlineData("/Author/", false, true, "/UrlEncode[[Author/]]")]
- [InlineData("/Author/", true, false, "/UrlEncode[[Author]]/")]
- [InlineData("/Author/", true, true, "/UrlEncode[[Author/]]/")]
- [InlineData("Author", false, false, "/UrlEncode[[Author]]")]
- [InlineData("Author", false, true, "/UrlEncode[[Author]]")]
- [InlineData("Author", true, false, "/UrlEncode[[Author]]/")]
- [InlineData("Author", true, true, "/UrlEncode[[Author]]/")]
- [InlineData("", false, false, "")]
- [InlineData("", false, true, "")]
- [InlineData("", true, false, "")]
- [InlineData("", true, true, "")]
- public void ToPathString(string url, bool appendTrailingSlash, bool encodeSlashes, string expected)
- {
- // Arrange
- var urlTestEncoder = new UrlTestEncoder();
- var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
- uriBuilldingContext.AppendTrailingSlash = appendTrailingSlash;
+ [Theory]
+ [InlineData("/Author", false, false, "/UrlEncode[[Author]]")]
+ [InlineData("/Author", false, true, "/UrlEncode[[Author]]")]
+ [InlineData("/Author", true, false, "/UrlEncode[[Author]]/")]
+ [InlineData("/Author", true, true, "/UrlEncode[[Author]]/")]
+ [InlineData("/Author/", false, false, "/UrlEncode[[Author]]/")]
+ [InlineData("/Author/", false, true, "/UrlEncode[[Author/]]")]
+ [InlineData("/Author/", true, false, "/UrlEncode[[Author]]/")]
+ [InlineData("/Author/", true, true, "/UrlEncode[[Author/]]/")]
+ [InlineData("Author", false, false, "/UrlEncode[[Author]]")]
+ [InlineData("Author", false, true, "/UrlEncode[[Author]]")]
+ [InlineData("Author", true, false, "/UrlEncode[[Author]]/")]
+ [InlineData("Author", true, true, "/UrlEncode[[Author]]/")]
+ [InlineData("", false, false, "")]
+ [InlineData("", false, true, "")]
+ [InlineData("", true, false, "")]
+ [InlineData("", true, true, "")]
+ public void ToPathString(string url, bool appendTrailingSlash, bool encodeSlashes, string expected)
+ {
+ // Arrange
+ var urlTestEncoder = new UrlTestEncoder();
+ var uriBuilldingContext = new UriBuildingContext(urlTestEncoder);
+ uriBuilldingContext.AppendTrailingSlash = appendTrailingSlash;
- // Act
- uriBuilldingContext.Accept(url, encodeSlashes);
+ // Act
+ uriBuilldingContext.Accept(url, encodeSlashes);
- // Assert
- Assert.Equal(expected, uriBuilldingContext.ToPathString().Value);
- }
+ // Assert
+ Assert.Equal(expected, uriBuilldingContext.ToPathString().Value);
}
}
diff --git a/src/Http/Routing/test/testassets/Benchmarks/Program.cs b/src/Http/Routing/test/testassets/Benchmarks/Program.cs
index 0a2901b150..40eeab110c 100644
--- a/src/Http/Routing/test/testassets/Benchmarks/Program.cs
+++ b/src/Http/Routing/test/testassets/Benchmarks/Program.cs
@@ -8,62 +8,61 @@ using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
-namespace Benchmarks
+namespace Benchmarks;
+
+public class Program
{
- public class Program
+ public static Task Main(string[] args)
{
- public static Task Main(string[] args)
- {
- return GetHostBuilder(args).Build().RunAsync();
- }
-
- public static IHostBuilder GetHostBuilder(string[] args)
- {
- var config = new ConfigurationBuilder()
- .AddCommandLine(args)
- .AddEnvironmentVariables(prefix: "RoutingBenchmarks_")
- .Build();
+ return GetHostBuilder(args).Build().RunAsync();
+ }
- // Consoler logger has a major impact on perf results, so do not use
- // default builder.
+ public static IHostBuilder GetHostBuilder(string[] args)
+ {
+ var config = new ConfigurationBuilder()
+ .AddCommandLine(args)
+ .AddEnvironmentVariables(prefix: "RoutingBenchmarks_")
+ .Build();
- var hostBuilder = new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .UseKestrel()
- .UseTestServer()
- .UseConfiguration(config);
- });
+ // Consoler logger has a major impact on perf results, so do not use
+ // default builder.
- var scenario = config["scenarios"]?.ToLowerInvariant();
- if (scenario == "plaintextdispatcher" || scenario == "plaintextendpointrouting")
- {
- hostBuilder.ConfigureWebHost(webHostBuilder =>
+ var hostBuilder = new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
- .UseStartup<StartupUsingEndpointRouting>()
- // for testing
- .UseSetting("Startup", nameof(StartupUsingEndpointRouting));
+ .UseKestrel()
+ .UseTestServer()
+ .UseConfiguration(config);
});
- }
- else if (scenario == "plaintextrouting" || scenario == "plaintextrouter")
+
+ var scenario = config["scenarios"]?.ToLowerInvariant();
+ if (scenario == "plaintextdispatcher" || scenario == "plaintextendpointrouting")
+ {
+ hostBuilder.ConfigureWebHost(webHostBuilder =>
{
- hostBuilder.ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .UseStartup<StartupUsingRouter>()
- // for testing
- .UseSetting("Startup", nameof(StartupUsingRouter));
- });
- }
- else
+ webHostBuilder
+ .UseStartup<StartupUsingEndpointRouting>()
+ // for testing
+ .UseSetting("Startup", nameof(StartupUsingEndpointRouting));
+ });
+ }
+ else if (scenario == "plaintextrouting" || scenario == "plaintextrouter")
+ {
+ hostBuilder.ConfigureWebHost(webHostBuilder =>
{
- throw new InvalidOperationException(
- $"Invalid scenario '{scenario}'. Allowed scenarios are PlaintextEndpointRouting and PlaintextRouter");
- }
-
- return hostBuilder;
+ webHostBuilder
+ .UseStartup<StartupUsingRouter>()
+ // for testing
+ .UseSetting("Startup", nameof(StartupUsingRouter));
+ });
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ $"Invalid scenario '{scenario}'. Allowed scenarios are PlaintextEndpointRouting and PlaintextRouter");
}
+
+ return hostBuilder;
}
}
diff --git a/src/Http/Routing/test/testassets/Benchmarks/StartupUsingEndpointRouting.cs b/src/Http/Routing/test/testassets/Benchmarks/StartupUsingEndpointRouting.cs
index cbda5fdc45..4ab96e45d4 100644
--- a/src/Http/Routing/test/testassets/Benchmarks/StartupUsingEndpointRouting.cs
+++ b/src/Http/Routing/test/testassets/Benchmarks/StartupUsingEndpointRouting.cs
@@ -2,31 +2,31 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
-namespace Benchmarks
+namespace Benchmarks;
+
+public class StartupUsingEndpointRouting
{
- public class StartupUsingEndpointRouting
+ private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
+
+ public void ConfigureServices(IServiceCollection services)
{
- private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
+ services.AddRouting();
+ }
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddRouting();
- }
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouting();
- public void Configure(IApplicationBuilder app)
+ app.UseEndpoints(endpoints =>
{
- app.UseRouting();
-
- app.UseEndpoints(endpoints =>
+ var endpointDataSource = new DefaultEndpointDataSource(new[]
{
- var endpointDataSource = new DefaultEndpointDataSource(new[]
- {
new RouteEndpoint(
requestDelegate: (httpContext) =>
{
@@ -41,10 +41,9 @@ namespace Benchmarks
order: 0,
metadata: EndpointMetadataCollection.Empty,
displayName: "Plaintext"),
- });
-
- endpoints.DataSources.Add(endpointDataSource);
});
- }
+
+ endpoints.DataSources.Add(endpointDataSource);
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/Benchmarks/StartupUsingRouter.cs b/src/Http/Routing/test/testassets/Benchmarks/StartupUsingRouter.cs
index ff9a425a74..c1c9f8d5da 100644
--- a/src/Http/Routing/test/testassets/Benchmarks/StartupUsingRouter.cs
+++ b/src/Http/Routing/test/testassets/Benchmarks/StartupUsingRouter.cs
@@ -1,36 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
-using System.Text;
-namespace Benchmarks
+namespace Benchmarks;
+
+public class StartupUsingRouter
{
- public class StartupUsingRouter
- {
- private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
+ private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddRouting();
- }
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRouting();
+ }
- public void Configure(IApplicationBuilder app)
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouter(routes =>
{
- app.UseRouter(routes =>
+ routes.MapRoute("/plaintext", (httpContext) =>
{
- routes.MapRoute("/plaintext", (httpContext) =>
- {
- var response = httpContext.Response;
- var payloadLength = _helloWorldPayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
- });
+ var response = httpContext.Response;
+ var payloadLength = _helloWorldPayload.Length;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ response.ContentLength = payloadLength;
+ return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
});
- }
+ });
}
-} \ No newline at end of file
+}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkConfigurationBuilder.cs b/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkConfigurationBuilder.cs
index 65bd086f5c..7e8fd3e2d5 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkConfigurationBuilder.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkConfigurationBuilder.cs
@@ -4,35 +4,34 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace RoutingSandbox.Framework
+namespace RoutingSandbox.Framework;
+
+public class FrameworkConfigurationBuilder
{
- public class FrameworkConfigurationBuilder
- {
- private readonly FrameworkEndpointDataSource _dataSource;
+ private readonly FrameworkEndpointDataSource _dataSource;
- internal FrameworkConfigurationBuilder(FrameworkEndpointDataSource dataSource)
- {
- _dataSource = dataSource;
- }
+ internal FrameworkConfigurationBuilder(FrameworkEndpointDataSource dataSource)
+ {
+ _dataSource = dataSource;
+ }
- public void AddPattern(string pattern)
- {
- AddPattern(RoutePatternFactory.Parse(pattern));
- }
+ public void AddPattern(string pattern)
+ {
+ AddPattern(RoutePatternFactory.Parse(pattern));
+ }
- public void AddPattern(RoutePattern pattern)
- {
- _dataSource.Patterns.Add(pattern);
- }
+ public void AddPattern(RoutePattern pattern)
+ {
+ _dataSource.Patterns.Add(pattern);
+ }
- public void AddHubMethod(string hub, string method, RequestDelegate requestDelegate)
+ public void AddHubMethod(string hub, string method, RequestDelegate requestDelegate)
+ {
+ _dataSource.HubMethods.Add(new HubMethod
{
- _dataSource.HubMethods.Add(new HubMethod
- {
- Hub = hub,
- Method = method,
- RequestDelegate = requestDelegate
- });
- }
+ Hub = hub,
+ Method = method,
+ RequestDelegate = requestDelegate
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointDataSource.cs b/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointDataSource.cs
index 2432aefe58..5fa0249a88 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointDataSource.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointDataSource.cs
@@ -12,90 +12,89 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
-namespace RoutingSandbox.Framework
+namespace RoutingSandbox.Framework;
+
+internal class FrameworkEndpointDataSource : EndpointDataSource, IEndpointConventionBuilder
{
- internal class FrameworkEndpointDataSource : EndpointDataSource, IEndpointConventionBuilder
- {
- private readonly RoutePatternTransformer _routePatternTransformer;
- private readonly List<Action<EndpointBuilder>> _conventions;
+ private readonly RoutePatternTransformer _routePatternTransformer;
+ private readonly List<Action<EndpointBuilder>> _conventions;
- public List<RoutePattern> Patterns { get; }
- public List<HubMethod> HubMethods { get; }
+ public List<RoutePattern> Patterns { get; }
+ public List<HubMethod> HubMethods { get; }
- private List<Endpoint> _endpoints;
+ private List<Endpoint> _endpoints;
- public FrameworkEndpointDataSource(RoutePatternTransformer routePatternTransformer)
- {
- _routePatternTransformer = routePatternTransformer;
- _conventions = new List<Action<EndpointBuilder>>();
+ public FrameworkEndpointDataSource(RoutePatternTransformer routePatternTransformer)
+ {
+ _routePatternTransformer = routePatternTransformer;
+ _conventions = new List<Action<EndpointBuilder>>();
- Patterns = new List<RoutePattern>();
- HubMethods = new List<HubMethod>();
- }
+ Patterns = new List<RoutePattern>();
+ HubMethods = new List<HubMethod>();
+ }
- public override IReadOnlyList<Endpoint> Endpoints
+ public override IReadOnlyList<Endpoint> Endpoints
+ {
+ get
{
- get
+ if (_endpoints == null)
{
- if (_endpoints == null)
- {
- _endpoints = BuildEndpoints();
- }
-
- return _endpoints;
+ _endpoints = BuildEndpoints();
}
+
+ return _endpoints;
}
+ }
+
+ private List<Endpoint> BuildEndpoints()
+ {
+ List<Endpoint> endpoints = new List<Endpoint>();
- private List<Endpoint> BuildEndpoints()
+ foreach (var hubMethod in HubMethods)
{
- List<Endpoint> endpoints = new List<Endpoint>();
+ var requiredValues = new { hub = hubMethod.Hub, method = hubMethod.Method };
+ var order = 1;
- foreach (var hubMethod in HubMethods)
+ foreach (var pattern in Patterns)
{
- var requiredValues = new { hub = hubMethod.Hub, method = hubMethod.Method };
- var order = 1;
+ var resolvedPattern = _routePatternTransformer.SubstituteRequiredValues(pattern, requiredValues);
+ if (resolvedPattern == null)
+ {
+ continue;
+ }
- foreach (var pattern in Patterns)
+ var endpointBuilder = new RouteEndpointBuilder(
+ hubMethod.RequestDelegate,
+ resolvedPattern,
+ order++);
+ endpointBuilder.DisplayName = $"{hubMethod.Hub}.{hubMethod.Method}";
+
+ foreach (var convention in _conventions)
{
- var resolvedPattern = _routePatternTransformer.SubstituteRequiredValues(pattern, requiredValues);
- if (resolvedPattern == null)
- {
- continue;
- }
-
- var endpointBuilder = new RouteEndpointBuilder(
- hubMethod.RequestDelegate,
- resolvedPattern,
- order++);
- endpointBuilder.DisplayName = $"{hubMethod.Hub}.{hubMethod.Method}";
-
- foreach (var convention in _conventions)
- {
- convention(endpointBuilder);
- }
-
- endpoints.Add(endpointBuilder.Build());
+ convention(endpointBuilder);
}
- }
- return endpoints;
+ endpoints.Add(endpointBuilder.Build());
+ }
}
- public override IChangeToken GetChangeToken()
- {
- return NullChangeToken.Singleton;
- }
+ return endpoints;
+ }
- public void Add(Action<EndpointBuilder> convention)
- {
- _conventions.Add(convention);
- }
+ public override IChangeToken GetChangeToken()
+ {
+ return NullChangeToken.Singleton;
}
- internal class HubMethod
+ public void Add(Action<EndpointBuilder> convention)
{
- public string Hub { get; set; }
- public string Method { get; set; }
- public RequestDelegate RequestDelegate { get; set; }
+ _conventions.Add(convention);
}
}
+
+internal class HubMethod
+{
+ public string Hub { get; set; }
+ public string Method { get; set; }
+ public RequestDelegate RequestDelegate { get; set; }
+}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointRouteBuilderExtensions.cs b/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointRouteBuilderExtensions.cs
index 47cb6ea28b..2c6676ff21 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/Framework/FrameworkEndpointRouteBuilderExtensions.cs
@@ -11,29 +11,28 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
-namespace RoutingSandbox.Framework
+namespace RoutingSandbox.Framework;
+
+public static class FrameworkEndpointRouteBuilderExtensions
{
- public static class FrameworkEndpointRouteBuilderExtensions
+ public static IEndpointConventionBuilder MapFramework(this IEndpointRouteBuilder endpoints, Action<FrameworkConfigurationBuilder> configure)
{
- public static IEndpointConventionBuilder MapFramework(this IEndpointRouteBuilder endpoints, Action<FrameworkConfigurationBuilder> configure)
+ if (endpoints == null)
+ {
+ throw new ArgumentNullException(nameof(endpoints));
+ }
+ if (configure == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
- if (configure == null)
- {
- throw new ArgumentNullException(nameof(configure));
- }
+ throw new ArgumentNullException(nameof(configure));
+ }
- var dataSource = endpoints.ServiceProvider.GetRequiredService<FrameworkEndpointDataSource>();
+ var dataSource = endpoints.ServiceProvider.GetRequiredService<FrameworkEndpointDataSource>();
- var configurationBuilder = new FrameworkConfigurationBuilder(dataSource);
- configure(configurationBuilder);
+ var configurationBuilder = new FrameworkConfigurationBuilder(dataSource);
+ configure(configurationBuilder);
- endpoints.DataSources.Add(dataSource);
+ endpoints.DataSources.Add(dataSource);
- return dataSource;
- }
+ return dataSource;
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs
index 8134a8c3ab..ec63313241 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/EndpointRouteBuilderExtensions.cs
@@ -9,22 +9,21 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public static class EndpointRouteBuilderExtensions
{
- public static class EndpointRouteBuilderExtensions
+ public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder endpoints, string pattern, string greeter)
{
- public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder endpoints, string pattern, string greeter)
+ if (endpoints == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- var pipeline = endpoints.CreateApplicationBuilder()
- .UseHello(greeter)
- .Build();
+ var pipeline = endpoints.CreateApplicationBuilder()
+ .UseHello(greeter)
+ .Build();
- return endpoints.Map(pattern, pipeline);
- }
+ return endpoints.Map(pattern, pipeline);
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs
index 2c56b23a8c..6459bf9448 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloAppBuilderExtensions.cs
@@ -5,21 +5,20 @@ using System;
using Microsoft.Extensions.Options;
using RoutingSample.Web.HelloExtension;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public static class HelloAppBuilderExtensions
{
- public static class HelloAppBuilderExtensions
+ public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter)
{
- public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter)
+ if (app == null)
{
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
-
- return app.UseMiddleware<HelloMiddleware>(Options.Create(new HelloOptions
- {
- Greeter = greeter
- }));
+ throw new ArgumentNullException(nameof(app));
}
+
+ return app.UseMiddleware<HelloMiddleware>(Options.Create(new HelloOptions
+ {
+ Greeter = greeter
+ }));
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloMiddleware.cs b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloMiddleware.cs
index 7f2f28c601..725238e4ea 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloMiddleware.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloMiddleware.cs
@@ -9,37 +9,36 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
-namespace RoutingSample.Web.HelloExtension
+namespace RoutingSample.Web.HelloExtension;
+
+public class HelloMiddleware
{
- public class HelloMiddleware
+ private readonly RequestDelegate _next;
+ private readonly HelloOptions _helloOptions;
+ private readonly byte[] _helloPayload;
+
+ public HelloMiddleware(RequestDelegate next, IOptions<HelloOptions> helloOptions)
{
- private readonly RequestDelegate _next;
- private readonly HelloOptions _helloOptions;
- private readonly byte[] _helloPayload;
+ _next = next;
+ _helloOptions = helloOptions.Value;
- public HelloMiddleware(RequestDelegate next, IOptions<HelloOptions> helloOptions)
+ var payload = new List<byte>();
+ payload.AddRange(Encoding.UTF8.GetBytes("Hello"));
+ if (!string.IsNullOrEmpty(_helloOptions.Greeter))
{
- _next = next;
- _helloOptions = helloOptions.Value;
-
- var payload = new List<byte>();
- payload.AddRange(Encoding.UTF8.GetBytes("Hello"));
- if (!string.IsNullOrEmpty(_helloOptions.Greeter))
- {
- payload.Add((byte)' ');
- payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter));
- }
- _helloPayload = payload.ToArray();
+ payload.Add((byte)' ');
+ payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter));
}
+ _helloPayload = payload.ToArray();
+ }
- public Task InvokeAsync(HttpContext context)
- {
- var response = context.Response;
- var payloadLength = _helloPayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_helloPayload, 0, payloadLength);
- }
+ public Task InvokeAsync(HttpContext context)
+ {
+ var response = context.Response;
+ var payloadLength = _helloPayload.Length;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ response.ContentLength = payloadLength;
+ return response.Body.WriteAsync(_helloPayload, 0, payloadLength);
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloOptions.cs b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloOptions.cs
index 672b27769d..a97f4d1b03 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloOptions.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/HelloExtension/HelloOptions.cs
@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace RoutingSample.Web.HelloExtension
+namespace RoutingSample.Web.HelloExtension;
+
+public class HelloOptions
{
- public class HelloOptions
- {
- public string Greeter { get; set; }
- }
+ public string Greeter { get; set; }
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs b/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs
index 4d02d0d815..684499d3bf 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs
@@ -9,71 +9,70 @@ using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace RoutingSandbox
+namespace RoutingSandbox;
+
+public class Program
{
- public class Program
+ public const string EndpointRoutingScenario = "endpointrouting";
+ public const string RouterScenario = "router";
+
+ public static Task Main(string[] args)
{
- public const string EndpointRoutingScenario = "endpointrouting";
- public const string RouterScenario = "router";
+ var host = GetHostBuilder(args).Build();
+ return host.RunAsync();
+ }
- public static Task Main(string[] args)
+ // For unit testing
+ public static IHostBuilder GetHostBuilder(string[] args)
+ {
+ string scenario;
+ if (args.Length == 0)
{
- var host = GetHostBuilder(args).Build();
- return host.RunAsync();
- }
+ Console.WriteLine("Choose a sample to run:");
+ Console.WriteLine($"1. {EndpointRoutingScenario}");
+ Console.WriteLine($"2. {RouterScenario}");
+ Console.WriteLine();
- // For unit testing
- public static IHostBuilder GetHostBuilder(string[] args)
+ scenario = Console.ReadLine();
+ }
+ else
{
- string scenario;
- if (args.Length == 0)
- {
- Console.WriteLine("Choose a sample to run:");
- Console.WriteLine($"1. {EndpointRoutingScenario}");
- Console.WriteLine($"2. {RouterScenario}");
- Console.WriteLine();
-
- scenario = Console.ReadLine();
- }
- else
- {
- scenario = args[0];
- }
-
- Type startupType;
- switch (scenario)
- {
- case "1":
- case EndpointRoutingScenario:
- startupType = typeof(UseEndpointRoutingStartup);
- break;
+ scenario = args[0];
+ }
- case "2":
- case RouterScenario:
- startupType = typeof(UseRouterStartup);
- break;
+ Type startupType;
+ switch (scenario)
+ {
+ case "1":
+ case EndpointRoutingScenario:
+ startupType = typeof(UseEndpointRoutingStartup);
+ break;
- default:
- Console.WriteLine($"unknown scenario {scenario}");
- Console.WriteLine($"usage: dotnet run -- ({EndpointRoutingScenario}|{RouterScenario})");
- throw new InvalidOperationException();
+ case "2":
+ case RouterScenario:
+ startupType = typeof(UseRouterStartup);
+ break;
- }
+ default:
+ Console.WriteLine($"unknown scenario {scenario}");
+ Console.WriteLine($"usage: dotnet run -- ({EndpointRoutingScenario}|{RouterScenario})");
+ throw new InvalidOperationException();
- return new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .UseKestrel()
- .UseIISIntegration()
- .UseContentRoot(Environment.CurrentDirectory)
- .UseStartup(startupType);
- })
- .ConfigureLogging(b =>
- {
- b.AddConsole();
- b.SetMinimumLevel(LogLevel.Critical);
- });
}
+
+ return new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseKestrel()
+ .UseIISIntegration()
+ .UseContentRoot(Environment.CurrentDirectory)
+ .UseStartup(startupType);
+ })
+ .ConfigureLogging(b =>
+ {
+ b.AddConsole();
+ b.SetMinimumLevel(LogLevel.Critical);
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/SlugifyParameterTransformer.cs b/src/Http/Routing/test/testassets/RoutingSandbox/SlugifyParameterTransformer.cs
index 693db12bd2..8acba74542 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/SlugifyParameterTransformer.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/SlugifyParameterTransformer.cs
@@ -6,14 +6,13 @@ using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Routing;
-namespace RoutingSandbox
+namespace RoutingSandbox;
+
+public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
- public class SlugifyParameterTransformer : IOutboundParameterTransformer
+ public string TransformOutbound(object value)
{
- public string TransformOutbound(object value)
- {
- // Slugify value
- return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2", RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
- }
+ // Slugify value
+ return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2", RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/UseEndpointRoutingStartup.cs b/src/Http/Routing/test/testassets/RoutingSandbox/UseEndpointRoutingStartup.cs
index 48744b5d1b..c53fd3300a 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/UseEndpointRoutingStartup.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/UseEndpointRoutingStartup.cs
@@ -14,115 +14,114 @@ using Microsoft.AspNetCore.Routing.Internal;
using Microsoft.Extensions.DependencyInjection;
using RoutingSandbox.Framework;
-namespace RoutingSandbox
+namespace RoutingSandbox;
+
+public class UseEndpointRoutingStartup
{
- public class UseEndpointRoutingStartup
- {
- private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!");
+ private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!");
- public void ConfigureServices(IServiceCollection services)
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRouting(options =>
{
- services.AddRouting(options =>
- {
- options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
- });
- services.AddSingleton<FrameworkEndpointDataSource>();
- }
+ options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
+ });
+ services.AddSingleton<FrameworkEndpointDataSource>();
+ }
- public void Configure(IApplicationBuilder app)
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseStaticFiles();
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
{
- app.UseStaticFiles();
- app.UseRouting();
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapHello("/helloworld", "World");
+ endpoints.MapHello("/helloworld", "World");
- endpoints.MapGet(
- "/",
- (httpContext) =>
- {
- var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
+ endpoints.MapGet(
+ "/",
+ (httpContext) =>
+ {
+ var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
- var sb = new StringBuilder();
- sb.AppendLine("Endpoints:");
- foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
+ var sb = new StringBuilder();
+ sb.AppendLine("Endpoints:");
+ foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
+ {
+ sb.AppendLine(FormattableString.Invariant($"- {endpoint.RoutePattern.RawText}"));
+ foreach (var metadata in endpoint.Metadata)
{
- sb.AppendLine(FormattableString.Invariant($"- {endpoint.RoutePattern.RawText}"));
- foreach (var metadata in endpoint.Metadata)
- {
- sb.AppendLine(" " + metadata);
- }
+ sb.AppendLine(" " + metadata);
}
+ }
- var response = httpContext.Response;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- return response.WriteAsync(sb.ToString());
- });
- endpoints.MapGet(
- "/plaintext",
- (httpContext) =>
- {
- var response = httpContext.Response;
- var payloadLength = _plainTextPayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
- });
- endpoints.MapGet(
- "/graph",
- (httpContext) =>
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(sb.ToString());
+ });
+ endpoints.MapGet(
+ "/plaintext",
+ (httpContext) =>
+ {
+ var response = httpContext.Response;
+ var payloadLength = _plainTextPayload.Length;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ response.ContentLength = payloadLength;
+ return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
+ });
+ endpoints.MapGet(
+ "/graph",
+ (httpContext) =>
+ {
+ using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
{
- using (var writer = new StreamWriter(httpContext.Response.Body, Encoding.UTF8, 1024, leaveOpen: true))
- {
- var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
- var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
- graphWriter.Write(dataSource, writer);
- }
+ var graphWriter = httpContext.RequestServices.GetRequiredService<DfaGraphWriter>();
+ var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
+ graphWriter.Write(dataSource, writer);
+ }
- return Task.CompletedTask;
- }).WithDisplayName("DFA Graph");
+ return Task.CompletedTask;
+ }).WithDisplayName("DFA Graph");
- endpoints.MapGet("/attributes", HandlerWithAttributes);
+ endpoints.MapGet("/attributes", HandlerWithAttributes);
- endpoints.Map("/getwithattributes", Handler);
+ endpoints.Map("/getwithattributes", Handler);
- endpoints.MapFramework(frameworkBuilder =>
- {
- frameworkBuilder.AddPattern("/transform/{hub:slugify=TestHub}/{method:slugify=TestMethod}");
- frameworkBuilder.AddPattern("/{hub}/{method=TestMethod}");
+ endpoints.MapFramework(frameworkBuilder =>
+ {
+ frameworkBuilder.AddPattern("/transform/{hub:slugify=TestHub}/{method:slugify=TestMethod}");
+ frameworkBuilder.AddPattern("/{hub}/{method=TestMethod}");
- frameworkBuilder.AddHubMethod("TestHub", "TestMethod", context => context.Response.WriteAsync("TestMethod!"));
- frameworkBuilder.AddHubMethod("Login", "Authenticate", context => context.Response.WriteAsync("Authenticate!"));
- frameworkBuilder.AddHubMethod("Login", "Logout", context => context.Response.WriteAsync("Logout!"));
- });
+ frameworkBuilder.AddHubMethod("TestHub", "TestMethod", context => context.Response.WriteAsync("TestMethod!"));
+ frameworkBuilder.AddHubMethod("Login", "Authenticate", context => context.Response.WriteAsync("Authenticate!"));
+ frameworkBuilder.AddHubMethod("Login", "Logout", context => context.Response.WriteAsync("Logout!"));
});
+ });
- }
+ }
- [Authorize]
- private Task HandlerWithAttributes(HttpContext context)
- {
- return context.Response.WriteAsync("I have ann authorize attribute");
- }
+ [Authorize]
+ private Task HandlerWithAttributes(HttpContext context)
+ {
+ return context.Response.WriteAsync("I have ann authorize attribute");
+ }
- [HttpGet]
- private Task Handler(HttpContext context)
- {
- return context.Response.WriteAsync("I have a method metadata attribute");
- }
+ [HttpGet]
+ private Task Handler(HttpContext context)
+ {
+ return context.Response.WriteAsync("I have a method metadata attribute");
+ }
- private class AuthorizeAttribute : Attribute
- {
+ private class AuthorizeAttribute : Attribute
+ {
- }
+ }
- private class HttpGetAttribute : Attribute, IHttpMethodMetadata
- {
- public bool AcceptCorsPreflight => false;
+ private class HttpGetAttribute : Attribute, IHttpMethodMetadata
+ {
+ public bool AcceptCorsPreflight => false;
- public IReadOnlyList<string> HttpMethods { get; } = new List<string> { "GET" };
- }
+ public IReadOnlyList<string> HttpMethods { get; } = new List<string> { "GET" };
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/UseRouterStartup.cs b/src/Http/Routing/test/testassets/RoutingSandbox/UseRouterStartup.cs
index a74b7f493e..59d3a577c5 100644
--- a/src/Http/Routing/test/testassets/RoutingSandbox/UseRouterStartup.cs
+++ b/src/Http/Routing/test/testassets/RoutingSandbox/UseRouterStartup.cs
@@ -9,35 +9,34 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
-namespace RoutingSandbox
+namespace RoutingSandbox;
+
+public class UseRouterStartup
{
- public class UseRouterStartup
- {
- private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
+ private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddRouting();
- }
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRouting();
+ }
- public void Configure(IApplicationBuilder app)
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouter(routes =>
{
- app.UseRouter(routes =>
+ routes.DefaultHandler = new RouteHandler((httpContext) =>
{
- routes.DefaultHandler = new RouteHandler((httpContext) =>
- {
- var request = httpContext.Request;
- return httpContext.Response.WriteAsync($"Verb = {request.Method.ToUpperInvariant()} - Path = {request.Path} - Route values - {string.Join(", ", httpContext.GetRouteData().Values)}");
- });
-
- routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"API Get {routeData.Values["id"]}"))
- .MapMiddlewareRoute("api/middleware", (appBuilder) => appBuilder.Run(httpContext => httpContext.Response.WriteAsync("Middleware!")))
- .MapRoute(
- name: "AllVerbs",
- template: "api/all/{name}/{lastName?}",
- defaults: new { lastName = "Doe" },
- constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
+ var request = httpContext.Request;
+ return httpContext.Response.WriteAsync($"Verb = {request.Method.ToUpperInvariant()} - Path = {request.Path} - Route values - {string.Join(", ", httpContext.GetRouteData().Values)}");
});
- }
+
+ routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"API Get {routeData.Values["id"]}"))
+ .MapMiddlewareRoute("api/middleware", (appBuilder) => appBuilder.Run(httpContext => httpContext.Response.WriteAsync("Middleware!")))
+ .MapRoute(
+ name: "AllVerbs",
+ template: "api/all/{name}/{lastName?}",
+ defaults: new { lastName = "Doe" },
+ constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/EndsWithStringRouteConstraint.cs b/src/Http/Routing/test/testassets/RoutingWebSite/EndsWithStringRouteConstraint.cs
index 0fca7c55eb..7577b7a760 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/EndsWithStringRouteConstraint.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/EndsWithStringRouteConstraint.cs
@@ -6,28 +6,27 @@ using System.Globalization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
-namespace RoutingWebSite
+namespace RoutingWebSite;
+
+internal class EndsWithStringRouteConstraint : IRouteConstraint
{
- internal class EndsWithStringRouteConstraint : IRouteConstraint
+ private readonly string _endsWith;
+
+ public EndsWithStringRouteConstraint(string endsWith)
{
- private readonly string _endsWith;
+ _endsWith = endsWith;
+ }
- public EndsWithStringRouteConstraint(string endsWith)
+ public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
+ {
+ var value = values[routeKey];
+ if (value == null)
{
- _endsWith = endsWith;
+ return false;
}
- public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
- {
- var value = values[routeKey];
- if (value == null)
- {
- return false;
- }
-
- var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
- var endsWith = valueString.EndsWith(_endsWith, StringComparison.OrdinalIgnoreCase);
- return endsWith;
- }
+ var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
+ var endsWith = valueString.EndsWith(_endsWith, StringComparison.OrdinalIgnoreCase);
+ return endsWith;
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/EndpointRouteBuilderExtensions.cs
index 7e221fbe08..3751c816a1 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/EndpointRouteBuilderExtensions.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/EndpointRouteBuilderExtensions.cs
@@ -9,22 +9,21 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public static class EndpointRouteBuilderExtensions
{
- public static class EndpointRouteBuilderExtensions
+ public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder endpoints, string template, string greeter)
{
- public static IEndpointConventionBuilder MapHello(this IEndpointRouteBuilder endpoints, string template, string greeter)
+ if (endpoints == null)
{
- if (endpoints == null)
- {
- throw new ArgumentNullException(nameof(endpoints));
- }
+ throw new ArgumentNullException(nameof(endpoints));
+ }
- var pipeline = endpoints.CreateApplicationBuilder()
- .UseHello(greeter)
- .Build();
+ var pipeline = endpoints.CreateApplicationBuilder()
+ .UseHello(greeter)
+ .Build();
- return endpoints.Map(template, pipeline).WithDisplayName("Hello " + greeter);
- }
+ return endpoints.Map(template, pipeline).WithDisplayName("Hello " + greeter);
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloAppBuilderExtensions.cs b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloAppBuilderExtensions.cs
index 6334ad18f0..a72705d7a4 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloAppBuilderExtensions.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloAppBuilderExtensions.cs
@@ -5,21 +5,20 @@ using System;
using Microsoft.Extensions.Options;
using RoutingWebSite.HelloExtension;
-namespace Microsoft.AspNetCore.Builder
+namespace Microsoft.AspNetCore.Builder;
+
+public static class HelloAppBuilderExtensions
{
- public static class HelloAppBuilderExtensions
+ public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter)
{
- public static IApplicationBuilder UseHello(this IApplicationBuilder app, string greeter)
+ if (app == null)
{
- if (app == null)
- {
- throw new ArgumentNullException(nameof(app));
- }
-
- return app.UseMiddleware<HelloMiddleware>(Options.Create(new HelloOptions
- {
- Greeter = greeter
- }));
+ throw new ArgumentNullException(nameof(app));
}
+
+ return app.UseMiddleware<HelloMiddleware>(Options.Create(new HelloOptions
+ {
+ Greeter = greeter
+ }));
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloMiddleware.cs b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloMiddleware.cs
index 2494ae7ea9..901f43ff3d 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloMiddleware.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloMiddleware.cs
@@ -9,37 +9,36 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
-namespace RoutingWebSite.HelloExtension
+namespace RoutingWebSite.HelloExtension;
+
+public class HelloMiddleware
{
- public class HelloMiddleware
+ private readonly RequestDelegate _next;
+ private readonly HelloOptions _helloOptions;
+ private readonly byte[] _helloPayload;
+
+ public HelloMiddleware(RequestDelegate next, IOptions<HelloOptions> helloOptions)
{
- private readonly RequestDelegate _next;
- private readonly HelloOptions _helloOptions;
- private readonly byte[] _helloPayload;
+ _next = next;
+ _helloOptions = helloOptions.Value;
- public HelloMiddleware(RequestDelegate next, IOptions<HelloOptions> helloOptions)
+ var payload = new List<byte>();
+ payload.AddRange(Encoding.UTF8.GetBytes("Hello"));
+ if (!string.IsNullOrEmpty(_helloOptions.Greeter))
{
- _next = next;
- _helloOptions = helloOptions.Value;
-
- var payload = new List<byte>();
- payload.AddRange(Encoding.UTF8.GetBytes("Hello"));
- if (!string.IsNullOrEmpty(_helloOptions.Greeter))
- {
- payload.Add((byte)' ');
- payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter));
- }
- _helloPayload = payload.ToArray();
+ payload.Add((byte)' ');
+ payload.AddRange(Encoding.UTF8.GetBytes(_helloOptions.Greeter));
}
+ _helloPayload = payload.ToArray();
+ }
- public Task InvokeAsync(HttpContext context)
- {
- var response = context.Response;
- var payloadLength = _helloPayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_helloPayload, 0, payloadLength);
- }
+ public Task InvokeAsync(HttpContext context)
+ {
+ var response = context.Response;
+ var payloadLength = _helloPayload.Length;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ response.ContentLength = payloadLength;
+ return response.Body.WriteAsync(_helloPayload, 0, payloadLength);
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloOptions.cs b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloOptions.cs
index 7a216ddb36..d66b682572 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloOptions.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/HelloExtension/HelloOptions.cs
@@ -1,10 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace RoutingWebSite.HelloExtension
+namespace RoutingWebSite.HelloExtension;
+
+public class HelloOptions
{
- public class HelloOptions
- {
- public string Greeter { get; set; }
- }
+ public string Greeter { get; set; }
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/MapFallbackStartup.cs b/src/Http/Routing/test/testassets/RoutingWebSite/MapFallbackStartup.cs
index e3d02e5567..ca381179e9 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/MapFallbackStartup.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/MapFallbackStartup.cs
@@ -5,32 +5,31 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
-namespace RoutingWebSite
+namespace RoutingWebSite;
+
+public class MapFallbackStartup
{
- public class MapFallbackStartup
+ public void ConfigureServices(IServiceCollection services)
{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddRouting();
- }
+ services.AddRouting();
+ }
- public void Configure(IApplicationBuilder app)
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
{
- app.UseRouting();
- app.UseEndpoints(endpoints =>
+ endpoints.MapFallback("/prefix/{*path:nonfile}", (context) =>
{
- endpoints.MapFallback("/prefix/{*path:nonfile}", (context) =>
- {
- return context.Response.WriteAsync("FallbackCustomPattern");
- });
-
- endpoints.MapFallback((context) =>
- {
- return context.Response.WriteAsync("FallbackDefaultPattern");
- });
+ return context.Response.WriteAsync("FallbackCustomPattern");
+ });
- endpoints.MapHello("/helloworld", "World");
+ endpoints.MapFallback((context) =>
+ {
+ return context.Response.WriteAsync("FallbackDefaultPattern");
});
- }
+
+ endpoints.MapHello("/helloworld", "World");
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/Program.cs b/src/Http/Routing/test/testassets/RoutingWebSite/Program.cs
index 594a6384c3..697d24735f 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/Program.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/Program.cs
@@ -9,72 +9,71 @@ using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
-namespace RoutingWebSite
+namespace RoutingWebSite;
+
+public class Program
{
- public class Program
+ public const string EndpointRoutingScenario = "endpointrouting";
+ public const string RouterScenario = "router";
+
+ public static Task Main(string[] args)
{
- public const string EndpointRoutingScenario = "endpointrouting";
- public const string RouterScenario = "router";
+ var host = GetHostBuilder(args).Build();
+ return host.RunAsync();
+ }
- public static Task Main(string[] args)
+ // For unit testing
+ public static IHostBuilder GetHostBuilder(string[] args)
+ {
+ string scenario;
+ if (args.Length == 0)
{
- var host = GetHostBuilder(args).Build();
- return host.RunAsync();
- }
+ Console.WriteLine("Choose a sample to run:");
+ Console.WriteLine($"1. {EndpointRoutingScenario}");
+ Console.WriteLine($"2. {RouterScenario}");
+ Console.WriteLine();
- // For unit testing
- public static IHostBuilder GetHostBuilder(string[] args)
+ scenario = Console.ReadLine();
+ }
+ else
{
- string scenario;
- if (args.Length == 0)
- {
- Console.WriteLine("Choose a sample to run:");
- Console.WriteLine($"1. {EndpointRoutingScenario}");
- Console.WriteLine($"2. {RouterScenario}");
- Console.WriteLine();
-
- scenario = Console.ReadLine();
- }
- else
- {
- scenario = args[0];
- }
-
- Type startupType;
- switch (scenario)
- {
- case "1":
- case EndpointRoutingScenario:
- startupType = typeof(UseEndpointRoutingStartup);
- break;
+ scenario = args[0];
+ }
- case "2":
- case RouterScenario:
- startupType = typeof(UseRouterStartup);
- break;
+ Type startupType;
+ switch (scenario)
+ {
+ case "1":
+ case EndpointRoutingScenario:
+ startupType = typeof(UseEndpointRoutingStartup);
+ break;
- default:
- Console.WriteLine($"unknown scenario {scenario}");
- Console.WriteLine($"usage: dotnet run -- ({EndpointRoutingScenario}|{RouterScenario})");
- throw new InvalidOperationException();
+ case "2":
+ case RouterScenario:
+ startupType = typeof(UseRouterStartup);
+ break;
- }
+ default:
+ Console.WriteLine($"unknown scenario {scenario}");
+ Console.WriteLine($"usage: dotnet run -- ({EndpointRoutingScenario}|{RouterScenario})");
+ throw new InvalidOperationException();
- return new HostBuilder()
- .ConfigureWebHost(webHostBuilder =>
- {
- webHostBuilder
- .UseKestrel()
- .UseIISIntegration()
- .UseContentRoot(Environment.CurrentDirectory)
- .UseStartup(startupType)
- .UseTestServer();
- })
- .ConfigureLogging(b =>
- {
- b.AddConsole();
- b.SetMinimumLevel(LogLevel.Critical);
- });
}
+
+ return new HostBuilder()
+ .ConfigureWebHost(webHostBuilder =>
+ {
+ webHostBuilder
+ .UseKestrel()
+ .UseIISIntegration()
+ .UseContentRoot(Environment.CurrentDirectory)
+ .UseStartup(startupType)
+ .UseTestServer();
+ })
+ .ConfigureLogging(b =>
+ {
+ b.AddConsole();
+ b.SetMinimumLevel(LogLevel.Critical);
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/UseEndpointRoutingStartup.cs b/src/Http/Routing/test/testassets/RoutingWebSite/UseEndpointRoutingStartup.cs
index 67a9f27f32..62744ce1e0 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/UseEndpointRoutingStartup.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/UseEndpointRoutingStartup.cs
@@ -16,163 +16,162 @@ using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
-namespace RoutingWebSite
+namespace RoutingWebSite;
+
+public class UseEndpointRoutingStartup
{
- public class UseEndpointRoutingStartup
+ private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!");
+
+ public void ConfigureServices(IServiceCollection services)
{
- private static readonly byte[] _plainTextPayload = Encoding.UTF8.GetBytes("Plain text!");
+ services.AddTransient<EndsWithStringRouteConstraint>();
- public void ConfigureServices(IServiceCollection services)
+ services.AddRouting(options =>
{
- services.AddTransient<EndsWithStringRouteConstraint>();
+ options.ConstraintMap.Add("endsWith", typeof(EndsWithStringRouteConstraint));
+ });
+ }
- services.AddRouting(options =>
- {
- options.ConstraintMap.Add("endsWith", typeof(EndsWithStringRouteConstraint));
- });
- }
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseStaticFiles();
- public void Configure(IApplicationBuilder app)
- {
- app.UseStaticFiles();
+ app.UseRouting();
- app.UseRouting();
+ app.Map("/Branch1", branch => SetupBranch(branch, "Branch1"));
+ app.Map("/Branch2", branch => SetupBranch(branch, "Branch2"));
- app.Map("/Branch1", branch => SetupBranch(branch, "Branch1"));
- app.Map("/Branch2", branch => SetupBranch(branch, "Branch2"));
+ // Imagine some more stuff here...
- // Imagine some more stuff here...
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapHello("/helloworld", "World");
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapHello("/helloworld", "World");
+ endpoints.MapGet(
+ "/",
+ (httpContext) =>
+ {
+ var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
- endpoints.MapGet(
- "/",
- (httpContext) =>
- {
- var dataSource = httpContext.RequestServices.GetRequiredService<EndpointDataSource>();
-
- var sb = new StringBuilder();
- sb.AppendLine("Endpoints:");
- foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
- {
- sb.AppendLine(FormattableString.Invariant($"- {endpoint.RoutePattern.RawText}"));
- }
-
- var response = httpContext.Response;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- return response.WriteAsync(sb.ToString());
- });
- endpoints.MapGet(
- "/plaintext",
- (httpContext) =>
- {
- var response = httpContext.Response;
- var payloadLength = _plainTextPayload.Length;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- response.ContentLength = payloadLength;
- return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
- });
- endpoints.MapGet(
- "/convention",
- (httpContext) =>
- {
- var endpoint = httpContext.GetEndpoint();
- return httpContext.Response.WriteAsync((endpoint.Metadata.GetMetadata<CustomMetadata>() != null) ? "Has metadata" : "No metadata");
- }).Add(b =>
- {
- b.Metadata.Add(new CustomMetadata());
- });
- endpoints.MapGet(
- "/withconstraints/{id:endsWith(_001)}",
- (httpContext) =>
- {
- var response = httpContext.Response;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- return response.WriteAsync("WithConstraints");
- });
- endpoints.MapGet(
- "/withoptionalconstraints/{id:endsWith(_001)?}",
- (httpContext) =>
- {
- var response = httpContext.Response;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- return response.WriteAsync("withoptionalconstraints");
- });
- endpoints.MapGet(
- "/WithSingleAsteriskCatchAll/{*path}",
- (httpContext) =>
+ var sb = new StringBuilder();
+ sb.AppendLine("Endpoints:");
+ foreach (var endpoint in dataSource.Endpoints.OfType<RouteEndpoint>().OrderBy(e => e.RoutePattern.RawText, StringComparer.OrdinalIgnoreCase))
{
- var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
-
- var response = httpContext.Response;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- return response.WriteAsync(
- "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
- }).WithMetadata(new RouteNameMetadata(routeName: "WithSingleAsteriskCatchAll"));
- endpoints.MapGet(
- "/WithDoubleAsteriskCatchAll/{**path}",
- (httpContext) =>
- {
- var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
-
- var response = httpContext.Response;
- response.StatusCode = 200;
- response.ContentType = "text/plain";
- return response.WriteAsync(
- "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
- }).WithMetadata(new RouteNameMetadata(routeName: "WithDoubleAsteriskCatchAll"));
-
- MapHostEndpoint(endpoints);
- MapHostEndpoint(endpoints, "*.0.0.1");
- MapHostEndpoint(endpoints, "127.0.0.1");
- MapHostEndpoint(endpoints, "*.0.0.1:5000", "*.0.0.1:5001");
- MapHostEndpoint(endpoints, "contoso.com:*", "*.contoso.com:*");
- });
- }
-
- private class CustomMetadata
- {
- }
+ sb.AppendLine(FormattableString.Invariant($"- {endpoint.RoutePattern.RawText}"));
+ }
- private IEndpointConventionBuilder MapHostEndpoint(IEndpointRouteBuilder endpoints, params string[] hosts)
- {
- var hostsDisplay = (hosts == null || hosts.Length == 0)
- ? "*:*"
- : string.Join(",", hosts.Select(h => h.Contains(':') ? h : h + ":*"));
-
- var conventionBuilder = endpoints.MapGet(
- "api/DomainWildcard",
- httpContext =>
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(sb.ToString());
+ });
+ endpoints.MapGet(
+ "/plaintext",
+ (httpContext) =>
{
var response = httpContext.Response;
+ var payloadLength = _plainTextPayload.Length;
response.StatusCode = 200;
response.ContentType = "text/plain";
- return response.WriteAsync(hostsDisplay);
+ response.ContentLength = payloadLength;
+ return response.Body.WriteAsync(_plainTextPayload, 0, payloadLength);
});
+ endpoints.MapGet(
+ "/convention",
+ (httpContext) =>
+ {
+ var endpoint = httpContext.GetEndpoint();
+ return httpContext.Response.WriteAsync((endpoint.Metadata.GetMetadata<CustomMetadata>() != null) ? "Has metadata" : "No metadata");
+ }).Add(b =>
+ {
+ b.Metadata.Add(new CustomMetadata());
+ });
+ endpoints.MapGet(
+ "/withconstraints/{id:endsWith(_001)}",
+ (httpContext) =>
+ {
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync("WithConstraints");
+ });
+ endpoints.MapGet(
+ "/withoptionalconstraints/{id:endsWith(_001)?}",
+ (httpContext) =>
+ {
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync("withoptionalconstraints");
+ });
+ endpoints.MapGet(
+ "/WithSingleAsteriskCatchAll/{*path}",
+ (httpContext) =>
+ {
+ var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
+
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(
+ "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithSingleAsteriskCatchAll", new { }));
+ }).WithMetadata(new RouteNameMetadata(routeName: "WithSingleAsteriskCatchAll"));
+ endpoints.MapGet(
+ "/WithDoubleAsteriskCatchAll/{**path}",
+ (httpContext) =>
+ {
+ var linkGenerator = httpContext.RequestServices.GetRequiredService<LinkGenerator>();
+
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(
+ "Link: " + linkGenerator.GetPathByRouteValues(httpContext, "WithDoubleAsteriskCatchAll", new { }));
+ }).WithMetadata(new RouteNameMetadata(routeName: "WithDoubleAsteriskCatchAll"));
+
+ MapHostEndpoint(endpoints);
+ MapHostEndpoint(endpoints, "*.0.0.1");
+ MapHostEndpoint(endpoints, "127.0.0.1");
+ MapHostEndpoint(endpoints, "*.0.0.1:5000", "*.0.0.1:5001");
+ MapHostEndpoint(endpoints, "contoso.com:*", "*.contoso.com:*");
+ });
+ }
- conventionBuilder.Add(endpointBuilder =>
+ private class CustomMetadata
+ {
+ }
+
+ private IEndpointConventionBuilder MapHostEndpoint(IEndpointRouteBuilder endpoints, params string[] hosts)
+ {
+ var hostsDisplay = (hosts == null || hosts.Length == 0)
+ ? "*:*"
+ : string.Join(",", hosts.Select(h => h.Contains(':') ? h : h + ":*"));
+
+ var conventionBuilder = endpoints.MapGet(
+ "api/DomainWildcard",
+ httpContext =>
{
- endpointBuilder.Metadata.Add(new HostAttribute(hosts));
- endpointBuilder.DisplayName += " HOST: " + hostsDisplay;
+ var response = httpContext.Response;
+ response.StatusCode = 200;
+ response.ContentType = "text/plain";
+ return response.WriteAsync(hostsDisplay);
});
- return conventionBuilder;
- }
+ conventionBuilder.Add(endpointBuilder =>
+ {
+ endpointBuilder.Metadata.Add(new HostAttribute(hosts));
+ endpointBuilder.DisplayName += " HOST: " + hostsDisplay;
+ });
+
+ return conventionBuilder;
+ }
- private void SetupBranch(IApplicationBuilder app, string name)
+ private void SetupBranch(IApplicationBuilder app, string name)
+ {
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
{
- app.UseRouting();
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapGet("api/get/{id}", (context) => context.Response.WriteAsync($"{name} - API Get {context.Request.RouteValues["id"]}"));
- });
- }
+ endpoints.MapGet("api/get/{id}", (context) => context.Response.WriteAsync($"{name} - API Get {context.Request.RouteValues["id"]}"));
+ });
}
}
diff --git a/src/Http/Routing/test/testassets/RoutingWebSite/UseRouterStartup.cs b/src/Http/Routing/test/testassets/RoutingWebSite/UseRouterStartup.cs
index 82b0236fd2..edc223a152 100644
--- a/src/Http/Routing/test/testassets/RoutingWebSite/UseRouterStartup.cs
+++ b/src/Http/Routing/test/testassets/RoutingWebSite/UseRouterStartup.cs
@@ -9,46 +9,45 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
-namespace RoutingWebSite
+namespace RoutingWebSite;
+
+public class UseRouterStartup
{
- public class UseRouterStartup
- {
- private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
+ private static readonly TimeSpan RegexMatchTimeout = TimeSpan.FromSeconds(10);
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddRouting();
- }
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddRouting();
+ }
- public void Configure(IApplicationBuilder app)
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseRouter(routes =>
{
- app.UseRouter(routes =>
+ routes.DefaultHandler = new RouteHandler((httpContext) =>
{
- routes.DefaultHandler = new RouteHandler((httpContext) =>
- {
- var request = httpContext.Request;
- return httpContext.Response.WriteAsync($"Verb = {request.Method.ToUpperInvariant()} - Path = {request.Path} - Route values - {string.Join(", ", httpContext.GetRouteData().Values)}");
- });
-
- routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"API Get {routeData.Values["id"]}"))
- .MapMiddlewareRoute("api/middleware", (appBuilder) => appBuilder.Run(httpContext => httpContext.Response.WriteAsync("Middleware!")))
- .MapRoute(
- name: "AllVerbs",
- template: "api/all/{name}/{lastName?}",
- defaults: new { lastName = "Doe" },
- constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
+ var request = httpContext.Request;
+ return httpContext.Response.WriteAsync($"Verb = {request.Method.ToUpperInvariant()} - Path = {request.Path} - Route values - {string.Join(", ", httpContext.GetRouteData().Values)}");
});
- app.Map("/Branch1", branch => SetupBranch(branch, "Branch1"));
- app.Map("/Branch2", branch => SetupBranch(branch, "Branch2"));
- }
+ routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"API Get {routeData.Values["id"]}"))
+ .MapMiddlewareRoute("api/middleware", (appBuilder) => appBuilder.Run(httpContext => httpContext.Response.WriteAsync("Middleware!")))
+ .MapRoute(
+ name: "AllVerbs",
+ template: "api/all/{name}/{lastName?}",
+ defaults: new { lastName = "Doe" },
+ constraints: new { lastName = new RegexRouteConstraint(new Regex("[a-zA-Z]{3}", RegexOptions.CultureInvariant, RegexMatchTimeout)) });
+ });
- private void SetupBranch(IApplicationBuilder app, string name)
+ app.Map("/Branch1", branch => SetupBranch(branch, "Branch1"));
+ app.Map("/Branch2", branch => SetupBranch(branch, "Branch2"));
+ }
+
+ private void SetupBranch(IApplicationBuilder app, string name)
+ {
+ app.UseRouter(routes =>
{
- app.UseRouter(routes =>
- {
- routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"{name} - API Get {routeData.Values["id"]}"));
- });
- }
+ routes.MapGet("api/get/{id}", (request, response, routeData) => response.WriteAsync($"{name} - API Get {routeData.Values["id"]}"));
+ });
}
}
diff --git a/src/Http/Routing/tools/Swaggatherer/Program.cs b/src/Http/Routing/tools/Swaggatherer/Program.cs
index b0eb68aa63..6dc96fad82 100644
--- a/src/Http/Routing/tools/Swaggatherer/Program.cs
+++ b/src/Http/Routing/tools/Swaggatherer/Program.cs
@@ -1,14 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Swaggatherer
+namespace Swaggatherer;
+
+internal static class Program
{
- internal static class Program
+ public static void Main(string[] args)
{
- public static void Main(string[] args)
- {
- var application = new SwaggathererApplication();
- application.Execute(args);
- }
+ var application = new SwaggathererApplication();
+ application.Execute(args);
}
}
diff --git a/src/Http/Routing/tools/Swaggatherer/RouteEntry.cs b/src/Http/Routing/tools/Swaggatherer/RouteEntry.cs
index 0a0a2110d7..fc954d364e 100644
--- a/src/Http/Routing/tools/Swaggatherer/RouteEntry.cs
+++ b/src/Http/Routing/tools/Swaggatherer/RouteEntry.cs
@@ -3,13 +3,12 @@
using Microsoft.AspNetCore.Routing.Template;
-namespace Swaggatherer
+namespace Swaggatherer;
+
+internal class RouteEntry
{
- internal class RouteEntry
- {
- public RouteTemplate Template { get; set; }
- public string Method { get; set; }
- public decimal Precedence { get; set; }
- public string RequestUrl { get; set; }
- }
+ public RouteTemplate Template { get; set; }
+ public string Method { get; set; }
+ public decimal Precedence { get; set; }
+ public string RequestUrl { get; set; }
}
diff --git a/src/Http/Routing/tools/Swaggatherer/SwaggathererApplication.cs b/src/Http/Routing/tools/Swaggatherer/SwaggathererApplication.cs
index e15e7b0bda..0b49bb15ad 100644
--- a/src/Http/Routing/tools/Swaggatherer/SwaggathererApplication.cs
+++ b/src/Http/Routing/tools/Swaggatherer/SwaggathererApplication.cs
@@ -10,252 +10,251 @@ using Microsoft.Extensions.CommandLineUtils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace Swaggatherer
+namespace Swaggatherer;
+
+internal class SwaggathererApplication : CommandLineApplication
{
- internal class SwaggathererApplication : CommandLineApplication
+ public SwaggathererApplication()
{
- public SwaggathererApplication()
- {
- Invoke = InvokeCore;
+ Invoke = InvokeCore;
+
+ HttpMethods = Option("-m|--method", "allow multiple endpoints with different http method", CommandOptionType.NoValue);
+ Input = Option("-i", "input swagger 2.0 JSON file", CommandOptionType.MultipleValue);
+ InputDirectory = Option("-d", "input directory", CommandOptionType.SingleValue);
+ Output = Option("-o", "output", CommandOptionType.SingleValue);
+
+ HelpOption("-h|--help");
+ }
+
+ public CommandOption Input { get; }
- HttpMethods = Option("-m|--method", "allow multiple endpoints with different http method", CommandOptionType.NoValue);
- Input = Option("-i", "input swagger 2.0 JSON file", CommandOptionType.MultipleValue);
- InputDirectory = Option("-d", "input directory", CommandOptionType.SingleValue);
- Output = Option("-o", "output", CommandOptionType.SingleValue);
+ public CommandOption InputDirectory { get; }
- HelpOption("-h|--help");
+ // Support multiple endpoints that are distinguished only by http method.
+ public CommandOption HttpMethods { get; }
+
+ public CommandOption Output { get; }
+
+ private int InvokeCore()
+ {
+ if (!Input.HasValue() && !InputDirectory.HasValue())
+ {
+ ShowHelp();
+ return 1;
}
- public CommandOption Input { get; }
+ if (Input.HasValue() && InputDirectory.HasValue())
+ {
+ ShowHelp();
+ return 1;
+ }
- public CommandOption InputDirectory { get; }
+ if (!Output.HasValue())
+ {
+ Output.Values.Add("Out.generated.cs");
+ }
- // Support multiple endpoints that are distinguished only by http method.
- public CommandOption HttpMethods { get; }
+ if (InputDirectory.HasValue())
+ {
+ Input.Values.AddRange(Directory.EnumerateFiles(InputDirectory.Value(), "*.json", SearchOption.AllDirectories));
+ }
- public CommandOption Output { get; }
+ Console.WriteLine($"Processing {Input.Values.Count} files...");
+ var entries = new List<RouteEntry>();
+ for (var i = 0; i < Input.Values.Count; i++)
+ {
+ var input = ReadInput(Input.Values[i]);
+ ParseEntries(input, entries);
+ }
- private int InvokeCore()
+ // We don't yet want to support complex segments.
+ for (var i = entries.Count - 1; i >= 0; i--)
{
- if (!Input.HasValue() && !InputDirectory.HasValue())
+ if (HasComplexSegment(entries[i]))
{
- ShowHelp();
- return 1;
+ Out.WriteLine("Skipping route with complex segment: " + entries[i].Template.TemplateText);
+ entries.RemoveAt(i);
}
+ }
- if (Input.HasValue() && InputDirectory.HasValue())
- {
- ShowHelp();
- return 1;
- }
+ // The data that we're provided by might be unambiguous.
+ // Remove any routes that would be ambiguous in our system.
+ var routesByPrecedence = new Dictionary<decimal, List<RouteEntry>>();
+ for (var i = entries.Count - 1; i >= 0; i--)
+ {
+ var entry = entries[i];
+ var precedence = RoutePrecedence.ComputeInbound(entries[i].Template);
- if (!Output.HasValue())
+ if (!routesByPrecedence.TryGetValue(precedence, out var matches))
{
- Output.Values.Add("Out.generated.cs");
+ matches = new List<RouteEntry>();
+ routesByPrecedence.Add(precedence, matches);
}
- if (InputDirectory.HasValue())
+ if (IsDuplicateTemplate(entry, matches))
{
- Input.Values.AddRange(Directory.EnumerateFiles(InputDirectory.Value(), "*.json", SearchOption.AllDirectories));
+ Out.WriteLine("Duplicate route template: " + entries[i].Template.TemplateText);
+ entries.RemoveAt(i);
+ continue;
}
- Console.WriteLine($"Processing {Input.Values.Count} files...");
- var entries = new List<RouteEntry>();
- for (var i = 0; i < Input.Values.Count; i++)
- {
- var input = ReadInput(Input.Values[i]);
- ParseEntries(input, entries);
- }
+ matches.Add(entry);
+ }
- // We don't yet want to support complex segments.
- for (var i = entries.Count - 1; i >= 0; i--)
+ // We're not too sophisticated with how we generate parameter values, just hoping for
+ // the best. For parameters we generate a segment that is the same length as the parameter name
+ // but with a minimum of 5 characters to avoid collisions.
+ for (var i = entries.Count - 1; i >= 0; i--)
+ {
+ entries[i].RequestUrl = GenerateRequestUrl(entries[i].Template);
+ if (entries[i].RequestUrl == null)
{
- if (HasComplexSegment(entries[i]))
- {
- Out.WriteLine("Skipping route with complex segment: " + entries[i].Template.TemplateText);
- entries.RemoveAt(i);
- }
+ Out.WriteLine("Failed to create a request for: " + entries[i].Template.TemplateText);
+ entries.RemoveAt(i);
+ continue;
}
+ }
- // The data that we're provided by might be unambiguous.
- // Remove any routes that would be ambiguous in our system.
- var routesByPrecedence = new Dictionary<decimal, List<RouteEntry>>();
- for (var i = entries.Count - 1; i >= 0; i--)
- {
- var entry = entries[i];
- var precedence = RoutePrecedence.ComputeInbound(entries[i].Template);
-
- if (!routesByPrecedence.TryGetValue(precedence, out var matches))
- {
- matches = new List<RouteEntry>();
- routesByPrecedence.Add(precedence, matches);
- }
+ Sort(entries);
- if (IsDuplicateTemplate(entry, matches))
- {
- Out.WriteLine("Duplicate route template: " + entries[i].Template.TemplateText);
- entries.RemoveAt(i);
- continue;
- }
+ var text = Template.Execute(entries);
+ File.WriteAllText(Output.Value(), text);
+ return 0;
+ }
- matches.Add(entry);
+ private JObject ReadInput(string input)
+ {
+ using (var reader = File.OpenText(input))
+ {
+ try
+ {
+ return JObject.Load(new JsonTextReader(reader));
}
-
- // We're not too sophisticated with how we generate parameter values, just hoping for
- // the best. For parameters we generate a segment that is the same length as the parameter name
- // but with a minimum of 5 characters to avoid collisions.
- for (var i = entries.Count - 1; i >= 0; i--)
+ catch (JsonReaderException ex)
{
- entries[i].RequestUrl = GenerateRequestUrl(entries[i].Template);
- if (entries[i].RequestUrl == null)
- {
- Out.WriteLine("Failed to create a request for: " + entries[i].Template.TemplateText);
- entries.RemoveAt(i);
- continue;
- }
+ Out.WriteLine($"Error reading: {input}");
+ Out.WriteLine(ex);
+ return new JObject();
}
-
- Sort(entries);
-
- var text = Template.Execute(entries);
- File.WriteAllText(Output.Value(), text);
- return 0;
}
+ }
- private JObject ReadInput(string input)
+ private void ParseEntries(JObject input, List<RouteEntry> entries)
+ {
+ var basePath = "";
+ if (input["basePath"] is JProperty basePathProperty)
{
- using (var reader = File.OpenText(input))
- {
- try
- {
- return JObject.Load(new JsonTextReader(reader));
- }
- catch (JsonReaderException ex)
- {
- Out.WriteLine($"Error reading: {input}");
- Out.WriteLine(ex);
- return new JObject();
- }
- }
+ basePath = basePathProperty.Value<string>();
}
- private void ParseEntries(JObject input, List<RouteEntry> entries)
+ if (input["paths"] is JObject paths)
{
- var basePath = "";
- if (input["basePath"] is JProperty basePathProperty)
+ foreach (var path in paths.Properties())
{
- basePath = basePathProperty.Value<string>();
- }
-
- if (input["paths"] is JObject paths)
- {
- foreach (var path in paths.Properties())
+ foreach (var method in ((JObject)path.Value).Properties())
{
- foreach (var method in ((JObject)path.Value).Properties())
+ var template = basePath + path.Name;
+ var parsed = TemplateParser.Parse(template);
+ entries.Add(new RouteEntry()
{
- var template = basePath + path.Name;
- var parsed = TemplateParser.Parse(template);
- entries.Add(new RouteEntry()
- {
- Method = HttpMethods.HasValue() ? method.Name.ToString() : null,
- Template = parsed,
- Precedence = RoutePrecedence.ComputeInbound(parsed),
- });
- }
+ Method = HttpMethods.HasValue() ? method.Name.ToString() : null,
+ Template = parsed,
+ Precedence = RoutePrecedence.ComputeInbound(parsed),
+ });
}
}
}
+ }
- private bool HasComplexSegment(RouteEntry entry)
+ private bool HasComplexSegment(RouteEntry entry)
+ {
+ for (var i = 0; i < entry.Template.Segments.Count; i++)
{
- for (var i = 0; i < entry.Template.Segments.Count; i++)
+ if (!entry.Template.Segments[i].IsSimple)
{
- if (!entry.Template.Segments[i].IsSimple)
- {
- return true;
- }
+ return true;
}
-
- return false;
}
- private bool IsDuplicateTemplate(RouteEntry entry, List<RouteEntry> others)
+ return false;
+ }
+
+ private bool IsDuplicateTemplate(RouteEntry entry, List<RouteEntry> others)
+ {
+ for (var j = 0; j < others.Count; j++)
{
- for (var j = 0; j < others.Count; j++)
- {
- // This is another route with the same precedence. It is guaranteed to have the same number of segments
- // of the same kinds and in the same order. We just need to check the literals.
- var other = others[j];
+ // This is another route with the same precedence. It is guaranteed to have the same number of segments
+ // of the same kinds and in the same order. We just need to check the literals.
+ var other = others[j];
- var isSame = true;
- for (var k = 0; k < entry.Template.Segments.Count; k++)
+ var isSame = true;
+ for (var k = 0; k < entry.Template.Segments.Count; k++)
+ {
+ if (!string.Equals(
+ entry.Template.Segments[k].Parts[0].Text,
+ other.Template.Segments[k].Parts[0].Text,
+ StringComparison.OrdinalIgnoreCase))
{
- if (!string.Equals(
- entry.Template.Segments[k].Parts[0].Text,
- other.Template.Segments[k].Parts[0].Text,
- StringComparison.OrdinalIgnoreCase))
- {
- isSame = false;
- break;
- }
-
- if (HttpMethods.HasValue() &&
- !string.Equals(entry.Method, other.Method, StringComparison.OrdinalIgnoreCase))
- {
- isSame = false;
- break;
- }
+ isSame = false;
+ break;
}
- if (isSame)
+ if (HttpMethods.HasValue() &&
+ !string.Equals(entry.Method, other.Method, StringComparison.OrdinalIgnoreCase))
{
- return true;
+ isSame = false;
+ break;
}
}
- return false;
- }
-
- private static void Sort(List<RouteEntry> entries)
- {
- // We need to sort these in precedence order for the linear matchers.
- entries.Sort((x, y) =>
+ if (isSame)
{
- var comparison = RoutePrecedence.ComputeInbound(x.Template).CompareTo(RoutePrecedence.ComputeInbound(y.Template));
- if (comparison != 0)
- {
- return comparison;
- }
-
- return string.Compare(x.Template.TemplateText, y.Template.TemplateText, StringComparison.Ordinal);
- });
+ return true;
+ }
}
- private static string GenerateRequestUrl(RouteTemplate template)
+ return false;
+ }
+
+ private static void Sort(List<RouteEntry> entries)
+ {
+ // We need to sort these in precedence order for the linear matchers.
+ entries.Sort((x, y) =>
{
- if (template.Segments.Count == 0)
+ var comparison = RoutePrecedence.ComputeInbound(x.Template).CompareTo(RoutePrecedence.ComputeInbound(y.Template));
+ if (comparison != 0)
{
- return "/";
+ return comparison;
}
- var url = new StringBuilder();
- for (var i = 0; i < template.Segments.Count; i++)
- {
- // We don't yet handle complex segments
- var part = template.Segments[i].Parts[0];
-
- url.Append('/');
- url.Append(part.IsLiteral ? part.Text : GenerateParameterValue(part));
- }
+ return string.Compare(x.Template.TemplateText, y.Template.TemplateText, StringComparison.Ordinal);
+ });
+ }
- return url.ToString();
+ private static string GenerateRequestUrl(RouteTemplate template)
+ {
+ if (template.Segments.Count == 0)
+ {
+ return "/";
}
- private static string GenerateParameterValue(TemplatePart part)
+ var url = new StringBuilder();
+ for (var i = 0; i < template.Segments.Count; i++)
{
- var text = Guid.NewGuid().ToString();
- var length = Math.Min(text.Length, Math.Max(5, part.Name.Length));
- return text.Substring(0, length);
+ // We don't yet handle complex segments
+ var part = template.Segments[i].Parts[0];
+
+ url.Append('/');
+ url.Append(part.IsLiteral ? part.Text : GenerateParameterValue(part));
}
+
+ return url.ToString();
+ }
+
+ private static string GenerateParameterValue(TemplatePart part)
+ {
+ var text = Guid.NewGuid().ToString();
+ var length = Math.Min(text.Length, Math.Max(5, part.Name.Length));
+ return text.Substring(0, length);
}
}
diff --git a/src/Http/Routing/tools/Swaggatherer/Template.cs b/src/Http/Routing/tools/Swaggatherer/Template.cs
index 8f2ba4c198..ef1ee847d7 100644
--- a/src/Http/Routing/tools/Swaggatherer/Template.cs
+++ b/src/Http/Routing/tools/Swaggatherer/Template.cs
@@ -5,66 +5,66 @@ using System;
using System.Collections.Generic;
using System.Globalization;
-namespace Swaggatherer
+namespace Swaggatherer;
+
+internal static class Template
{
- internal static class Template
+ public static string Execute(IReadOnlyList<RouteEntry> entries)
{
- public static string Execute(IReadOnlyList<RouteEntry> entries)
+ var controllerCount = 0;
+ var templatesVisited = new Dictionary<string, (int ControllerIndex, int ActionIndex)>(
+ StringComparer.OrdinalIgnoreCase);
+
+ var setupEndpointsLines = new List<string>();
+ for (var i = 0; i < entries.Count; i++)
{
- var controllerCount = 0;
- var templatesVisited = new Dictionary<string, (int ControllerIndex, int ActionIndex)>(
- StringComparer.OrdinalIgnoreCase);
+ var entry = entries[i];
- var setupEndpointsLines = new List<string>();
- for (var i = 0; i < entries.Count; i++)
+ // In attribute routing, same template is used for all actions within that controller. The following
+ // simulates that where we only increment the controller count when a new endpoint for a new template
+ // is being created.
+ var template = entry.Template.TemplateText;
+ if (!templatesVisited.TryGetValue(template, out var visitedTemplateInfo))
{
- var entry = entries[i];
-
- // In attribute routing, same template is used for all actions within that controller. The following
- // simulates that where we only increment the controller count when a new endpoint for a new template
- // is being created.
- var template = entry.Template.TemplateText;
- if (!templatesVisited.TryGetValue(template, out var visitedTemplateInfo))
- {
- controllerCount++;
- visitedTemplateInfo = (controllerCount, 0);
- }
-
- // Increment the action count within a controller template
- visitedTemplateInfo.ActionIndex++;
- templatesVisited[template] = visitedTemplateInfo;
-
- var controllerName = $"Controller{visitedTemplateInfo.ControllerIndex}";
- var actionName = $"Action{visitedTemplateInfo.ActionIndex}";
-
- var httpMethodText = entry.Method == null ? "httpMethod: null" : $"\"{entry.Method.ToUpperInvariant()}\"";
- setupEndpointsLines.Add($" Endpoints[{i}] = CreateEndpoint(\"{template}\", \"{controllerName}\", \"{actionName}\", {httpMethodText});");
+ controllerCount++;
+ visitedTemplateInfo = (controllerCount, 0);
}
- var setupRequestsLines = new List<string>();
- for (var i = 0; i < entries.Count; i++)
- {
- var entry = entries[i];
- setupRequestsLines.Add($" Requests[{i}] = new DefaultHttpContext();");
- setupRequestsLines.Add($" Requests[{i}].RequestServices = CreateServices();");
+ // Increment the action count within a controller template
+ visitedTemplateInfo.ActionIndex++;
+ templatesVisited[template] = visitedTemplateInfo;
- if (entry.Method != null)
- {
- setupRequestsLines.Add($" Requests[{i}].Request.Method = HttpMethods.GetCanonicalizedValue({entries[i].Method});");
- }
+ var controllerName = $"Controller{visitedTemplateInfo.ControllerIndex}";
+ var actionName = $"Action{visitedTemplateInfo.ActionIndex}";
- setupRequestsLines.Add($" Requests[{i}].Request.Path = \"{entries[i].RequestUrl}\";");
- }
+ var httpMethodText = entry.Method == null ? "httpMethod: null" : $"\"{entry.Method.ToUpperInvariant()}\"";
+ setupEndpointsLines.Add($" Endpoints[{i}] = CreateEndpoint(\"{template}\", \"{controllerName}\", \"{actionName}\", {httpMethodText});");
+ }
+
+ var setupRequestsLines = new List<string>();
+ for (var i = 0; i < entries.Count; i++)
+ {
+ var entry = entries[i];
+ setupRequestsLines.Add($" Requests[{i}] = new DefaultHttpContext();");
+ setupRequestsLines.Add($" Requests[{i}].RequestServices = CreateServices();");
- var setupMatcherLines = new List<string>();
- for (var i = 0; i < entries.Count; i++)
+ if (entry.Method != null)
{
- setupMatcherLines.Add($" builder.AddEndpoint(Endpoints[{i}]);");
+ setupRequestsLines.Add($" Requests[{i}].Request.Method = HttpMethods.GetCanonicalizedValue({entries[i].Method});");
}
- return string.Format(
- CultureInfo.InvariantCulture,
- @"
+ setupRequestsLines.Add($" Requests[{i}].Request.Path = \"{entries[i].RequestUrl}\";");
+ }
+
+ var setupMatcherLines = new List<string>();
+ for (var i = 0; i < entries.Count; i++)
+ {
+ setupMatcherLines.Add($" builder.AddEndpoint(Endpoints[{i}]);");
+ }
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ @"
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
@@ -129,10 +129,9 @@ namespace Microsoft.AspNetCore.Routing
}}
}}
}}",
- string.Join(Environment.NewLine, setupEndpointsLines),
- string.Join(Environment.NewLine, setupRequestsLines),
- string.Join(Environment.NewLine, setupMatcherLines),
- entries.Count);
- }
+string.Join(Environment.NewLine, setupEndpointsLines),
+string.Join(Environment.NewLine, setupRequestsLines),
+string.Join(Environment.NewLine, setupMatcherLines),
+entries.Count);
}
}
diff --git a/src/Http/Shared/CookieHeaderParserShared.cs b/src/Http/Shared/CookieHeaderParserShared.cs
index 9f9c3fac4a..f107a04b8e 100644
--- a/src/Http/Shared/CookieHeaderParserShared.cs
+++ b/src/Http/Shared/CookieHeaderParserShared.cs
@@ -5,237 +5,236 @@ using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal static class CookieHeaderParserShared
{
- internal static class CookieHeaderParserShared
+ public static bool TryParseValues(StringValues values, IDictionary<string, string> store, bool enableCookieNameEncoding, bool supportsMultipleValues)
{
- public static bool TryParseValues(StringValues values, IDictionary<string, string> store, bool enableCookieNameEncoding, bool 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.
+ if (values.Count == 0)
{
- // 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.
- if (values.Count == 0)
- {
- return false;
- }
- var hasFoundValue = false;
-
- for (var i = 0; i < values.Count; i++)
- {
- var value = values[i];
- var index = 0;
-
- while (!string.IsNullOrEmpty(value) && index < value.Length)
- {
- if (TryParseValue(value, ref index, supportsMultipleValues, out var parsedName, out var parsedValue))
- {
- // The entry may not contain an actual value, like " , "
- var name = enableCookieNameEncoding ? Uri.UnescapeDataString(parsedName.Value.Value!) : parsedName.Value.Value!;
- store[name] = Uri.UnescapeDataString(parsedValue.Value.Value!);
- hasFoundValue = true;
- }
- else
- {
- // Skip the invalid values and keep trying.
- index++;
- }
- }
- }
-
- return hasFoundValue;
+ return false;
}
+ var hasFoundValue = false;
- public static bool TryParseValue(StringSegment value, ref int index, bool supportsMultipleValues, [NotNullWhen(true)] out StringSegment? parsedName, [NotNullWhen(true)] out StringSegment? parsedValue)
+ for (var i = 0; i < values.Count; i++)
{
- parsedName = null;
- parsedValue = null;
+ var value = values[i];
+ var index = 0;
- // 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))
+ while (!string.IsNullOrEmpty(value) && index < value.Length)
{
- return supportsMultipleValues;
- }
-
- var current = GetNextNonEmptyOrWhitespaceIndex(value, index, supportsMultipleValues, out var separatorFound);
-
- if (separatorFound && !supportsMultipleValues)
- {
- return false; // leading separators not allowed if we don't support multiple values.
- }
-
- if (current == value.Length)
- {
- if (supportsMultipleValues)
+ if (TryParseValue(value, ref index, supportsMultipleValues, out var parsedName, out var parsedValue))
+ {
+ // The entry may not contain an actual value, like " , "
+ var name = enableCookieNameEncoding ? Uri.UnescapeDataString(parsedName.Value.Value!) : parsedName.Value.Value!;
+ store[name] = Uri.UnescapeDataString(parsedValue.Value.Value!);
+ hasFoundValue = true;
+ }
+ else
{
- index = current;
+ // Skip the invalid values and keep trying.
+ index++;
}
- return supportsMultipleValues;
}
+ }
- if (!TryGetCookieLength(value, ref current, out parsedName, out parsedValue))
- {
- return false;
- }
+ return hasFoundValue;
+ }
- current = GetNextNonEmptyOrWhitespaceIndex(value, current, supportsMultipleValues, out separatorFound);
+ public static bool TryParseValue(StringSegment value, ref int index, bool supportsMultipleValues, [NotNullWhen(true)] out StringSegment? parsedName, [NotNullWhen(true)] out StringSegment? parsedValue)
+ {
+ parsedName = null;
+ 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 current = GetNextNonEmptyOrWhitespaceIndex(value, index, supportsMultipleValues, out var separatorFound);
+
+ if (separatorFound && !supportsMultipleValues)
+ {
+ return false; // leading separators not allowed if we don't support multiple values.
+ }
- // 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)))
+ if (current == value.Length)
+ {
+ if (supportsMultipleValues)
{
- return false;
+ index = current;
}
+ return supportsMultipleValues;
+ }
- index = current;
-
- return true;
+ if (!TryGetCookieLength(value, ref current, out parsedName, out parsedValue))
+ {
+ return false;
}
- private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int startIndex, bool skipEmptyValues, out bool separatorFound)
+ 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)))
{
- Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
+ return false;
+ }
- separatorFound = false;
- var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+ index = current;
- if ((current == input.Length) || (input[current] != ',' && input[current] != ';'))
- {
- return current;
- }
+ return true;
+ }
- // 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);
+ private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int startIndex, bool skipEmptyValues, out bool separatorFound)
+ {
+ Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
- 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);
- }
- }
+ separatorFound = false;
+ var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+ if ((current == input.Length) || (input[current] != ',' && input[current] != ';'))
+ {
return current;
}
- // name=value; name="value"
- internal static bool TryGetCookieLength(StringSegment input, ref int offset, [NotNullWhen(true)] out StringSegment? parsedName, [NotNullWhen(true)] out StringSegment? parsedValue)
- {
- Contract.Requires(offset >= 0);
-
- parsedName = null;
- parsedValue = null;
+ // 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 (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
+ if (skipEmptyValues)
+ {
+ // Most headers only split on ',', but cookies primarily split on ';'
+ while ((current < input.Length) && ((input[current] == ',') || (input[current] == ';')))
{
- return false;
+ current++; // skip delimiter.
+ current = current + HttpRuleParser.GetWhitespaceLength(input, current);
}
+ }
- // The caller should have already consumed any leading whitespace, commas, etc..
+ return current;
+ }
- // Name=value;
+ // name=value; name="value"
+ internal static bool TryGetCookieLength(StringSegment input, ref int offset, [NotNullWhen(true)] out StringSegment? parsedName, [NotNullWhen(true)] out StringSegment? parsedValue)
+ {
+ Contract.Requires(offset >= 0);
- // Name
- var itemLength = HttpRuleParser.GetTokenLength(input, offset);
- if (itemLength == 0)
- {
- return false;
- }
+ parsedName = null;
+ parsedValue = null;
- parsedName = input.Subsegment(offset, itemLength);
- offset += itemLength;
+ if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
+ {
+ return false;
+ }
- // = (no spaces)
- if (!ReadEqualsSign(input, ref offset))
- {
- return false;
- }
+ // The caller should have already consumed any leading whitespace, commas, etc..
- // value or "quoted value"
- // The value may be empty
- parsedValue = GetCookieValue(input, ref offset);
+ // Name=value;
- return true;
+ // Name
+ var itemLength = HttpRuleParser.GetTokenLength(input, offset);
+ if (itemLength == 0)
+ {
+ return false;
}
- // 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)
+ parsedName = input.Subsegment(offset, itemLength);
+ offset += itemLength;
+
+ // = (no spaces)
+ if (!ReadEqualsSign(input, ref offset))
{
- Contract.Requires(offset >= 0);
- Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - offset)));
+ return false;
+ }
- var startIndex = offset;
+ // value or "quoted value"
+ // The value may be empty
+ parsedValue = GetCookieValue(input, ref offset);
- if (offset >= input.Length)
- {
- return StringSegment.Empty;
- }
- var inQuotes = false;
+ return true;
+ }
- if (input[offset] == '"')
- {
- inQuotes = true;
- offset++;
- }
+ // 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(offset >= 0);
+ Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - offset)));
- while (offset < input.Length)
- {
- var c = input[offset];
- if (!IsCookieValueChar(c))
- {
- break;
- }
+ var startIndex = offset;
- offset++;
- }
+ if (offset >= input.Length)
+ {
+ return StringSegment.Empty;
+ }
+ var inQuotes = false;
- if (inQuotes)
- {
- if (offset == input.Length || input[offset] != '"')
- {
- // Missing final quote
- return StringSegment.Empty;
- }
- offset++;
- }
+ if (input[offset] == '"')
+ {
+ inQuotes = true;
+ offset++;
+ }
- var length = offset - startIndex;
- if (offset > startIndex)
+ while (offset < input.Length)
+ {
+ var c = input[offset];
+ if (!IsCookieValueChar(c))
{
- return input.Subsegment(startIndex, length);
+ break;
}
- return StringSegment.Empty;
+ offset++;
}
- private static bool ReadEqualsSign(StringSegment input, ref int offset)
+ if (inQuotes)
{
- // = (no spaces)
- if (offset >= input.Length || input[offset] != '=')
+ if (offset == input.Length || input[offset] != '"')
{
- return false;
+ // Missing final quote
+ return StringSegment.Empty;
}
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)
+ var length = offset - startIndex;
+ if (offset > startIndex)
{
- if (c < 0x21 || c > 0x7E)
- {
- return false;
- }
- return !(c == '"' || c == ',' || c == ';' || c == '\\');
+ 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 == '\\');
}
}
diff --git a/src/Http/Shared/HttpParseResult.cs b/src/Http/Shared/HttpParseResult.cs
index ce550ed193..af01f87546 100644
--- a/src/Http/Shared/HttpParseResult.cs
+++ b/src/Http/Shared/HttpParseResult.cs
@@ -1,12 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal enum HttpParseResult
{
- internal enum HttpParseResult
- {
- Parsed,
- NotParsed,
- InvalidFormat,
- }
+ Parsed,
+ NotParsed,
+ InvalidFormat,
}
diff --git a/src/Http/Shared/HttpRuleParser.cs b/src/Http/Shared/HttpRuleParser.cs
index 42cc2e1a2e..02b9b4dd09 100644
--- a/src/Http/Shared/HttpRuleParser.cs
+++ b/src/Http/Shared/HttpRuleParser.cs
@@ -7,13 +7,13 @@ using System.Globalization;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.Net.Http.Headers
+namespace Microsoft.Net.Http.Headers;
+
+internal static class HttpRuleParser
{
- internal static class HttpRuleParser
- {
- private static readonly bool[] TokenChars = CreateTokenChars();
- private const int MaxNestedCount = 5;
- private static readonly string[] DateFormats = new string[] {
+ 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
@@ -35,311 +35,310 @@ namespace Microsoft.Net.Http.Headers
"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");
+ 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;
- private static bool[] CreateTokenChars()
- {
- // token = 1*<any CHAR except CTLs or separators>
- // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ // iso-8859-1, Western European (ISO)
+ internal static readonly Encoding DefaultHttpEncoding = Encoding.GetEncoding("iso-8859-1");
- var tokenChars = new bool[128]; // everything is false
+ private static bool[] CreateTokenChars()
+ {
+ // token = 1*<any CHAR except CTLs or separators>
+ // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
- for (var i = 33; i < 127; i++) // skip Space (32) & DEL (127)
- {
- tokenChars[i] = true;
- }
+ var tokenChars = new bool[128]; // everything is false
- // 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;
+ for (var i = 33; i < 127; i++) // skip Space (32) & DEL (127)
+ {
+ tokenChars[i] = true;
}
- internal static bool IsTokenChar(char character)
- {
- // Must be between 'space' (32) and 'DEL' (127)
- if (character > 127)
- {
- return false;
- }
+ // 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;
+ }
- return TokenChars[character];
+ internal static bool IsTokenChar(char character)
+ {
+ // Must be between 'space' (32) and 'DEL' (127)
+ if (character > 127)
+ {
+ return false;
}
- [Pure]
- internal static int GetTokenLength(StringSegment input, int startIndex)
+ return TokenChars[character];
+ }
+
+ [Pure]
+ internal static int GetTokenLength(StringSegment input, int startIndex)
+ {
+ Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
+
+ if (startIndex >= input.Length)
{
- Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
+ return 0;
+ }
+
+ var current = startIndex;
- if (startIndex >= input.Length)
+ while (current < input.Length)
+ {
+ if (!IsTokenChar(input[current]))
{
- return 0;
+ return current - startIndex;
}
+ current++;
+ }
+ return input.Length - startIndex;
+ }
- var current = startIndex;
+ internal static int GetWhitespaceLength(StringSegment input, int startIndex)
+ {
+ Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
- while (current < input.Length)
- {
- if (!IsTokenChar(input[current]))
- {
- return current - startIndex;
- }
- current++;
- }
- return input.Length - startIndex;
+ if (startIndex >= input.Length)
+ {
+ return 0;
}
- internal static int GetWhitespaceLength(StringSegment input, int startIndex)
+ var current = startIndex;
+
+ char c;
+ while (current < input.Length)
{
- Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
+ c = input[current];
- if (startIndex >= input.Length)
+ if ((c == SP) || (c == Tab))
{
- return 0;
+ current++;
+ continue;
}
- var current = startIndex;
-
- char c;
- while (current < input.Length)
+ if (c == CR)
{
- c = input[current];
-
- if ((c == SP) || (c == Tab))
+ // 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))
{
- 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))
+ var spaceOrTab = input[current + 2];
+ if ((spaceOrTab == SP) || (spaceOrTab == Tab))
{
- var spaceOrTab = input[current + 2];
- if ((spaceOrTab == SP) || (spaceOrTab == Tab))
- {
- current += 3;
- continue;
- }
+ current += 3;
+ continue;
}
}
-
- return current - startIndex;
}
- // All characters between startIndex and the end of the string are LWS characters.
- return input.Length - startIndex;
+ 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((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;
}
- internal static int GetNumberLength(StringSegment input, int startIndex, bool allowDecimal)
+ while (current < input.Length)
{
- 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] == '.')
+ c = input[current];
+ if ((c >= '0') && (c <= '9'))
{
- return 0;
+ current++;
}
-
- while (current < input.Length)
+ else if (!haveDot && (c == '.'))
{
- c = input[current];
- if ((c >= '0') && (c <= '9'))
- {
- current++;
- }
- else if (!haveDot && (c == '.'))
- {
- // Note that value "1." is valid.
- haveDot = true;
- current++;
- }
- else
- {
- break;
- }
+ // 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)
+ 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((startIndex >= 0) && (startIndex < input.Length));
+ Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) &&
+ (Contract.ValueAtReturn(out length) <= (input.Length - startIndex)));
+
+ length = 0;
+
+ if (input[startIndex] != '\\')
{
- var nestedCount = 0;
- return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length);
+ return HttpParseResult.NotParsed;
}
- // quoted-pair = "\" CHAR
- // CHAR = <any US-ASCII character (octets 0 - 127)>
- internal static HttpParseResult GetQuotedPairLength(StringSegment input, int startIndex, out int length)
+ // Quoted-char has 2 characters. Check whether 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))
{
- Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
- Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) &&
- (Contract.ValueAtReturn(out length) <= (input.Length - startIndex)));
+ return HttpParseResult.InvalidFormat;
+ }
- length = 0;
+ // We don't care what the char next to '\' is.
+ length = 2;
+ return HttpParseResult.Parsed;
+ }
- if (input[startIndex] != '\\')
- {
- return HttpParseResult.NotParsed;
- }
+ // 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));
- // Quoted-char has 2 characters. Check whether 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;
- }
+ length = 0;
- // We don't care what the char next to '\' is.
- length = 2;
- return HttpParseResult.Parsed;
+ if (input[startIndex] != openChar)
+ {
+ return HttpParseResult.NotParsed;
}
- // 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)
+ var current = startIndex + 1; // Start parsing with the character next to the first open-char
+ while (current < input.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)
+ // 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))
{
- return HttpParseResult.NotParsed;
+ // 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;
}
- var current = startIndex + 1; // Start parsing with the character next to the first open-char
- while (current < input.Length)
+ // If we support nested expressions and we find an open-char, then parse the nested expressions.
+ if (supportsNesting && (input[current] == openChar))
{
- // 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
{
- nestedCount++;
- try
+ // Check if we exceeded the number of nested calls.
+ if (nestedCount > MaxNestedCount)
{
- // Check if we exceeded the number of nested calls.
- if (nestedCount > MaxNestedCount)
- {
- return HttpParseResult.InvalidFormat;
- }
-
- var nestedLength = 0;
- var 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;
- }
+ return HttpParseResult.InvalidFormat;
}
- finally
+
+ var nestedLength = 0;
+ var nestedResult = GetExpressionLength(input, current, openChar, closeChar,
+ supportsNesting, ref nestedCount, out nestedLength);
+
+ switch (nestedResult)
{
- nestedCount--;
+ 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;
}
}
-
- if (input[current] == closeChar)
+ finally
{
- length = current - startIndex + 1;
- return HttpParseResult.Parsed;
+ nestedCount--;
}
- current++;
}
- // We didn't see the final quote, therefore we have an invalid expression string.
- return HttpParseResult.InvalidFormat;
+ 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/Shared/StreamCopyOperationInternal.cs b/src/Http/Shared/StreamCopyOperationInternal.cs
index 50e22fcfa9..128069dcac 100644
--- a/src/Http/Shared/StreamCopyOperationInternal.cs
+++ b/src/Http/Shared/StreamCopyOperationInternal.cs
@@ -8,80 +8,79 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.Http
+namespace Microsoft.AspNetCore.Http;
+
+// FYI: In most cases the source will be a FileStream and the destination will be to the network.
+internal static class StreamCopyOperationInternal
{
- // FYI: In most cases the source will be a FileStream and the destination will be to the network.
- internal static class StreamCopyOperationInternal
+ private const int DefaultBufferSize = 4096;
+
+ /// <summary>Asynchronously reads the given number of 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)
{
- private const int DefaultBufferSize = 4096;
+ return CopyToAsync(source, destination, count, DefaultBufferSize, cancel);
+ }
- /// <summary>Asynchronously reads the given number of 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 given number of 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)
+ {
+ var bytesRemaining = count;
- /// <summary>Asynchronously reads the given number of 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)
+ var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+ try
{
- var bytesRemaining = count;
+ Debug.Assert(source != null);
+ Debug.Assert(destination != null);
+ Debug.Assert(!bytesRemaining.HasValue || bytesRemaining.GetValueOrDefault() >= 0);
+ Debug.Assert(buffer != null);
- var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
- try
+ while (true)
{
- Debug.Assert(source != null);
- Debug.Assert(destination != null);
- Debug.Assert(!bytesRemaining.HasValue || bytesRemaining.GetValueOrDefault() >= 0);
- Debug.Assert(buffer != null);
-
- while (true)
+ // The natural end of the range.
+ if (bytesRemaining.HasValue && bytesRemaining.GetValueOrDefault() <= 0)
{
- // The natural end of the range.
- if (bytesRemaining.HasValue && bytesRemaining.GetValueOrDefault() <= 0)
- {
- return;
- }
+ return;
+ }
- cancel.ThrowIfCancellationRequested();
+ cancel.ThrowIfCancellationRequested();
- var readLength = buffer.Length;
- if (bytesRemaining.HasValue)
- {
- readLength = (int)Math.Min(bytesRemaining.GetValueOrDefault(), (long)readLength);
- }
- var read = await source.ReadAsync(buffer.AsMemory(0, readLength), cancel);
+ var readLength = buffer.Length;
+ if (bytesRemaining.HasValue)
+ {
+ readLength = (int)Math.Min(bytesRemaining.GetValueOrDefault(), (long)readLength);
+ }
+ var read = await source.ReadAsync(buffer.AsMemory(0, readLength), cancel);
- if (bytesRemaining.HasValue)
- {
- bytesRemaining -= read;
- }
+ if (bytesRemaining.HasValue)
+ {
+ bytesRemaining -= read;
+ }
- // End of the source stream.
- if (read == 0)
- {
- return;
- }
+ // End of the source stream.
+ if (read == 0)
+ {
+ return;
+ }
- cancel.ThrowIfCancellationRequested();
+ cancel.ThrowIfCancellationRequested();
- await destination.WriteAsync(buffer.AsMemory(0, read), cancel);
- }
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(buffer);
+ await destination.WriteAsync(buffer.AsMemory(0, read), cancel);
}
}
+ finally
+ {
+ ArrayPool<byte>.Shared.Return(buffer);
+ }
}
}
diff --git a/src/Http/WebUtilities/perf/Microbenchmarks/FormPipeReaderInternalsBenchmark.cs b/src/Http/WebUtilities/perf/Microbenchmarks/FormPipeReaderInternalsBenchmark.cs
index 8b3c20c2c0..fc3137bff9 100644
--- a/src/Http/WebUtilities/perf/Microbenchmarks/FormPipeReaderInternalsBenchmark.cs
+++ b/src/Http/WebUtilities/perf/Microbenchmarks/FormPipeReaderInternalsBenchmark.cs
@@ -5,40 +5,39 @@ using System.Buffers;
using System.Text;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.WebUtilities.Microbenchmarks
+namespace Microsoft.AspNetCore.WebUtilities.Microbenchmarks;
+
+/// <summary>
+/// Test internal parsing speed of FormPipeReader without pipe
+/// </summary>
+public class FormPipeReaderInternalsBenchmark
{
- /// <summary>
- /// Test internal parsing speed of FormPipeReader without pipe
- /// </summary>
- public class FormPipeReaderInternalsBenchmark
+ private readonly byte[] _singleUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=boo&haha=hehe&lol=temp");
+ private readonly byte[] _firstUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=bo");
+ private readonly byte[] _secondUtf8 = Encoding.UTF8.GetBytes("o&haha=hehe&lol=temp");
+ private FormPipeReader _formPipeReader;
+
+ [IterationSetup]
+ public void Setup()
+ {
+ _formPipeReader = new FormPipeReader(null);
+ }
+
+ [Benchmark]
+ public void ReadUtf8Data()
{
- private readonly byte[] _singleUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=boo&haha=hehe&lol=temp");
- private readonly byte[] _firstUtf8 = Encoding.UTF8.GetBytes("foo=bar&baz=bo");
- private readonly byte[] _secondUtf8 = Encoding.UTF8.GetBytes("o&haha=hehe&lol=temp");
- private FormPipeReader _formPipeReader;
-
- [IterationSetup]
- public void Setup()
- {
- _formPipeReader = new FormPipeReader(null);
- }
-
- [Benchmark]
- public void ReadUtf8Data()
- {
- var buffer = new ReadOnlySequence<byte>(_singleUtf8);
- KeyValueAccumulator accum = default;
-
- _formPipeReader.ParseFormValues(ref buffer, ref accum, isFinalBlock: true);
- }
-
- [Benchmark]
- public void ReadUtf8MultipleBlockData()
- {
- var buffer = ReadOnlySequenceFactory.CreateSegments(_firstUtf8, _secondUtf8);
- KeyValueAccumulator accum = default;
-
- _formPipeReader.ParseFormValues(ref buffer, ref accum, isFinalBlock: true);
- }
+ var buffer = new ReadOnlySequence<byte>(_singleUtf8);
+ KeyValueAccumulator accum = default;
+
+ _formPipeReader.ParseFormValues(ref buffer, ref accum, isFinalBlock: true);
+ }
+
+ [Benchmark]
+ public void ReadUtf8MultipleBlockData()
+ {
+ var buffer = ReadOnlySequenceFactory.CreateSegments(_firstUtf8, _secondUtf8);
+ KeyValueAccumulator accum = default;
+
+ _formPipeReader.ParseFormValues(ref buffer, ref accum, isFinalBlock: true);
}
}
diff --git a/src/Http/WebUtilities/perf/Microbenchmarks/FormReaderBenchmark.cs b/src/Http/WebUtilities/perf/Microbenchmarks/FormReaderBenchmark.cs
index d520dd60fa..8d7e59a44f 100644
--- a/src/Http/WebUtilities/perf/Microbenchmarks/FormReaderBenchmark.cs
+++ b/src/Http/WebUtilities/perf/Microbenchmarks/FormReaderBenchmark.cs
@@ -11,39 +11,38 @@ using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class FormReaderBenchmark
{
- public class FormReaderBenchmark
+ [Benchmark]
+ public async Task ReadSmallFormAsyncStream()
{
- [Benchmark]
- public async Task ReadSmallFormAsyncStream()
- {
- var bytes = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
- var stream = new MemoryStream(bytes);
+ var bytes = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
+ var stream = new MemoryStream(bytes);
- for (var i = 0; i < 1000; i++)
- {
- var formReader = new FormReader(stream);
- await formReader.ReadFormAsync();
- stream.Position = 0;
- }
+ for (var i = 0; i < 1000; i++)
+ {
+ var formReader = new FormReader(stream);
+ await formReader.ReadFormAsync();
+ stream.Position = 0;
}
+ }
- [Benchmark]
- public async Task ReadSmallFormAsyncPipe()
- {
- var pipe = new Pipe();
- var bytes = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
+ [Benchmark]
+ public async Task ReadSmallFormAsyncPipe()
+ {
+ var pipe = new Pipe();
+ var bytes = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
- for (var i = 0; i < 1000; i++)
- {
- pipe.Writer.Write(bytes);
- pipe.Writer.Complete();
- var formReader = new FormPipeReader(pipe.Reader);
- await formReader.ReadFormAsync();
- pipe.Reader.Complete();
- pipe.Reset();
- }
+ for (var i = 0; i < 1000; i++)
+ {
+ pipe.Writer.Write(bytes);
+ pipe.Writer.Complete();
+ var formReader = new FormPipeReader(pipe.Reader);
+ await formReader.ReadFormAsync();
+ pipe.Reader.Complete();
+ pipe.Reset();
}
}
}
diff --git a/src/Http/WebUtilities/perf/Microbenchmarks/HttpRequestStreamReaderReadLineBenchmark.cs b/src/Http/WebUtilities/perf/Microbenchmarks/HttpRequestStreamReaderReadLineBenchmark.cs
index a4a6c7aa85..f90d48caf4 100644
--- a/src/Http/WebUtilities/perf/Microbenchmarks/HttpRequestStreamReaderReadLineBenchmark.cs
+++ b/src/Http/WebUtilities/perf/Microbenchmarks/HttpRequestStreamReaderReadLineBenchmark.cs
@@ -7,49 +7,48 @@ using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class HttpRequestStreamReaderReadLineBenchmark
{
- public class HttpRequestStreamReaderReadLineBenchmark
+ private MemoryStream _stream;
+
+ [Params(200, 1000, 1025, 1600)] // Default buffer length is 1024
+ public int Length { get; set; }
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ var data = new char[Length];
+
+ data[Length - 2] = '\r';
+ data[Length - 1] = '\n';
+
+ _stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
+ }
+
+ [Benchmark]
+ public async Task<string> ReadLineAsync()
+ {
+ var reader = CreateReader();
+ var result = await reader.ReadLineAsync();
+ Debug.Assert(result.Length == Length - 2);
+ return result;
+ }
+
+ [Benchmark]
+ public string ReadLine()
+ {
+ var reader = CreateReader();
+ var result = reader.ReadLine();
+ Debug.Assert(result.Length == Length - 2);
+ return result;
+ }
+
+ [Benchmark]
+ public HttpRequestStreamReader CreateReader()
{
- private MemoryStream _stream;
-
- [Params(200, 1000, 1025, 1600)] // Default buffer length is 1024
- public int Length { get; set; }
-
- [GlobalSetup]
- public void GlobalSetup()
- {
- var data = new char[Length];
-
- data[Length - 2] = '\r';
- data[Length - 1] = '\n';
-
- _stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
- }
-
- [Benchmark]
- public async Task<string> ReadLineAsync()
- {
- var reader = CreateReader();
- var result = await reader.ReadLineAsync();
- Debug.Assert(result.Length == Length - 2);
- return result;
- }
-
- [Benchmark]
- public string ReadLine()
- {
- var reader = CreateReader();
- var result = reader.ReadLine();
- Debug.Assert(result.Length == Length - 2);
- return result;
- }
-
- [Benchmark]
- public HttpRequestStreamReader CreateReader()
- {
- _stream.Seek(0, SeekOrigin.Begin);
- return new HttpRequestStreamReader(_stream, Encoding.UTF8);
- }
+ _stream.Seek(0, SeekOrigin.Begin);
+ return new HttpRequestStreamReader(_stream, Encoding.UTF8);
}
}
diff --git a/src/Http/WebUtilities/src/AspNetCoreTempDirectory.cs b/src/Http/WebUtilities/src/AspNetCoreTempDirectory.cs
index 77944c647e..6a0fcc04c6 100644
--- a/src/Http/WebUtilities/src/AspNetCoreTempDirectory.cs
+++ b/src/Http/WebUtilities/src/AspNetCoreTempDirectory.cs
@@ -6,34 +6,33 @@
using System;
using System.IO;
-namespace Microsoft.AspNetCore.Internal
+namespace Microsoft.AspNetCore.Internal;
+
+internal static class AspNetCoreTempDirectory
{
- internal static class AspNetCoreTempDirectory
- {
- private static string? _tempDirectory;
+ private static string? _tempDirectory;
- public static string TempDirectory
+ public static string TempDirectory
+ {
+ get
{
- get
+ if (_tempDirectory == null)
{
- 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))
- {
- throw new DirectoryNotFoundException(temp);
- }
+ // Look for folders in the following order.
+ var temp = Environment.GetEnvironmentVariable("ASPNETCORE_TEMP") ?? // ASPNETCORE_TEMP - User set temporary location.
+ Path.GetTempPath(); // Fall back.
- _tempDirectory = temp;
+ if (!Directory.Exists(temp))
+ {
+ throw new DirectoryNotFoundException(temp);
}
- return _tempDirectory;
+ _tempDirectory = temp;
}
- }
- public static Func<string> TempDirectoryFactory => () => TempDirectory;
+ return _tempDirectory;
+ }
}
+
+ public static Func<string> TempDirectoryFactory => () => TempDirectory;
}
diff --git a/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs b/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs
index 8c601b2668..13450fbb18 100644
--- a/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs
+++ b/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs
@@ -1,33 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Encodes and decodes using base64 url encoding.
+/// </summary>
+public static class Base64UrlTextEncoder
{
/// <summary>
- /// Encodes and decodes using base64 url encoding.
+ /// Encodes supplied data into Base64 and replaces any URL encodable characters into non-URL encodable
+ /// characters.
/// </summary>
- public static class Base64UrlTextEncoder
+ /// <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)
{
- /// <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);
- }
+ 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);
- }
+ /// <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
index e14c36b51d..4020c8242c 100644
--- a/src/Http/WebUtilities/src/BufferedReadStream.cs
+++ b/src/Http/WebUtilities/src/BufferedReadStream.cs
@@ -8,429 +8,428 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+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;
+ private int _bufferCount;
+ private bool _disposed;
+
/// <summary>
- /// A Stream that wraps another stream and allows reading lines.
- /// The data is buffered in memory.
+ /// Creates a new stream.
/// </summary>
- public class BufferedReadStream : Stream
+ /// <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)
{
- 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;
- private int _bufferCount;
- 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)
+ /// <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)
{
- if (inner == null)
- {
- throw new ArgumentNullException(nameof(inner));
- }
-
- _inner = inner;
- _bytePool = bytePool;
- _buffer = bytePool.Rent(bufferSize);
+ throw new ArgumentNullException(nameof(inner));
}
- /// <summary>
- /// The currently buffered data.
- /// </summary>
- public ArraySegment<byte> BufferedData
- {
- get { return new ArraySegment<byte>(_buffer, _bufferOffset, _bufferCount); }
- }
+ _inner = inner;
+ _bytePool = bytePool;
+ _buffer = bytePool.Rent(bufferSize);
+ }
- /// <inheritdoc/>
- public override bool CanRead
- {
- get { return _inner.CanRead || _bufferCount > 0; }
- }
+ /// <summary>
+ /// The currently buffered data.
+ /// </summary>
+ public ArraySegment<byte> BufferedData
+ {
+ get { return new ArraySegment<byte>(_buffer, _bufferOffset, _bufferCount); }
+ }
- /// <inheritdoc/>
- public override bool CanSeek
- {
- get { return _inner.CanSeek; }
- }
+ /// <inheritdoc/>
+ public override bool CanRead
+ {
+ get { return _inner.CanRead || _bufferCount > 0; }
+ }
- /// <inheritdoc/>
- public override bool CanTimeout
- {
- get { return _inner.CanTimeout; }
- }
+ /// <inheritdoc/>
+ public override bool CanSeek
+ {
+ get { return _inner.CanSeek; }
+ }
- /// <inheritdoc/>
- public override bool CanWrite
- {
- get { return _inner.CanWrite; }
- }
+ /// <inheritdoc/>
+ public override bool CanTimeout
+ {
+ get { return _inner.CanTimeout; }
+ }
- /// <inheritdoc/>
- public override long Length
- {
- get { return _inner.Length; }
- }
+ /// <inheritdoc/>
+ public override bool CanWrite
+ {
+ get { return _inner.CanWrite; }
+ }
+
+ /// <inheritdoc/>
+ public override long Length
+ {
+ get { return _inner.Length; }
+ }
- /// <inheritdoc/>
- public override long Position
+ /// <inheritdoc/>
+ public override long Position
+ {
+ get { return _inner.Position - _bufferCount; }
+ set
{
- get { return _inner.Position - _bufferCount; }
- set
+ if (value < 0)
{
- if (value < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(value), value, "Position must be positive.");
- }
- if (value == Position)
- {
- return;
- }
+ throw new ArgumentOutOfRangeException(nameof(value), value, "Position must be positive.");
+ }
+ if (value == Position)
+ {
+ return;
+ }
- // Backwards?
- if (value <= _inner.Position)
+ // Backwards?
+ if (value <= _inner.Position)
+ {
+ // Forward within the buffer?
+ var innerOffset = (int)(_inner.Position - value);
+ if (innerOffset <= _bufferCount)
{
- // 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;
- }
+ // Yes, just skip some of the buffered data
+ _bufferOffset += innerOffset;
+ _bufferCount -= innerOffset;
}
else
{
- // Forward, reset the buffer
+ // No, 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)
+ else
{
- Position = Position + offset;
+ // Forward, reset the buffer
+ _bufferOffset = 0;
+ _bufferCount = 0;
+ _inner.Position = value;
}
- else // if (origin == SeekOrigin.End)
- {
- Position = Length + offset;
- }
- return Position;
}
+ }
- /// <inheritdoc/>
- public override void SetLength(long value)
+ /// <inheritdoc/>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
{
- _inner.SetLength(value);
+ Position = offset;
}
-
- /// <inheritdoc/>
- protected override void Dispose(bool disposing)
+ else if (origin == SeekOrigin.Current)
{
- if (!_disposed)
- {
- _disposed = true;
- _bytePool.Return(_buffer);
-
- if (disposing)
- {
- _inner.Dispose();
- }
- }
+ Position = Position + offset;
}
-
- /// <inheritdoc/>
- public override void Flush()
+ else // if (origin == SeekOrigin.End)
{
- _inner.Flush();
+ Position = Length + offset;
}
+ return Position;
+ }
- /// <inheritdoc/>
- public override Task FlushAsync(CancellationToken cancellationToken)
- {
- return _inner.FlushAsync(cancellationToken);
- }
+ /// <inheritdoc/>
+ public override void SetLength(long value)
+ {
+ _inner.SetLength(value);
+ }
- /// <inheritdoc/>
- public override void Write(byte[] buffer, int offset, int count)
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposed)
{
- _inner.Write(buffer, offset, count);
- }
+ _disposed = true;
+ _bytePool.Return(_buffer);
- /// <inheritdoc/>
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return _inner.WriteAsync(buffer, offset, count, cancellationToken);
+ if (disposing)
+ {
+ _inner.Dispose();
+ }
}
+ }
- /// <inheritdoc/>
- public override int Read(byte[] buffer, int offset, int count)
- {
- ValidateBuffer(buffer, offset, count);
+ /// <inheritdoc/>
+ public override void Flush()
+ {
+ _inner.Flush();
+ }
- // Drain buffer
- if (_bufferCount > 0)
- {
- int toCopy = Math.Min(_bufferCount, count);
- Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy);
- _bufferOffset += toCopy;
- _bufferCount -= toCopy;
- return toCopy;
- }
+ /// <inheritdoc/>
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ return _inner.FlushAsync(cancellationToken);
+ }
- return _inner.Read(buffer, offset, count);
- }
+ /// <inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ _inner.Write(buffer, offset, count);
+ }
- /// <inheritdoc/>
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ /// <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)
{
- ValidateBuffer(buffer, offset, count);
- return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ int toCopy = Math.Min(_bufferCount, count);
+ Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy);
+ _bufferOffset += toCopy;
+ _bufferCount -= toCopy;
+ return toCopy;
}
- /// <inheritdoc/>
- public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
- {
- // Drain buffer
- if (_bufferCount > 0)
- {
- int toCopy = Math.Min(_bufferCount, buffer.Length);
- _buffer.AsMemory(_bufferOffset, toCopy).CopyTo(buffer);
- _bufferOffset += toCopy;
- _bufferCount -= toCopy;
- return toCopy;
- }
+ return _inner.Read(buffer, offset, count);
+ }
+
+ /// <inheritdoc/>
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ ValidateBuffer(buffer, offset, count);
+ return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
- return await _inner.ReadAsync(buffer, cancellationToken);
+ /// <inheritdoc/>
+ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
+ {
+ // Drain buffer
+ if (_bufferCount > 0)
+ {
+ int toCopy = Math.Min(_bufferCount, buffer.Length);
+ _buffer.AsMemory(_bufferOffset, toCopy).CopyTo(buffer);
+ _bufferOffset += toCopy;
+ _bufferCount -= toCopy;
+ return toCopy;
}
- /// <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()
+ return await _inner.ReadAsync(buffer, 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)
{
- if (_bufferCount > 0)
- {
- return true;
- }
- // Downshift to make room
- _bufferOffset = 0;
- _bufferCount = _inner.Read(_buffer, 0, _buffer.Length);
- return _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)
+ /// <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)
{
- if (_bufferCount > 0)
- {
- return true;
- }
- // Downshift to make room
- _bufferOffset = 0;
- _bufferCount = await _inner.ReadAsync(_buffer.AsMemory(), cancellationToken);
- return _bufferCount > 0;
+ return true;
}
+ // Downshift to make room
+ _bufferOffset = 0;
+ _bufferCount = await _inner.ReadAsync(_buffer.AsMemory(), 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)
+ /// <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)
{
- if (minCount > _buffer.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length);
- }
- while (_bufferCount < minCount)
+ throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length);
+ }
+ while (_bufferCount < minCount)
+ {
+ // Downshift to make room
+ if (_bufferOffset > 0)
{
- // 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)
+ if (_bufferCount > 0)
{
- return false;
+ 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;
}
+ 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)
+ /// <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)
{
- if (minCount > _buffer.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length);
- }
- while (_bufferCount < minCount)
+ throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length);
+ }
+ while (_bufferCount < minCount)
+ {
+ // Downshift to make room
+ if (_bufferOffset > 0)
{
- // Downshift to make room
- if (_bufferOffset > 0)
+ if (_bufferCount > 0)
{
- if (_bufferCount > 0)
- {
- Buffer.BlockCopy(_buffer, _bufferOffset, _buffer, 0, _bufferCount);
- }
- _bufferOffset = 0;
- }
- int read = await _inner.ReadAsync(_buffer.AsMemory(_bufferOffset + _bufferCount, _buffer.Length - _bufferCount - _bufferOffset), cancellationToken);
- _bufferCount += read;
- if (read == 0)
- {
- return false;
+ Buffer.BlockCopy(_buffer, _bufferOffset, _buffer, 0, _bufferCount);
}
+ _bufferOffset = 0;
+ }
+ int read = await _inner.ReadAsync(_buffer.AsMemory(_bufferOffset + _bufferCount, _buffer.Length - _bufferCount - _bufferOffset), cancellationToken);
+ _bufferCount += read;
+ if (read == 0)
+ {
+ return false;
}
- return true;
}
+ 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)
+ /// <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))
{
- CheckDisposed();
- using (var builder = new MemoryStream(200))
- {
- bool foundCR = false, foundCRLF = false;
+ bool foundCR = false, foundCRLF = false;
- while (!foundCRLF && EnsureBuffered())
+ while (!foundCRLF && EnsureBuffered())
+ {
+ if (builder.Length > lengthLimit)
{
- if (builder.Length > lengthLimit)
- {
- throw new InvalidDataException($"Line length limit {lengthLimit} exceeded.");
- }
- ProcessLineChar(builder, ref foundCR, ref foundCRLF);
+ throw new InvalidDataException($"Line length limit {lengthLimit} exceeded.");
}
-
- return DecodeLine(builder, foundCRLF);
+ 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)
+ /// <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))
{
- CheckDisposed();
- using (var builder = new MemoryStream(200))
- {
- bool foundCR = false, foundCRLF = false;
+ bool foundCR = false, foundCRLF = false;
- while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
+ while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
+ {
+ if (builder.Length > lengthLimit)
{
- if (builder.Length > lengthLimit)
- {
- throw new InvalidDataException($"Line length limit {lengthLimit} exceeded.");
- }
-
- ProcessLineChar(builder, ref foundCR, ref foundCRLF);
+ throw new InvalidDataException($"Line length limit {lengthLimit} exceeded.");
}
- return DecodeLine(builder, foundCRLF);
+ ProcessLineChar(builder, ref foundCR, ref 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;
+ return DecodeLine(builder, foundCRLF);
}
+ }
- private static string DecodeLine(MemoryStream builder, bool 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)
{
- // Drop the final CRLF, if any
- var length = foundCRLF ? builder.Length - 2 : builder.Length;
- return Encoding.UTF8.GetString(builder.ToArray(), 0, (int)length);
+ foundCRLF = true;
+ return;
}
+ foundCR = b == CR;
+ }
+
+ private static 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()
+ private void CheckDisposed()
+ {
+ if (_disposed)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(BufferedReadStream));
- }
+ throw new ObjectDisposedException(nameof(BufferedReadStream));
}
+ }
- private static void ValidateBuffer(byte[] buffer, int offset, int count)
+ private static void ValidateBuffer(byte[] buffer, int offset, int count)
+ {
+ // Delegate most of our validation.
+ var ignored = new ArraySegment<byte>(buffer, offset, count);
+ if (count == 0)
{
- // 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.");
- }
+ 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
index 720b8921b5..96133c39e8 100644
--- a/src/Http/WebUtilities/src/FileBufferingReadStream.cs
+++ b/src/Http/WebUtilities/src/FileBufferingReadStream.cs
@@ -10,498 +10,497 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
-namespace Microsoft.AspNetCore.WebUtilities
+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;
+
/// <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.
+ /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
/// </summary>
- public class FileBufferingReadStream : Stream
+ /// <param name="inner">The wrapping <see cref="Stream" />.</param>
+ /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
+ public FileBufferingReadStream(Stream inner, int memoryThreshold)
+ : this(inner, memoryThreshold, bufferLimit: null, tempFileDirectoryAccessor: AspNetCoreTempDirectory.TempDirectoryFactory)
{
- 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;
-
- /// <summary>
- /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
- /// </summary>
- /// <param name="inner">The wrapping <see cref="Stream" />.</param>
- /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
- public FileBufferingReadStream(Stream inner, int memoryThreshold)
- : this(inner, memoryThreshold, bufferLimit: null, tempFileDirectoryAccessor: AspNetCoreTempDirectory.TempDirectoryFactory)
- {
- }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
+ /// </summary>
+ /// <param name="inner">The wrapping <see cref="Stream" />.</param>
+ /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
+ /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
+ /// <param name="tempFileDirectoryAccessor">Provides the temporary directory to which files are buffered to.</param>
+ public FileBufferingReadStream(
+ Stream inner,
+ int memoryThreshold,
+ long? bufferLimit,
+ Func<string> tempFileDirectoryAccessor)
+ : this(inner, memoryThreshold, bufferLimit, tempFileDirectoryAccessor, ArrayPool<byte>.Shared)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
- /// </summary>
- /// <param name="inner">The wrapping <see cref="Stream" />.</param>
- /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
- /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
- /// <param name="tempFileDirectoryAccessor">Provides the temporary directory to which files are buffered to.</param>
- public FileBufferingReadStream(
- Stream inner,
- int memoryThreshold,
- long? bufferLimit,
- Func<string> tempFileDirectoryAccessor)
- : this(inner, memoryThreshold, bufferLimit, tempFileDirectoryAccessor, ArrayPool<byte>.Shared)
+ /// <summary>
+ /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
+ /// </summary>
+ /// <param name="inner">The wrapping <see cref="Stream" />.</param>
+ /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
+ /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
+ /// <param name="tempFileDirectoryAccessor">Provides the temporary directory to which files are buffered to.</param>
+ /// <param name="bytePool">The <see cref="ArrayPool{T}"/> to use.</param>
+ public FileBufferingReadStream(
+ Stream inner,
+ int memoryThreshold,
+ long? bufferLimit,
+ Func<string> tempFileDirectoryAccessor,
+ ArrayPool<byte> bytePool)
+ {
+ if (inner == null)
{
+ throw new ArgumentNullException(nameof(inner));
}
- /// <summary>
- /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
- /// </summary>
- /// <param name="inner">The wrapping <see cref="Stream" />.</param>
- /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
- /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
- /// <param name="tempFileDirectoryAccessor">Provides the temporary directory to which files are buffered to.</param>
- /// <param name="bytePool">The <see cref="ArrayPool{T}"/> to use.</param>
- public FileBufferingReadStream(
- Stream inner,
- int memoryThreshold,
- long? bufferLimit,
- Func<string> tempFileDirectoryAccessor,
- ArrayPool<byte> bytePool)
+ if (tempFileDirectoryAccessor == null)
{
- 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;
+ throw new ArgumentNullException(nameof(tempFileDirectoryAccessor));
}
- /// <summary>
- /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
- /// </summary>
- /// <param name="inner">The wrapping <see cref="Stream" />.</param>
- /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
- /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
- /// <param name="tempFileDirectory">The temporary directory to which files are buffered to.</param>
- public FileBufferingReadStream(
- Stream inner,
- int memoryThreshold,
- long? bufferLimit,
- string tempFileDirectory)
- : this(inner, memoryThreshold, bufferLimit, tempFileDirectory, ArrayPool<byte>.Shared)
+ _bytePool = bytePool;
+ if (memoryThreshold <= _maxRentedBufferSize)
{
+ _rentedBuffer = bytePool.Rent(memoryThreshold);
+ _buffer = new MemoryStream(_rentedBuffer);
+ _buffer.SetLength(0);
}
-
- /// <summary>
- /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
- /// </summary>
- /// <param name="inner">The wrapping <see cref="Stream" />.</param>
- /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
- /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
- /// <param name="tempFileDirectory">The temporary directory to which files are buffered to.</param>
- /// <param name="bytePool">The <see cref="ArrayPool{T}"/> to use.</param>
- public FileBufferingReadStream(
- Stream inner,
- int memoryThreshold,
- long? bufferLimit,
- string tempFileDirectory,
- ArrayPool<byte> bytePool)
+ else
{
- if (inner == null)
- {
- throw new ArgumentNullException(nameof(inner));
- }
+ _buffer = new MemoryStream();
+ }
- if (tempFileDirectory == null)
- {
- throw new ArgumentNullException(nameof(tempFileDirectory));
- }
+ _inner = inner;
+ _memoryThreshold = memoryThreshold;
+ _bufferLimit = bufferLimit;
+ _tempFileDirectoryAccessor = tempFileDirectoryAccessor;
+ }
- _bytePool = bytePool;
- if (memoryThreshold <= _maxRentedBufferSize)
- {
- _rentedBuffer = bytePool.Rent(memoryThreshold);
- _buffer = new MemoryStream(_rentedBuffer);
- _buffer.SetLength(0);
- }
- else
- {
- _buffer = new MemoryStream();
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
+ /// </summary>
+ /// <param name="inner">The wrapping <see cref="Stream" />.</param>
+ /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
+ /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
+ /// <param name="tempFileDirectory">The temporary directory to which files are buffered to.</param>
+ public FileBufferingReadStream(
+ Stream inner,
+ int memoryThreshold,
+ long? bufferLimit,
+ string tempFileDirectory)
+ : this(inner, memoryThreshold, bufferLimit, tempFileDirectory, ArrayPool<byte>.Shared)
+ {
+ }
- _inner = inner;
- _memoryThreshold = memoryThreshold;
- _bufferLimit = bufferLimit;
- _tempFileDirectory = tempFileDirectory;
+ /// <summary>
+ /// Initializes a new instance of <see cref="FileBufferingReadStream" />.
+ /// </summary>
+ /// <param name="inner">The wrapping <see cref="Stream" />.</param>
+ /// <param name="memoryThreshold">The maximum size to buffer in memory.</param>
+ /// <param name="bufferLimit">The maximum size that will be buffered before this <see cref="Stream"/> throws.</param>
+ /// <param name="tempFileDirectory">The temporary directory to which files are buffered to.</param>
+ /// <param name="bytePool">The <see cref="ArrayPool{T}"/> to use.</param>
+ public FileBufferingReadStream(
+ Stream inner,
+ int memoryThreshold,
+ long? bufferLimit,
+ string tempFileDirectory,
+ ArrayPool<byte> bytePool)
+ {
+ if (inner == null)
+ {
+ throw new ArgumentNullException(nameof(inner));
}
- /// <summary>
- /// The maximum amount of memory in bytes to allocate before switching to a file on disk.
- /// </summary>
- /// <remarks>
- /// Defaults to 32kb.
- /// </remarks>
- public int MemoryThreshold => _memoryThreshold;
-
- /// <summary>
- /// Gets a value that determines if the contents are buffered entirely in memory.
- /// </summary>
- public bool InMemory
+ if (tempFileDirectory == null)
{
- get { return _inMemory; }
+ throw new ArgumentNullException(nameof(tempFileDirectory));
}
- /// <summary>
- /// Gets a value that determines where the contents are buffered on disk.
- /// </summary>
- public string? TempFileName
+ _bytePool = bytePool;
+ if (memoryThreshold <= _maxRentedBufferSize)
{
- get { return _tempFileName; }
+ _rentedBuffer = bytePool.Rent(memoryThreshold);
+ _buffer = new MemoryStream(_rentedBuffer);
+ _buffer.SetLength(0);
}
-
- /// <inheritdoc/>
- public override bool CanRead
+ else
{
- get { return true; }
+ _buffer = new MemoryStream();
}
- /// <inheritdoc/>
- public override bool CanSeek
+ _inner = inner;
+ _memoryThreshold = memoryThreshold;
+ _bufferLimit = bufferLimit;
+ _tempFileDirectory = tempFileDirectory;
+ }
+
+ /// <summary>
+ /// The maximum amount of memory in bytes to allocate before switching to a file on disk.
+ /// </summary>
+ /// <remarks>
+ /// Defaults to 32kb.
+ /// </remarks>
+ public int MemoryThreshold => _memoryThreshold;
+
+ /// <summary>
+ /// Gets a value that determines if the contents are buffered entirely in memory.
+ /// </summary>
+ public bool InMemory
+ {
+ get { return _inMemory; }
+ }
+
+ /// <summary>
+ /// Gets a value that determines where the contents are buffered on disk.
+ /// </summary>
+ public string? TempFileName
+ {
+ get { return _tempFileName; }
+ }
+
+ /// <inheritdoc/>
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ /// <inheritdoc/>
+ public override bool CanSeek
+ {
+ get { return true; }
+ }
+
+ /// <inheritdoc/>
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
+
+ /// <summary>
+ /// The total bytes read from and buffered by the stream so far, it will not represent the full
+ /// data length until the stream is fully buffered. e.g. using <c>stream.DrainAsync()</c>.
+ /// </summary>
+ public override long Length
+ {
+ get { return _buffer.Length; }
+ }
+
+ /// <inheritdoc/>
+ public override long Position
+ {
+ get { return _buffer.Position; }
+ // Note this will not allow seeking forward beyond the end of the buffer.
+ set
{
- get { return true; }
+ ThrowIfDisposed();
+ _buffer.Position = value;
}
+ }
- /// <inheritdoc/>
- public override bool CanWrite
+ /// <inheritdoc/>
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ ThrowIfDisposed();
+ if (!_completelyBuffered && origin == SeekOrigin.End)
{
- get { return false; }
+ // 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.");
}
-
- /// <summary>
- /// The total bytes read from and buffered by the stream so far, it will not represent the full
- /// data length until the stream is fully buffered. e.g. using <c>stream.DrainAsync()</c>.
- /// </summary>
- public override long Length
+ else if (!_completelyBuffered && origin == SeekOrigin.Current && offset + Position > Length)
{
- get { return _buffer.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.");
}
-
- /// <inheritdoc/>
- public override long Position
+ else if (!_completelyBuffered && origin == SeekOrigin.Begin && offset > Length)
{
- get { return _buffer.Position; }
- // Note this will not allow seeking forward beyond the end of the buffer.
- set
- {
- ThrowIfDisposed();
- _buffer.Position = value;
- }
+ // 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);
+ }
- /// <inheritdoc/>
- public override long Seek(long offset, SeekOrigin origin)
+ private Stream CreateTempFile()
+ {
+ if (_tempFileDirectory == null)
{
- 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);
+ Debug.Assert(_tempFileDirectoryAccessor != null);
+ _tempFileDirectory = _tempFileDirectoryAccessor();
+ Debug.Assert(_tempFileDirectory != null);
}
- 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);
+ }
- _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);
- }
+ /// <inheritdoc/>
+ public override int Read(Span<byte> buffer)
+ {
+ ThrowIfDisposed();
- /// <inheritdoc/>
- public override int Read(Span<byte> buffer)
+ if (_buffer.Position < _buffer.Length || _completelyBuffered)
{
- ThrowIfDisposed();
-
- if (_buffer.Position < _buffer.Length || _completelyBuffered)
- {
- // Just read from the buffer
- return _buffer.Read(buffer);
- }
+ // Just read from the buffer
+ return _buffer.Read(buffer);
+ }
- var read = _inner.Read(buffer);
+ var read = _inner.Read(buffer);
- if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
- {
- throw new IOException("Buffer limit exceeded.");
- }
+ if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
+ {
+ throw new IOException("Buffer limit exceeded.");
+ }
- // We're about to go over the threshold, switch to a file
- if (_inMemory && _memoryThreshold - read < _buffer.Length)
+ // We're about to go over the threshold, switch to a file
+ if (_inMemory && _memoryThreshold - read < _buffer.Length)
+ {
+ _inMemory = false;
+ var oldBuffer = _buffer;
+ _buffer = CreateTempFile();
+ if (_rentedBuffer == null)
{
- _inMemory = false;
- var oldBuffer = _buffer;
- _buffer = CreateTempFile();
- if (_rentedBuffer == null)
+ // Copy data from the in memory buffer to the file stream using a pooled buffer
+ oldBuffer.Position = 0;
+ var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
+ try
{
- // Copy data from the in memory buffer to the file stream using a pooled buffer
- oldBuffer.Position = 0;
- var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
- try
+ var copyRead = oldBuffer.Read(rentedBuffer);
+ while (copyRead > 0)
{
- var copyRead = oldBuffer.Read(rentedBuffer);
- while (copyRead > 0)
- {
- _buffer.Write(rentedBuffer.AsSpan(0, copyRead));
- copyRead = oldBuffer.Read(rentedBuffer);
- }
- }
- finally
- {
- _bytePool.Return(rentedBuffer);
+ _buffer.Write(rentedBuffer.AsSpan(0, copyRead));
+ copyRead = oldBuffer.Read(rentedBuffer);
}
}
- else
+ finally
{
- _buffer.Write(_rentedBuffer.AsSpan(0, (int)oldBuffer.Length));
- _bytePool.Return(_rentedBuffer);
- _rentedBuffer = null;
+ _bytePool.Return(rentedBuffer);
}
}
-
- if (read > 0)
- {
- _buffer.Write(buffer.Slice(0, read));
- }
else
{
- _completelyBuffered = true;
+ _buffer.Write(_rentedBuffer.AsSpan(0, (int)oldBuffer.Length));
+ _bytePool.Return(_rentedBuffer);
+ _rentedBuffer = null;
}
-
- return read;
}
- /// <inheritdoc/>
- public override int Read(byte[] buffer, int offset, int count)
+ if (read > 0)
{
- return Read(buffer.AsSpan(offset, count));
+ _buffer.Write(buffer.Slice(0, read));
}
-
- /// <inheritdoc/>
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ else
{
- return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ _completelyBuffered = true;
}
- /// <inheritdoc/>
- [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
- public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
- {
- ThrowIfDisposed();
+ return read;
+ }
- if (_buffer.Position < _buffer.Length || _completelyBuffered)
- {
- // Just read from the buffer
- return await _buffer.ReadAsync(buffer, cancellationToken);
- }
+ /// <inheritdoc/>
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return Read(buffer.AsSpan(offset, count));
+ }
- var read = await _inner.ReadAsync(buffer, cancellationToken);
+ /// <inheritdoc/>
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ }
- if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
- {
- throw new IOException("Buffer limit exceeded.");
- }
+ /// <inheritdoc/>
+ [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
+ public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ if (_buffer.Position < _buffer.Length || _completelyBuffered)
+ {
+ // Just read from the buffer
+ return await _buffer.ReadAsync(buffer, cancellationToken);
+ }
+
+ var read = await _inner.ReadAsync(buffer, cancellationToken);
- if (_inMemory && _memoryThreshold - read < _buffer.Length)
+ if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
+ {
+ throw new IOException("Buffer limit exceeded.");
+ }
+
+ if (_inMemory && _memoryThreshold - read < _buffer.Length)
+ {
+ _inMemory = false;
+ var oldBuffer = _buffer;
+ _buffer = CreateTempFile();
+ if (_rentedBuffer == null)
{
- _inMemory = false;
- var oldBuffer = _buffer;
- _buffer = CreateTempFile();
- if (_rentedBuffer == null)
+ oldBuffer.Position = 0;
+ var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
+ try
{
- oldBuffer.Position = 0;
- var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
- try
- {
- // oldBuffer is a MemoryStream, no need to do async reads.
- var copyRead = oldBuffer.Read(rentedBuffer);
- while (copyRead > 0)
- {
- await _buffer.WriteAsync(rentedBuffer.AsMemory(0, copyRead), cancellationToken);
- copyRead = oldBuffer.Read(rentedBuffer);
- }
- }
- finally
+ // oldBuffer is a MemoryStream, no need to do async reads.
+ var copyRead = oldBuffer.Read(rentedBuffer);
+ while (copyRead > 0)
{
- _bytePool.Return(rentedBuffer);
+ await _buffer.WriteAsync(rentedBuffer.AsMemory(0, copyRead), cancellationToken);
+ copyRead = oldBuffer.Read(rentedBuffer);
}
}
- else
+ finally
{
- await _buffer.WriteAsync(_rentedBuffer.AsMemory(0, (int)oldBuffer.Length), cancellationToken);
- _bytePool.Return(_rentedBuffer);
- _rentedBuffer = null;
+ _bytePool.Return(rentedBuffer);
}
}
-
- if (read > 0)
- {
- await _buffer.WriteAsync(buffer.Slice(0, read), cancellationToken);
- }
else
{
- _completelyBuffered = true;
+ await _buffer.WriteAsync(_rentedBuffer.AsMemory(0, (int)oldBuffer.Length), cancellationToken);
+ _bytePool.Return(_rentedBuffer);
+ _rentedBuffer = null;
}
-
- return read;
}
- /// <inheritdoc/>
- public override void Write(byte[] buffer, int offset, int count)
+ if (read > 0)
{
- throw new NotSupportedException();
+ await _buffer.WriteAsync(buffer.Slice(0, read), cancellationToken);
}
-
- /// <inheritdoc/>
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ else
{
- throw new NotSupportedException();
+ _completelyBuffered = true;
}
- /// <inheritdoc/>
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
+ return read;
+ }
+
+ /// <inheritdoc/>
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ public override void Flush()
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ {
+ // Set a minimum buffer size of 4K since the base Stream implementation has weird behavior when the stream is
+ // seekable *and* the length is 0 (it passes in a buffer size of 1).
+ // See https://github.com/dotnet/runtime/blob/222415c56c9ea73530444768c0e68413eb374f5d/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs#L164-L184
+ bufferSize = Math.Max(4096, bufferSize);
- /// <inheritdoc/>
- public override void Flush()
+ // If we're completed buffered then copy from the underlying source
+ if (_completelyBuffered)
{
- throw new NotSupportedException();
+ return _buffer.CopyToAsync(destination, bufferSize, cancellationToken);
}
- /// <inheritdoc/>
- public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ async Task CopyToAsyncImpl()
{
- // Set a minimum buffer size of 4K since the base Stream implementation has weird behavior when the stream is
- // seekable *and* the length is 0 (it passes in a buffer size of 1).
- // See https://github.com/dotnet/runtime/blob/222415c56c9ea73530444768c0e68413eb374f5d/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs#L164-L184
- bufferSize = Math.Max(4096, bufferSize);
-
- // If we're completed buffered then copy from the underlying source
- if (_completelyBuffered)
- {
- return _buffer.CopyToAsync(destination, bufferSize, cancellationToken);
- }
-
- async Task CopyToAsyncImpl()
+ // At least a 4K buffer
+ byte[] buffer = _bytePool.Rent(bufferSize);
+ try
{
- // At least a 4K buffer
- byte[] buffer = _bytePool.Rent(bufferSize);
- try
+ while (true)
{
- while (true)
+ int bytesRead = await ReadAsync(buffer, cancellationToken);
+ if (bytesRead == 0)
{
- int bytesRead = await ReadAsync(buffer, cancellationToken);
- if (bytesRead == 0)
- {
- break;
- }
- await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
+ break;
}
- }
- finally
- {
- _bytePool.Return(buffer);
+ await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken);
}
}
-
- return CopyToAsyncImpl();
+ finally
+ {
+ _bytePool.Return(buffer);
+ }
}
- /// <inheritdoc/>
- protected override void Dispose(bool disposing)
+ return CopyToAsyncImpl();
+ }
+
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposed)
{
- if (!_disposed)
+ _disposed = true;
+ if (_rentedBuffer != null)
{
- _disposed = true;
- if (_rentedBuffer != null)
- {
- _bytePool.Return(_rentedBuffer);
- }
+ _bytePool.Return(_rentedBuffer);
+ }
- if (disposing)
- {
- _buffer.Dispose();
- }
+ if (disposing)
+ {
+ _buffer.Dispose();
}
}
+ }
- /// <inheritdoc/>
- public override async ValueTask DisposeAsync()
+ /// <inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ if (!_disposed)
{
- if (!_disposed)
+ _disposed = true;
+ if (_rentedBuffer != null)
{
- _disposed = true;
- if (_rentedBuffer != null)
- {
- _bytePool.Return(_rentedBuffer);
- }
-
- await _buffer.DisposeAsync();
+ _bytePool.Return(_rentedBuffer);
}
+
+ await _buffer.DisposeAsync();
}
+ }
- private void ThrowIfDisposed()
+ private void ThrowIfDisposed()
+ {
+ if (_disposed)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(FileBufferingReadStream));
- }
+ throw new ObjectDisposedException(nameof(FileBufferingReadStream));
}
}
}
diff --git a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs
index 28c4c27ab7..f3512522ce 100644
--- a/src/Http/WebUtilities/src/FileBufferingWriteStream.cs
+++ b/src/Http/WebUtilities/src/FileBufferingWriteStream.cs
@@ -11,305 +11,304 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// A <see cref="Stream"/> that buffers content to be written to disk. Use <see cref="DrainBufferAsync(Stream, CancellationToken)" />
+/// to write buffered content to a target <see cref="Stream" />.
+/// </summary>
+public sealed class FileBufferingWriteStream : Stream
{
+ private const int DefaultMemoryThreshold = 32 * 1024; // 32k
+
+ private readonly int _memoryThreshold;
+ private readonly long? _bufferLimit;
+ private readonly Func<string> _tempFileDirectoryAccessor;
+
/// <summary>
- /// A <see cref="Stream"/> that buffers content to be written to disk. Use <see cref="DrainBufferAsync(Stream, CancellationToken)" />
- /// to write buffered content to a target <see cref="Stream" />.
+ /// Initializes a new instance of <see cref="FileBufferingWriteStream"/>.
/// </summary>
- public sealed class FileBufferingWriteStream : Stream
+ /// <param name="memoryThreshold">
+ /// The maximum amount of memory in bytes to allocate before switching to a file on disk.
+ /// Defaults to 32kb.
+ /// </param>
+ /// <param name="bufferLimit">
+ /// The maximum amount of bytes that the <see cref="FileBufferingWriteStream"/> is allowed to buffer.
+ /// </param>
+ /// <param name="tempFileDirectoryAccessor">Provides the location of the directory to write buffered contents to.
+ /// When unspecified, uses the value specified by the environment variable <c>ASPNETCORE_TEMP</c> if available, otherwise
+ /// uses the value returned by <see cref="Path.GetTempPath"/>.
+ /// </param>
+ public FileBufferingWriteStream(
+ int memoryThreshold = DefaultMemoryThreshold,
+ long? bufferLimit = null,
+ Func<string>? tempFileDirectoryAccessor = null)
{
- private const int DefaultMemoryThreshold = 32 * 1024; // 32k
-
- private readonly int _memoryThreshold;
- private readonly long? _bufferLimit;
- private readonly Func<string> _tempFileDirectoryAccessor;
-
- /// <summary>
- /// Initializes a new instance of <see cref="FileBufferingWriteStream"/>.
- /// </summary>
- /// <param name="memoryThreshold">
- /// The maximum amount of memory in bytes to allocate before switching to a file on disk.
- /// Defaults to 32kb.
- /// </param>
- /// <param name="bufferLimit">
- /// The maximum amount of bytes that the <see cref="FileBufferingWriteStream"/> is allowed to buffer.
- /// </param>
- /// <param name="tempFileDirectoryAccessor">Provides the location of the directory to write buffered contents to.
- /// When unspecified, uses the value specified by the environment variable <c>ASPNETCORE_TEMP</c> if available, otherwise
- /// uses the value returned by <see cref="Path.GetTempPath"/>.
- /// </param>
- public FileBufferingWriteStream(
- int memoryThreshold = DefaultMemoryThreshold,
- long? bufferLimit = null,
- Func<string>? tempFileDirectoryAccessor = null)
+ if (memoryThreshold < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(memoryThreshold));
+ }
+
+ if (bufferLimit != null && bufferLimit < memoryThreshold)
{
- if (memoryThreshold < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(memoryThreshold));
- }
-
- if (bufferLimit != null && bufferLimit < memoryThreshold)
- {
- // We would expect a limit at least as much as memoryThreshold
- throw new ArgumentOutOfRangeException(nameof(bufferLimit), $"{nameof(bufferLimit)} must be larger than {nameof(memoryThreshold)}.");
- }
-
- _memoryThreshold = memoryThreshold;
- _bufferLimit = bufferLimit;
- _tempFileDirectoryAccessor = tempFileDirectoryAccessor ?? AspNetCoreTempDirectory.TempDirectoryFactory;
- PagedByteBuffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ // We would expect a limit at least as much as memoryThreshold
+ throw new ArgumentOutOfRangeException(nameof(bufferLimit), $"{nameof(bufferLimit)} must be larger than {nameof(memoryThreshold)}.");
}
- /// <summary>
- /// The maximum amount of memory in bytes to allocate before switching to a file on disk.
- /// </summary>
- /// <remarks>
- /// Defaults to 32kb.
- /// </remarks>
- public int MemoryThreshold => _memoryThreshold;
+ _memoryThreshold = memoryThreshold;
+ _bufferLimit = bufferLimit;
+ _tempFileDirectoryAccessor = tempFileDirectoryAccessor ?? AspNetCoreTempDirectory.TempDirectoryFactory;
+ PagedByteBuffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ }
+
+ /// <summary>
+ /// The maximum amount of memory in bytes to allocate before switching to a file on disk.
+ /// </summary>
+ /// <remarks>
+ /// Defaults to 32kb.
+ /// </remarks>
+ public int MemoryThreshold => _memoryThreshold;
- /// <inheritdoc />
- public override bool CanRead => false;
+ /// <inheritdoc />
+ public override bool CanRead => false;
- /// <inheritdoc />
- public override bool CanSeek => false;
+ /// <inheritdoc />
+ public override bool CanSeek => false;
- /// <inheritdoc />
- public override bool CanWrite => true;
+ /// <inheritdoc />
+ public override bool CanWrite => true;
- /// <inheritdoc />
- public override long Length => PagedByteBuffer.Length + (FileStream?.Length ?? 0);
+ /// <inheritdoc />
+ public override long Length => PagedByteBuffer.Length + (FileStream?.Length ?? 0);
- /// <inheritdoc />
- public override long Position
- {
- get => throw new NotSupportedException();
- set => throw new NotSupportedException();
- }
+ /// <inheritdoc />
+ public override long Position
+ {
+ get => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
- internal PagedByteBuffer PagedByteBuffer { get; }
+ internal PagedByteBuffer PagedByteBuffer { get; }
- internal FileStream? FileStream { get; private set; }
+ internal FileStream? FileStream { get; private set; }
- internal bool Disposed { get; private set; }
+ internal bool Disposed { get; private set; }
- /// <inheritdoc />
- public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+ /// <inheritdoc />
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
- /// <inheritdoc />
- public override int Read(byte[] buffer, int offset, int count)
- => throw new NotSupportedException();
+ /// <inheritdoc />
+ public override int Read(byte[] buffer, int offset, int count)
+ => throw new NotSupportedException();
- /// <inheritdoc />
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- => throw new NotSupportedException();
+ /// <inheritdoc />
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ => throw new NotSupportedException();
+
+ /// <inheritdoc />
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ ThrowArgumentException(buffer, offset, count);
+ ThrowIfDisposed();
- /// <inheritdoc />
- public override void Write(byte[] buffer, int offset, int count)
+ if (_bufferLimit.HasValue && _bufferLimit - Length < count)
{
- ThrowArgumentException(buffer, offset, count);
- ThrowIfDisposed();
-
- if (_bufferLimit.HasValue && _bufferLimit - Length < count)
- {
- Dispose();
- throw new IOException("Buffer limit exceeded.");
- }
-
- // Allow buffering in memory if we're below the memory threshold once the current buffer is written.
- var allowMemoryBuffer = (_memoryThreshold - count) >= PagedByteBuffer.Length;
- if (allowMemoryBuffer)
- {
- // Buffer content in the MemoryStream if it has capacity.
- PagedByteBuffer.Add(buffer, offset, count);
- Debug.Assert(PagedByteBuffer.Length <= _memoryThreshold);
- }
- else
- {
- // If the MemoryStream is incapable of accommodating the content to be written
- // spool to disk.
- EnsureFileStream();
-
- // Spool memory content to disk.
- PagedByteBuffer.MoveTo(FileStream);
-
- FileStream.Write(buffer, offset, count);
- }
+ Dispose();
+ throw new IOException("Buffer limit exceeded.");
}
- /// <inheritdoc />
- public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ // Allow buffering in memory if we're below the memory threshold once the current buffer is written.
+ var allowMemoryBuffer = (_memoryThreshold - count) >= PagedByteBuffer.Length;
+ if (allowMemoryBuffer)
{
- ThrowArgumentException(buffer, offset, count);
- await WriteAsync(buffer.AsMemory(offset, count), cancellationToken);
+ // Buffer content in the MemoryStream if it has capacity.
+ PagedByteBuffer.Add(buffer, offset, count);
+ Debug.Assert(PagedByteBuffer.Length <= _memoryThreshold);
}
+ else
+ {
+ // If the MemoryStream is incapable of accommodating the content to be written
+ // spool to disk.
+ EnsureFileStream();
+
+ // Spool memory content to disk.
+ PagedByteBuffer.MoveTo(FileStream);
+
+ FileStream.Write(buffer, offset, count);
+ }
+ }
+
+ /// <inheritdoc />
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ ThrowArgumentException(buffer, offset, count);
+ await WriteAsync(buffer.AsMemory(offset, count), cancellationToken);
+ }
- /// <inheritdoc />
- [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads", Justification = "This is a method overload.")]
- public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ /// <inheritdoc />
+ [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads", Justification = "This is a method overload.")]
+ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ if (_bufferLimit.HasValue && _bufferLimit - Length < buffer.Length)
{
- ThrowIfDisposed();
-
- if (_bufferLimit.HasValue && _bufferLimit - Length < buffer.Length)
- {
- Dispose();
- throw new IOException("Buffer limit exceeded.");
- }
-
- // Allow buffering in memory if we're below the memory threshold once the current buffer is written.
- var allowMemoryBuffer = (_memoryThreshold - buffer.Length) >= PagedByteBuffer.Length;
- if (allowMemoryBuffer)
- {
- // Buffer content in the MemoryStream if it has capacity.
- PagedByteBuffer.Add(buffer);
- Debug.Assert(PagedByteBuffer.Length <= _memoryThreshold);
- }
- else
- {
- // If the MemoryStream is incapable of accommodating the content to be written
- // spool to disk.
- EnsureFileStream();
-
- // Spool memory content to disk.
- await PagedByteBuffer.MoveToAsync(FileStream, cancellationToken);
- await FileStream.WriteAsync(buffer, cancellationToken);
- }
+ Dispose();
+ throw new IOException("Buffer limit exceeded.");
}
- /// <inheritdoc />
- public override void Flush()
+ // Allow buffering in memory if we're below the memory threshold once the current buffer is written.
+ var allowMemoryBuffer = (_memoryThreshold - buffer.Length) >= PagedByteBuffer.Length;
+ if (allowMemoryBuffer)
{
- // Do nothing.
+ // Buffer content in the MemoryStream if it has capacity.
+ PagedByteBuffer.Add(buffer);
+ Debug.Assert(PagedByteBuffer.Length <= _memoryThreshold);
}
+ else
+ {
+ // If the MemoryStream is incapable of accommodating the content to be written
+ // spool to disk.
+ EnsureFileStream();
- /// <inheritdoc />
- public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+ // Spool memory content to disk.
+ await PagedByteBuffer.MoveToAsync(FileStream, cancellationToken);
+ await FileStream.WriteAsync(buffer, cancellationToken);
+ }
+ }
- /// <inheritdoc />
- public override void SetLength(long value) => throw new NotSupportedException();
+ /// <inheritdoc />
+ public override void Flush()
+ {
+ // Do nothing.
+ }
- /// <summary>
- /// Drains buffered content to <paramref name="destination"/>.
- /// </summary>
- /// <param name="destination">The <see cref="Stream" /> to drain buffered contents to.</param>
- /// <param name="cancellationToken">The <see cref="CancellationToken" />.</param>
- /// <returns>A <see cref="Task" /> that represents the asynchronous drain operation.</returns>
- public async Task DrainBufferAsync(Stream destination, CancellationToken cancellationToken = default)
- {
- // When not null, FileStream always has "older" spooled content. The PagedByteBuffer always has "newer"
- // unspooled content. Copy the FileStream content first when available.
- if (FileStream != null)
- {
- // We make a new stream for async reads from disk and async writes to the destination
- await using var readStream = new FileStream(FileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite, bufferSize: 1, useAsync: true);
+ /// <inheritdoc />
+ public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+ /// <inheritdoc />
+ public override void SetLength(long value) => throw new NotSupportedException();
- await readStream.CopyToAsync(destination, cancellationToken);
+ /// <summary>
+ /// Drains buffered content to <paramref name="destination"/>.
+ /// </summary>
+ /// <param name="destination">The <see cref="Stream" /> to drain buffered contents to.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken" />.</param>
+ /// <returns>A <see cref="Task" /> that represents the asynchronous drain operation.</returns>
+ public async Task DrainBufferAsync(Stream destination, CancellationToken cancellationToken = default)
+ {
+ // When not null, FileStream always has "older" spooled content. The PagedByteBuffer always has "newer"
+ // unspooled content. Copy the FileStream content first when available.
+ if (FileStream != null)
+ {
+ // We make a new stream for async reads from disk and async writes to the destination
+ await using var readStream = new FileStream(FileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite, bufferSize: 1, useAsync: true);
- // This is created with delete on close
- await FileStream.DisposeAsync();
- FileStream = null;
- }
+ await readStream.CopyToAsync(destination, cancellationToken);
- await PagedByteBuffer.MoveToAsync(destination, cancellationToken);
+ // This is created with delete on close
+ await FileStream.DisposeAsync();
+ FileStream = null;
}
- /// <summary>
- /// Drains buffered content to <paramref name="destination"/>.
- /// </summary>
- /// <param name="destination">The <see cref="PipeWriter" /> to drain buffered contents to.</param>
- /// <param name="cancellationToken">The <see cref="CancellationToken" />.</param>
- /// <returns>A <see cref="Task" /> that represents the asynchronous drain operation.</returns>
- [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
- public async Task DrainBufferAsync(PipeWriter destination, CancellationToken cancellationToken = default)
+ await PagedByteBuffer.MoveToAsync(destination, cancellationToken);
+ }
+
+ /// <summary>
+ /// Drains buffered content to <paramref name="destination"/>.
+ /// </summary>
+ /// <param name="destination">The <see cref="PipeWriter" /> to drain buffered contents to.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken" />.</param>
+ /// <returns>A <see cref="Task" /> that represents the asynchronous drain operation.</returns>
+ [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
+ public async Task DrainBufferAsync(PipeWriter destination, CancellationToken cancellationToken = default)
+ {
+ // When not null, FileStream always has "older" spooled content. The PagedByteBuffer always has "newer"
+ // unspooled content. Copy the FileStream content first when available.
+ if (FileStream != null)
{
- // When not null, FileStream always has "older" spooled content. The PagedByteBuffer always has "newer"
- // unspooled content. Copy the FileStream content first when available.
- if (FileStream != null)
- {
- // We make a new stream for async reads from disk and async writes to the destination
- await using var readStream = new FileStream(FileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite, bufferSize: 1, useAsync: true);
+ // We make a new stream for async reads from disk and async writes to the destination
+ await using var readStream = new FileStream(FileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite, bufferSize: 1, useAsync: true);
+
+ await readStream.CopyToAsync(destination, cancellationToken);
- await readStream.CopyToAsync(destination, cancellationToken);
+ // This is created with delete on close
+ await FileStream.DisposeAsync();
+ FileStream = null;
+ }
+
+ await PagedByteBuffer.MoveToAsync(destination, cancellationToken);
+ }
- // This is created with delete on close
- await FileStream.DisposeAsync();
- FileStream = null;
- }
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ if (!Disposed)
+ {
+ Disposed = true;
- await PagedByteBuffer.MoveToAsync(destination, cancellationToken);
+ PagedByteBuffer.Dispose();
+ FileStream?.Dispose();
}
+ }
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
+ /// <inheritdoc />
+ public override async ValueTask DisposeAsync()
+ {
+ if (!Disposed)
{
- if (!Disposed)
- {
- Disposed = true;
+ Disposed = true;
- PagedByteBuffer.Dispose();
- FileStream?.Dispose();
- }
+ PagedByteBuffer.Dispose();
+ await (FileStream?.DisposeAsync() ?? default);
}
+ }
- /// <inheritdoc />
- public override async ValueTask DisposeAsync()
+ [MemberNotNull(nameof(FileStream))]
+ private void EnsureFileStream()
+ {
+ if (FileStream == null)
{
- if (!Disposed)
- {
- Disposed = true;
+ var tempFileDirectory = _tempFileDirectoryAccessor();
+ var tempFileName = Path.Combine(tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid() + ".tmp");
+ FileStream = new FileStream(
+ tempFileName,
+ FileMode.Create,
+ FileAccess.Write,
+ FileShare.Delete | FileShare.ReadWrite,
+ bufferSize: 1,
+ FileOptions.SequentialScan | FileOptions.DeleteOnClose);
+ }
+ }
- PagedByteBuffer.Dispose();
- await (FileStream?.DisposeAsync() ?? default);
- }
+ private void ThrowIfDisposed()
+ {
+ if (Disposed)
+ {
+ throw new ObjectDisposedException(nameof(FileBufferingWriteStream));
+ }
+ }
+
+ private static void ThrowArgumentException(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
}
- [MemberNotNull(nameof(FileStream))]
- private void EnsureFileStream()
+ if (offset < 0)
{
- if (FileStream == null)
- {
- var tempFileDirectory = _tempFileDirectoryAccessor();
- var tempFileName = Path.Combine(tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid() + ".tmp");
- FileStream = new FileStream(
- tempFileName,
- FileMode.Create,
- FileAccess.Write,
- FileShare.Delete | FileShare.ReadWrite,
- bufferSize: 1,
- FileOptions.SequentialScan | FileOptions.DeleteOnClose);
- }
+ throw new ArgumentOutOfRangeException(nameof(offset));
}
- private void ThrowIfDisposed()
+ if (count < 0)
{
- if (Disposed)
- {
- throw new ObjectDisposedException(nameof(FileBufferingWriteStream));
- }
+ throw new ArgumentOutOfRangeException(nameof(count));
}
- private static void ThrowArgumentException(byte[] buffer, int offset, int count)
+ if (buffer.Length - offset < count)
{
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
-
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(offset));
- }
-
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count));
- }
-
- if (buffer.Length - offset < count)
- {
- throw new ArgumentOutOfRangeException(nameof(offset));
- }
+ throw new ArgumentOutOfRangeException(nameof(offset));
}
}
}
diff --git a/src/Http/WebUtilities/src/FileMultipartSection.cs b/src/Http/WebUtilities/src/FileMultipartSection.cs
index 09fe352345..2e8914c2dc 100644
--- a/src/Http/WebUtilities/src/FileMultipartSection.cs
+++ b/src/Http/WebUtilities/src/FileMultipartSection.cs
@@ -5,66 +5,65 @@ using System;
using System.IO;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Represents a file multipart section
+/// </summary>
+public class FileMultipartSection
{
+ private readonly ContentDispositionHeaderValue _contentDispositionHeader;
+
/// <summary>
- /// Represents a file multipart section
+ /// Creates a new instance of the <see cref="FileMultipartSection"/> class
/// </summary>
- public class FileMultipartSection
+ /// <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())
{
- private readonly 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 is null || !header.IsFileDisposition())
{
+ throw new ArgumentException("Argument must be a file section", nameof(section));
}
- /// <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 is null || !header.IsFileDisposition())
- {
- throw new ArgumentException("Argument must be a file section", nameof(section));
- }
-
- Section = section;
- _contentDispositionHeader = header;
+ Section = section;
+ _contentDispositionHeader = header;
- Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name).ToString();
- FileName = HeaderUtilities.RemoveQuotes(
- _contentDispositionHeader.FileNameStar.HasValue ?
- _contentDispositionHeader.FileNameStar :
- _contentDispositionHeader.FileName).ToString();
- }
+ 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 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 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 section
+ /// </summary>
+ public string Name { get; }
- /// <summary>
- /// Gets the name of the file from the section
- /// </summary>
- public string FileName { 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
index 5792e02629..b72cf91903 100644
--- a/src/Http/WebUtilities/src/FormMultipartSection.cs
+++ b/src/Http/WebUtilities/src/FormMultipartSection.cs
@@ -5,59 +5,58 @@ using System;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Represents a form multipart section
+/// </summary>
+public class FormMultipartSection
{
+ private readonly ContentDispositionHeaderValue _contentDispositionHeader;
+
/// <summary>
- /// Represents a form multipart section
+ /// Creates a new instance of the <see cref="FormMultipartSection"/> class
/// </summary>
- public class FormMultipartSection
+ /// <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())
{
- private readonly 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)
+ /// <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())
{
- 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();
+ throw new ArgumentException("Argument must be a form section", nameof(section));
}
- /// <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();
- }
+ 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/FormPipeReader.cs b/src/Http/WebUtilities/src/FormPipeReader.cs
index 9181a2df98..cd7268be14 100644
--- a/src/Http/WebUtilities/src/FormPipeReader.cs
+++ b/src/Http/WebUtilities/src/FormPipeReader.cs
@@ -15,423 +15,422 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Used to read an 'application/x-www-form-urlencoded' form.
+/// Internally reads from a PipeReader.
+/// </summary>
+public class FormPipeReader
{
+ private const int StackAllocThreshold = 128;
+ private const int DefaultValueCountLimit = 1024;
+ private const int DefaultKeyLengthLimit = 1024 * 2;
+ private const int DefaultValueLengthLimit = 1024 * 1024 * 4;
+
+ // Used for UTF8/ASCII (precalculated for fast path)
+ // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
+ private static ReadOnlySpan<byte> UTF8EqualEncoded => new byte[] { (byte)'=' };
+ private static ReadOnlySpan<byte> UTF8AndEncoded => new byte[] { (byte)'&' };
+
+ // Used for other encodings
+ private readonly byte[]? _otherEqualEncoding;
+ private readonly byte[]? _otherAndEncoding;
+
+ private readonly PipeReader _pipeReader;
+ private readonly Encoding _encoding;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormPipeReader"/>.
+ /// </summary>
+ /// <param name="pipeReader">The <see cref="PipeReader"/> to read from.</param>
+ public FormPipeReader(PipeReader pipeReader)
+ : this(pipeReader, Encoding.UTF8)
+ {
+ }
+
/// <summary>
- /// Used to read an 'application/x-www-form-urlencoded' form.
- /// Internally reads from a PipeReader.
+ /// Initializes a new instance of <see cref="FormPipeReader"/>.
/// </summary>
- public class FormPipeReader
+ /// <param name="pipeReader">The <see cref="PipeReader"/> to read from.</param>
+ /// <param name="encoding">The <see cref="Encoding"/>.</param>
+ public FormPipeReader(PipeReader pipeReader, Encoding encoding)
{
- private const int StackAllocThreshold = 128;
- private const int DefaultValueCountLimit = 1024;
- private const int DefaultKeyLengthLimit = 1024 * 2;
- private const int DefaultValueLengthLimit = 1024 * 1024 * 4;
-
- // Used for UTF8/ASCII (precalculated for fast path)
- // This uses C# compiler's ability to refer to static data directly. For more information see https://vcsjones.dev/2019/02/01/csharp-readonly-span-bytes-static
- private static ReadOnlySpan<byte> UTF8EqualEncoded => new byte[] { (byte)'=' };
- private static ReadOnlySpan<byte> UTF8AndEncoded => new byte[] { (byte)'&' };
-
- // Used for other encodings
- private readonly byte[]? _otherEqualEncoding;
- private readonly byte[]? _otherAndEncoding;
-
- private readonly PipeReader _pipeReader;
- private readonly Encoding _encoding;
-
- /// <summary>
- /// Initializes a new instance of <see cref="FormPipeReader"/>.
- /// </summary>
- /// <param name="pipeReader">The <see cref="PipeReader"/> to read from.</param>
- public FormPipeReader(PipeReader pipeReader)
- : this(pipeReader, Encoding.UTF8)
+ // https://docs.microsoft.com/en-us/dotnet/core/compatibility/syslib-warnings/syslib0001
+ if (encoding is Encoding { CodePage: 65000 })
{
+ throw new ArgumentException("UTF7 is unsupported and insecure. Please select a different encoding.");
}
- /// <summary>
- /// Initializes a new instance of <see cref="FormPipeReader"/>.
- /// </summary>
- /// <param name="pipeReader">The <see cref="PipeReader"/> to read from.</param>
- /// <param name="encoding">The <see cref="Encoding"/>.</param>
- public FormPipeReader(PipeReader pipeReader, Encoding encoding)
+ _pipeReader = pipeReader;
+ _encoding = encoding;
+
+ if (_encoding != Encoding.UTF8 && _encoding != Encoding.ASCII)
{
- // https://docs.microsoft.com/en-us/dotnet/core/compatibility/syslib-warnings/syslib0001
- if (encoding is Encoding { CodePage: 65000 })
- {
- throw new ArgumentException("UTF7 is unsupported and insecure. Please select a different encoding.");
- }
+ _otherEqualEncoding = _encoding.GetBytes("=");
+ _otherAndEncoding = _encoding.GetBytes("&");
+ }
+ }
- _pipeReader = pipeReader;
- _encoding = encoding;
+ /// <summary>
+ /// The limit on the number of form values to allow in ReadForm or ReadFormAsync.
+ /// </summary>
+ public int ValueCountLimit { get; set; } = DefaultValueCountLimit;
- if (_encoding != Encoding.UTF8 && _encoding != Encoding.ASCII)
- {
- _otherEqualEncoding = _encoding.GetBytes("=");
- _otherAndEncoding = _encoding.GetBytes("&");
- }
- }
+ /// <summary>
+ /// The limit on the length of form keys.
+ /// </summary>
+ public int KeyLengthLimit { get; set; } = DefaultKeyLengthLimit;
- /// <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;
-
- /// <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 = default)
+ /// <summary>
+ /// The limit on the length of form values.
+ /// </summary>
+ public int ValueLengthLimit { get; set; } = DefaultValueLengthLimit;
+
+ /// <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 = default)
+ {
+ KeyValueAccumulator accumulator = default;
+ while (true)
{
- KeyValueAccumulator accumulator = default;
- while (true)
- {
- var readResult = await _pipeReader.ReadAsync(cancellationToken);
+ var readResult = await _pipeReader.ReadAsync(cancellationToken);
- var buffer = readResult.Buffer;
+ var buffer = readResult.Buffer;
- if (!buffer.IsEmpty)
+ if (!buffer.IsEmpty)
+ {
+ try
{
- try
- {
- ParseFormValues(ref buffer, ref accumulator, readResult.IsCompleted);
- }
- catch
- {
- _pipeReader.AdvanceTo(buffer.Start, buffer.End);
- throw;
- }
+ ParseFormValues(ref buffer, ref accumulator, readResult.IsCompleted);
}
-
- if (readResult.IsCompleted)
+ catch
{
- _pipeReader.AdvanceTo(buffer.End);
-
- if (!buffer.IsEmpty)
- {
- throw new InvalidOperationException("End of body before form was fully parsed.");
- }
- break;
+ _pipeReader.AdvanceTo(buffer.Start, buffer.End);
+ throw;
}
-
- _pipeReader.AdvanceTo(buffer.Start, buffer.End);
}
- return accumulator.GetResults();
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- internal void ParseFormValues(
- ref ReadOnlySequence<byte> buffer,
- ref KeyValueAccumulator accumulator,
- bool isFinalBlock)
- {
- if (buffer.IsSingleSegment)
+ if (readResult.IsCompleted)
{
- ParseFormValuesFast(buffer.FirstSpan,
- ref accumulator,
- isFinalBlock,
- out var consumed);
+ _pipeReader.AdvanceTo(buffer.End);
- buffer = buffer.Slice(consumed);
- return;
+ if (!buffer.IsEmpty)
+ {
+ throw new InvalidOperationException("End of body before form was fully parsed.");
+ }
+ break;
}
- ParseValuesSlow(ref buffer,
+ _pipeReader.AdvanceTo(buffer.Start, buffer.End);
+ }
+
+ return accumulator.GetResults();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal void ParseFormValues(
+ ref ReadOnlySequence<byte> buffer,
+ ref KeyValueAccumulator accumulator,
+ bool isFinalBlock)
+ {
+ if (buffer.IsSingleSegment)
+ {
+ ParseFormValuesFast(buffer.FirstSpan,
ref accumulator,
- isFinalBlock);
+ isFinalBlock,
+ out var consumed);
+
+ buffer = buffer.Slice(consumed);
+ return;
}
- // Fast parsing for single span in ReadOnlySequence
- private void ParseFormValuesFast(ReadOnlySpan<byte> span,
- ref KeyValueAccumulator accumulator,
- bool isFinalBlock,
- out int consumed)
+ ParseValuesSlow(ref buffer,
+ ref accumulator,
+ isFinalBlock);
+ }
+
+ // Fast parsing for single span in ReadOnlySequence
+ private void ParseFormValuesFast(ReadOnlySpan<byte> span,
+ ref KeyValueAccumulator accumulator,
+ bool isFinalBlock,
+ out int consumed)
+ {
+ ReadOnlySpan<byte> key;
+ ReadOnlySpan<byte> value;
+ consumed = 0;
+ var equalsDelimiter = GetEqualsForEncoding();
+ var andDelimiter = GetAndForEncoding();
+
+ while (span.Length > 0)
{
- ReadOnlySpan<byte> key;
- ReadOnlySpan<byte> value;
- consumed = 0;
- var equalsDelimiter = GetEqualsForEncoding();
- var andDelimiter = GetAndForEncoding();
+ // Find the end of the key=value pair.
+ var ampersand = span.IndexOf(andDelimiter);
+ ReadOnlySpan<byte> keyValuePair;
+ int equals;
+ var foundAmpersand = ampersand != -1;
- while (span.Length > 0)
+ if (foundAmpersand)
{
- // Find the end of the key=value pair.
- var ampersand = span.IndexOf(andDelimiter);
- ReadOnlySpan<byte> keyValuePair;
- int equals;
- var foundAmpersand = ampersand != -1;
-
- if (foundAmpersand)
- {
- keyValuePair = span.Slice(0, ampersand);
- span = span.Slice(keyValuePair.Length + andDelimiter.Length);
- consumed += keyValuePair.Length + andDelimiter.Length;
- }
- else
+ keyValuePair = span.Slice(0, ampersand);
+ span = span.Slice(keyValuePair.Length + andDelimiter.Length);
+ consumed += keyValuePair.Length + andDelimiter.Length;
+ }
+ else
+ {
+ // We can't know that what is currently read is the end of the form value, that's only the case if this is the final block
+ // If we're not in the final block, then consume nothing
+ if (!isFinalBlock)
{
- // We can't know that what is currently read is the end of the form value, that's only the case if this is the final block
- // If we're not in the final block, then consume nothing
- if (!isFinalBlock)
+ // Don't buffer indefinitely
+ if ((uint)span.Length > (uint)KeyLengthLimit + (uint)ValueLengthLimit)
{
- // Don't buffer indefinitely
- if ((uint)span.Length > (uint)KeyLengthLimit + (uint)ValueLengthLimit)
- {
- ThrowKeyOrValueTooLargeException();
- }
- return;
+ ThrowKeyOrValueTooLargeException();
}
-
- keyValuePair = span;
- span = default;
- consumed += keyValuePair.Length;
+ return;
}
- equals = keyValuePair.IndexOf(equalsDelimiter);
+ keyValuePair = span;
+ span = default;
+ consumed += keyValuePair.Length;
+ }
- if (equals == -1)
- {
- // Too long for the whole segment to be a key.
- if (keyValuePair.Length > KeyLengthLimit)
- {
- ThrowKeyTooLargeException();
- }
+ equals = keyValuePair.IndexOf(equalsDelimiter);
- // There is no more data, this segment must be "key" with no equals or value.
- key = keyValuePair;
- value = default;
+ if (equals == -1)
+ {
+ // Too long for the whole segment to be a key.
+ if (keyValuePair.Length > KeyLengthLimit)
+ {
+ ThrowKeyTooLargeException();
}
- else
+
+ // There is no more data, this segment must be "key" with no equals or value.
+ key = keyValuePair;
+ value = default;
+ }
+ else
+ {
+ key = keyValuePair.Slice(0, equals);
+ if (key.Length > KeyLengthLimit)
{
- key = keyValuePair.Slice(0, equals);
- if (key.Length > KeyLengthLimit)
- {
- ThrowKeyTooLargeException();
- }
+ ThrowKeyTooLargeException();
+ }
- value = keyValuePair.Slice(equals + equalsDelimiter.Length);
- if (value.Length > ValueLengthLimit)
- {
- ThrowValueTooLargeException();
- }
+ value = keyValuePair.Slice(equals + equalsDelimiter.Length);
+ if (value.Length > ValueLengthLimit)
+ {
+ ThrowValueTooLargeException();
}
+ }
- var decodedKey = GetDecodedString(key);
- var decodedValue = GetDecodedString(value);
+ var decodedKey = GetDecodedString(key);
+ var decodedValue = GetDecodedString(value);
- AppendAndVerify(ref accumulator, decodedKey, decodedValue);
- }
+ AppendAndVerify(ref accumulator, decodedKey, decodedValue);
}
+ }
- // For multi-segment parsing of a read only sequence
- private void ParseValuesSlow(
- ref ReadOnlySequence<byte> buffer,
- ref KeyValueAccumulator accumulator,
- bool isFinalBlock)
- {
- var sequenceReader = new SequenceReader<byte>(buffer);
- ReadOnlySequence<byte> keyValuePair;
+ // For multi-segment parsing of a read only sequence
+ private void ParseValuesSlow(
+ ref ReadOnlySequence<byte> buffer,
+ ref KeyValueAccumulator accumulator,
+ bool isFinalBlock)
+ {
+ var sequenceReader = new SequenceReader<byte>(buffer);
+ ReadOnlySequence<byte> keyValuePair;
- var consumed = sequenceReader.Position;
- var consumedBytes = default(long);
- var equalsDelimiter = GetEqualsForEncoding();
- var andDelimiter = GetAndForEncoding();
+ var consumed = sequenceReader.Position;
+ var consumedBytes = default(long);
+ var equalsDelimiter = GetEqualsForEncoding();
+ var andDelimiter = GetAndForEncoding();
- while (!sequenceReader.End)
+ while (!sequenceReader.End)
+ {
+ if (!sequenceReader.TryReadTo(out keyValuePair, andDelimiter))
{
- if (!sequenceReader.TryReadTo(out keyValuePair, andDelimiter))
+ if (!isFinalBlock)
{
- if (!isFinalBlock)
+ // Don't buffer indefinitely
+ if ((uint)(sequenceReader.Consumed - consumedBytes) > (uint)KeyLengthLimit + (uint)ValueLengthLimit)
{
- // Don't buffer indefinitely
- if ((uint)(sequenceReader.Consumed - consumedBytes) > (uint)KeyLengthLimit + (uint)ValueLengthLimit)
- {
- ThrowKeyOrValueTooLargeException();
- }
- break;
+ ThrowKeyOrValueTooLargeException();
}
-
- // This must be the final key=value pair
- keyValuePair = buffer.Slice(sequenceReader.Position);
- sequenceReader.Advance(keyValuePair.Length);
+ break;
}
- if (keyValuePair.IsSingleSegment)
- {
- ParseFormValuesFast(keyValuePair.FirstSpan, ref accumulator, isFinalBlock: true, out var segmentConsumed);
- Debug.Assert(segmentConsumed == keyValuePair.FirstSpan.Length);
- consumedBytes = sequenceReader.Consumed;
- consumed = sequenceReader.Position;
- continue;
- }
+ // This must be the final key=value pair
+ keyValuePair = buffer.Slice(sequenceReader.Position);
+ sequenceReader.Advance(keyValuePair.Length);
+ }
+
+ if (keyValuePair.IsSingleSegment)
+ {
+ ParseFormValuesFast(keyValuePair.FirstSpan, ref accumulator, isFinalBlock: true, out var segmentConsumed);
+ Debug.Assert(segmentConsumed == keyValuePair.FirstSpan.Length);
+ consumedBytes = sequenceReader.Consumed;
+ consumed = sequenceReader.Position;
+ continue;
+ }
- var keyValueReader = new SequenceReader<byte>(keyValuePair);
- ReadOnlySequence<byte> value;
+ var keyValueReader = new SequenceReader<byte>(keyValuePair);
+ ReadOnlySequence<byte> value;
- if (keyValueReader.TryReadTo(out ReadOnlySequence<byte> key, equalsDelimiter))
+ if (keyValueReader.TryReadTo(out ReadOnlySequence<byte> key, equalsDelimiter))
+ {
+ if (key.Length > KeyLengthLimit)
{
- if (key.Length > KeyLengthLimit)
- {
- ThrowKeyTooLargeException();
- }
+ ThrowKeyTooLargeException();
+ }
- value = keyValuePair.Slice(keyValueReader.Position);
- if (value.Length > ValueLengthLimit)
- {
- ThrowValueTooLargeException();
- }
+ value = keyValuePair.Slice(keyValueReader.Position);
+ if (value.Length > ValueLengthLimit)
+ {
+ ThrowValueTooLargeException();
}
- else
+ }
+ else
+ {
+ // Too long for the whole segment to be a key.
+ if (keyValuePair.Length > KeyLengthLimit)
{
- // Too long for the whole segment to be a key.
- if (keyValuePair.Length > KeyLengthLimit)
- {
- ThrowKeyTooLargeException();
- }
-
- // There is no more data, this segment must be "key" with no equals or value.
- key = keyValuePair;
- value = default;
+ ThrowKeyTooLargeException();
}
- var decodedKey = GetDecodedStringFromReadOnlySequence(key);
- var decodedValue = GetDecodedStringFromReadOnlySequence(value);
+ // There is no more data, this segment must be "key" with no equals or value.
+ key = keyValuePair;
+ value = default;
+ }
- AppendAndVerify(ref accumulator, decodedKey, decodedValue);
+ var decodedKey = GetDecodedStringFromReadOnlySequence(key);
+ var decodedValue = GetDecodedStringFromReadOnlySequence(value);
- consumedBytes = sequenceReader.Consumed;
- consumed = sequenceReader.Position;
- }
+ AppendAndVerify(ref accumulator, decodedKey, decodedValue);
- buffer = buffer.Slice(consumed);
+ consumedBytes = sequenceReader.Consumed;
+ consumed = sequenceReader.Position;
}
- private void ThrowKeyOrValueTooLargeException()
- {
- throw new InvalidDataException($"Form key length limit {KeyLengthLimit} or value length limit {ValueLengthLimit} exceeded.");
- }
+ buffer = buffer.Slice(consumed);
+ }
+
+ private void ThrowKeyOrValueTooLargeException()
+ {
+ throw new InvalidDataException($"Form key length limit {KeyLengthLimit} or value length limit {ValueLengthLimit} exceeded.");
+ }
+
+ private void ThrowKeyTooLargeException()
+ {
+ throw new InvalidDataException($"Form key length limit {KeyLengthLimit} exceeded.");
+ }
+
+ private void ThrowValueTooLargeException()
+ {
+ throw new InvalidDataException($"Form value length limit {ValueLengthLimit} exceeded.");
+ }
- private void ThrowKeyTooLargeException()
+ [SkipLocalsInit]
+ private string GetDecodedStringFromReadOnlySequence(in ReadOnlySequence<byte> ros)
+ {
+ if (ros.IsSingleSegment)
{
- throw new InvalidDataException($"Form key length limit {KeyLengthLimit} exceeded.");
+ return GetDecodedString(ros.FirstSpan);
}
- private void ThrowValueTooLargeException()
+ if (ros.Length < StackAllocThreshold)
{
- throw new InvalidDataException($"Form value length limit {ValueLengthLimit} exceeded.");
+ Span<byte> buffer = stackalloc byte[StackAllocThreshold].Slice(0, (int)ros.Length);
+ ros.CopyTo(buffer);
+ return GetDecodedString(buffer);
}
-
- [SkipLocalsInit]
- private string GetDecodedStringFromReadOnlySequence(in ReadOnlySequence<byte> ros)
+ else
{
- if (ros.IsSingleSegment)
- {
- return GetDecodedString(ros.FirstSpan);
- }
+ var byteArray = ArrayPool<byte>.Shared.Rent((int)ros.Length);
- if (ros.Length < StackAllocThreshold)
+ try
{
- Span<byte> buffer = stackalloc byte[StackAllocThreshold].Slice(0, (int)ros.Length);
+ Span<byte> buffer = byteArray.AsSpan(0, (int)ros.Length);
ros.CopyTo(buffer);
return GetDecodedString(buffer);
}
- else
+ finally
{
- var byteArray = ArrayPool<byte>.Shared.Rent((int)ros.Length);
-
- try
- {
- Span<byte> buffer = byteArray.AsSpan(0, (int)ros.Length);
- ros.CopyTo(buffer);
- return GetDecodedString(buffer);
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(byteArray);
- }
+ ArrayPool<byte>.Shared.Return(byteArray);
}
}
+ }
- // Check that key/value constraints are met and appends value to accumulator.
- private void AppendAndVerify(ref KeyValueAccumulator accumulator, string decodedKey, string decodedValue)
- {
- accumulator.Append(decodedKey, decodedValue);
+ // Check that key/value constraints are met and appends value to accumulator.
+ private void AppendAndVerify(ref KeyValueAccumulator accumulator, string decodedKey, string decodedValue)
+ {
+ accumulator.Append(decodedKey, decodedValue);
- if (accumulator.ValueCount > ValueCountLimit)
- {
- throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
- }
+ if (accumulator.ValueCount > ValueCountLimit)
+ {
+ throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
}
+ }
- private string GetDecodedString(ReadOnlySpan<byte> readOnlySpan)
+ private string GetDecodedString(ReadOnlySpan<byte> readOnlySpan)
+ {
+ if (readOnlySpan.Length == 0)
{
- if (readOnlySpan.Length == 0)
- {
- return string.Empty;
- }
- else if (_encoding == Encoding.UTF8 || _encoding == Encoding.ASCII)
- {
- // UrlDecoder only works on UTF8 (and implicitly ASCII)
+ return string.Empty;
+ }
+ else if (_encoding == Encoding.UTF8 || _encoding == Encoding.ASCII)
+ {
+ // UrlDecoder only works on UTF8 (and implicitly ASCII)
- // We need to create a Span from a ReadOnlySpan. This cast is safe because the memory is still held by the pipe
- // We will also create a string from it by the end of the function.
- var span = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(readOnlySpan[0]), readOnlySpan.Length);
+ // We need to create a Span from a ReadOnlySpan. This cast is safe because the memory is still held by the pipe
+ // We will also create a string from it by the end of the function.
+ var span = MemoryMarshal.CreateSpan(ref Unsafe.AsRef(readOnlySpan[0]), readOnlySpan.Length);
- try
- {
- var bytes = UrlDecoder.DecodeInPlace(span, isFormEncoding: true);
- span = span.Slice(0, bytes);
+ try
+ {
+ var bytes = UrlDecoder.DecodeInPlace(span, isFormEncoding: true);
+ span = span.Slice(0, bytes);
- return _encoding.GetString(span);
- }
- catch (InvalidOperationException ex)
- {
- throw new InvalidDataException("The form value contains invalid characters.", ex);
- }
+ return _encoding.GetString(span);
}
- else
+ catch (InvalidOperationException ex)
{
- // Slow path for Unicode and other encodings.
- // Just do raw string replacement.
- var decodedString = _encoding.GetString(readOnlySpan);
- decodedString = decodedString.Replace('+', ' ');
- return Uri.UnescapeDataString(decodedString);
+ throw new InvalidDataException("The form value contains invalid characters.", ex);
}
}
+ else
+ {
+ // Slow path for Unicode and other encodings.
+ // Just do raw string replacement.
+ var decodedString = _encoding.GetString(readOnlySpan);
+ decodedString = decodedString.Replace('+', ' ');
+ return Uri.UnescapeDataString(decodedString);
+ }
+ }
- private ReadOnlySpan<byte> GetEqualsForEncoding()
+ private ReadOnlySpan<byte> GetEqualsForEncoding()
+ {
+ if (_encoding == Encoding.UTF8 || _encoding == Encoding.ASCII)
{
- if (_encoding == Encoding.UTF8 || _encoding == Encoding.ASCII)
- {
- return UTF8EqualEncoded;
- }
- else
- {
- return _otherEqualEncoding;
- }
+ return UTF8EqualEncoded;
}
+ else
+ {
+ return _otherEqualEncoding;
+ }
+ }
- private ReadOnlySpan<byte> GetAndForEncoding()
+ private ReadOnlySpan<byte> GetAndForEncoding()
+ {
+ if (_encoding == Encoding.UTF8 || _encoding == Encoding.ASCII)
{
- if (_encoding == Encoding.UTF8 || _encoding == Encoding.ASCII)
- {
- return UTF8AndEncoded;
- }
- else
- {
- return _otherAndEncoding;
- }
+ return UTF8AndEncoded;
+ }
+ else
+ {
+ return _otherAndEncoding;
}
}
}
diff --git a/src/Http/WebUtilities/src/FormReader.cs b/src/Http/WebUtilities/src/FormReader.cs
index 04afda2277..376d07c33c 100644
--- a/src/Http/WebUtilities/src/FormReader.cs
+++ b/src/Http/WebUtilities/src/FormReader.cs
@@ -11,343 +11,342 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Used to read an 'application/x-www-form-urlencoded' form.
+/// </summary>
+public class FormReader : IDisposable
{
/// <summary>
- /// Used to read an 'application/x-www-form-urlencoded' form.
+ /// Gets the default value for <see cref="ValueCountLimit"/>.
+ /// Defaults to 1024.
+ /// </summary>
+ public const int DefaultValueCountLimit = 1024;
+
+ /// <summary>
+ /// Gets the default value for <see cref="KeyLengthLimit"/>.
+ /// Defaults to 2,048 bytes‬, which is approximately 2KB.
+ /// </summary>
+ public const int DefaultKeyLengthLimit = 1024 * 2;
+
+ /// <summary>
+ /// Gets the default value for <see cref="ValueLengthLimit" />.
+ /// Defaults to 4,194,304 bytes‬, which is approximately 4MB.
+ /// </summary>
+ 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;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormReader"/>.
+ /// </summary>
+ /// <param name="data">The data to read.</param>
+ public FormReader(string data)
+ : this(data, ArrayPool<char>.Shared)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormReader"/>.
/// </summary>
- public class FormReader : IDisposable
+ /// <param name="data">The data to read.</param>
+ /// <param name="charPool">The <see cref="ArrayPool{T}"/> to use.</param>
+ public FormReader(string data, ArrayPool<char> charPool)
{
- /// <summary>
- /// Gets the default value for <see cref="ValueCountLimit"/>.
- /// Defaults to 1024.
- /// </summary>
- public const int DefaultValueCountLimit = 1024;
-
- /// <summary>
- /// Gets the default value for <see cref="KeyLengthLimit"/>.
- /// Defaults to 2,048 bytes‬, which is approximately 2KB.
- /// </summary>
- public const int DefaultKeyLengthLimit = 1024 * 2;
-
- /// <summary>
- /// Gets the default value for <see cref="ValueLengthLimit" />.
- /// Defaults to 4,194,304 bytes‬, which is approximately 4MB.
- /// </summary>
- 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;
-
- /// <summary>
- /// Initializes a new instance of <see cref="FormReader"/>.
- /// </summary>
- /// <param name="data">The data to read.</param>
- public FormReader(string data)
- : this(data, ArrayPool<char>.Shared)
+ if (data == null)
{
+ throw new ArgumentNullException(nameof(data));
}
- /// <summary>
- /// Initializes a new instance of <see cref="FormReader"/>.
- /// </summary>
- /// <param name="data">The data to read.</param>
- /// <param name="charPool">The <see cref="ArrayPool{T}"/> to use.</param>
- 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);
+ }
- _buffer = charPool.Rent(_rentedCharPoolLength);
- _charPool = charPool;
- _reader = new StringReader(data);
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormReader"/>.
+ /// </summary>
+ /// <param name="stream">The <see cref="Stream"/> to read. Assumes a utf-8 encoded stream.</param>
+ public FormReader(Stream stream)
+ : this(stream, Encoding.UTF8, ArrayPool<char>.Shared)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="FormReader"/>.
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to read. Assumes a utf-8 encoded stream.</param>
- public FormReader(Stream stream)
- : this(stream, Encoding.UTF8, ArrayPool<char>.Shared)
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormReader"/>.
+ /// </summary>
+ /// <param name="stream">The <see cref="Stream"/> to read.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ public FormReader(Stream stream, Encoding encoding)
+ : this(stream, encoding, ArrayPool<char>.Shared)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FormReader"/>.
+ /// </summary>
+ /// <param name="stream">The <see cref="Stream"/> to read.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ /// <param name="charPool">The <see cref="ArrayPool{T}"/> to use.</param>
+ public FormReader(Stream stream, Encoding encoding, ArrayPool<char> charPool)
+ {
+ if (stream == null)
{
+ throw new ArgumentNullException(nameof(stream));
}
- /// <summary>
- /// Initializes a new instance of <see cref="FormReader"/>.
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to read.</param>
- /// <param name="encoding">The character encoding to use.</param>
- public FormReader(Stream stream, Encoding encoding)
- : this(stream, encoding, ArrayPool<char>.Shared)
+ if (encoding == null)
{
+ throw new ArgumentNullException(nameof(encoding));
}
- /// <summary>
- /// Initializes a new instance of <see cref="FormReader"/>.
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to read.</param>
- /// <param name="encoding">The character encoding to use.</param>
- /// <param name="charPool">The <see cref="ArrayPool{T}"/> to use.</param>
- public FormReader(Stream stream, Encoding encoding, ArrayPool<char> charPool)
- {
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
+ _buffer = charPool.Rent(_rentedCharPoolLength);
+ _charPool = charPool;
+ _reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024 * 2, leaveOpen: true);
+ }
- if (encoding == null)
- {
- throw new ArgumentNullException(nameof(encoding));
- }
+ /// <summary>
+ /// The limit on the number of form values to allow in ReadForm or ReadFormAsync.
+ /// </summary>
+ public int ValueCountLimit { get; set; } = DefaultValueCountLimit;
- _buffer = charPool.Rent(_rentedCharPoolLength);
- _charPool = charPool;
- _reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024 * 2, leaveOpen: true);
- }
+ /// <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;
- /// <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()
+ // 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 (ReadSucceeded())
{
- ReadNextPairImpl();
- if (ReadSucceeded())
- {
- return new KeyValuePair<string, string>(_currentKey, _currentValue);
- }
- return null;
+ return new KeyValuePair<string, string>(_currentKey, _currentValue);
}
+ return null;
+ }
- private void ReadNextPairImpl()
+ private void ReadNextPairImpl()
+ {
+ StartReadNextPair();
+ while (!_endOfStream)
{
- StartReadNextPair();
- while (!_endOfStream)
+ // Empty
+ if (_bufferCount == 0)
+ {
+ Buffer();
+ }
+ if (TryReadNextPair())
{
- // Empty
- if (_bufferCount == 0)
- {
- Buffer();
- }
- if (TryReadNextPair())
- {
- break;
- }
+ 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())
+ // 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 (ReadSucceeded())
{
- await ReadNextPairAsyncImpl(cancellationToken);
- if (ReadSucceeded())
- {
- return new KeyValuePair<string, string>(_currentKey, _currentValue);
- }
- return null;
+ return new KeyValuePair<string, string>(_currentKey, _currentValue);
}
+ return null;
+ }
- private async Task ReadNextPairAsyncImpl(CancellationToken cancellationToken = new CancellationToken())
+ private async Task ReadNextPairAsyncImpl(CancellationToken cancellationToken = new CancellationToken())
+ {
+ StartReadNextPair();
+ while (!_endOfStream)
{
- StartReadNextPair();
- while (!_endOfStream)
+ // Empty
+ if (_bufferCount == 0)
+ {
+ await BufferAsync(cancellationToken);
+ }
+ if (TryReadNextPair())
{
- // Empty
- if (_bufferCount == 0)
- {
- await BufferAsync(cancellationToken);
- }
- if (TryReadNextPair())
- {
- break;
- }
+ break;
}
}
+ }
- private void StartReadNextPair()
- {
- _currentKey = null;
- _currentValue = null;
- }
+ private void StartReadNextPair()
+ {
+ _currentKey = null;
+ _currentValue = null;
+ }
- private bool TryReadNextPair()
+ private bool TryReadNextPair()
+ {
+ if (_currentKey == null)
{
- if (_currentKey == null)
+ if (!TryReadWord('=', KeyLengthLimit, out _currentKey))
{
- if (!TryReadWord('=', KeyLengthLimit, out _currentKey))
- {
- return false;
- }
-
- if (_bufferCount == 0)
- {
- return false;
- }
+ return false;
}
- if (_currentValue == null)
+ if (_bufferCount == 0)
{
- if (!TryReadWord('&', ValueLengthLimit, out _currentValue))
- {
- return false;
- }
+ return false;
}
- return true;
}
- private bool TryReadWord(char separator, int limit, [NotNullWhen(true)] out string? value)
+ if (_currentValue == null)
{
- do
+ if (!TryReadWord('&', ValueLengthLimit, out _currentValue))
{
- if (ReadChar(separator, limit, out value))
- {
- return true;
- }
- } while (_bufferCount > 0);
- return false;
+ return false;
+ }
}
+ return true;
+ }
- private bool ReadChar(char separator, int limit, [NotNullWhen(true)] out string? word)
+ private bool TryReadWord(char separator, int limit, [NotNullWhen(true)] out string? value)
+ {
+ do
{
- // End
- if (_bufferCount == 0)
- {
- word = BuildWord();
- return true;
- }
-
- var c = _buffer[_bufferOffset++];
- _bufferCount--;
-
- if (c == separator)
+ if (ReadChar(separator, limit, out value))
{
- 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;
- }
+ } while (_bufferCount > 0);
+ return false;
+ }
- // '+' un-escapes to ' ', %HH un-escapes as ASCII (or utf-8?)
- private string BuildWord()
+ private bool ReadChar(char separator, int limit, [NotNullWhen(true)] out string? word)
+ {
+ // End
+ if (_bufferCount == 0)
{
- _builder.Replace('+', ' ');
- var result = _builder.ToString();
- _builder.Clear();
- return Uri.UnescapeDataString(result); // TODO: Replace this, it's not completely accurate.
+ word = BuildWord();
+ return true;
}
- private void Buffer()
- {
- _bufferOffset = 0;
- _bufferCount = _reader.Read(_buffer, 0, _buffer.Length);
- _endOfStream = _bufferCount == 0;
- }
+ var c = _buffer[_bufferOffset++];
+ _bufferCount--;
- private async Task BufferAsync(CancellationToken cancellationToken)
+ if (c == separator)
{
- // TODO: StreamReader doesn't support cancellation?
- cancellationToken.ThrowIfCancellationRequested();
- _bufferOffset = 0;
- _bufferCount = await _reader.ReadAsync(_buffer, 0, _buffer.Length);
- _endOfStream = _bufferCount == 0;
+ word = BuildWord();
+ return true;
}
-
- /// <summary>
- /// Parses text from an HTTP form body.
- /// </summary>
- /// <returns>The collection containing the parsed HTTP form body.</returns>
- public Dictionary<string, StringValues> ReadForm()
+ if (_builder.Length >= limit)
{
- var accumulator = new KeyValueAccumulator();
- while (!_endOfStream)
- {
- ReadNextPairImpl();
- Append(ref accumulator);
- }
- return accumulator.GetResults();
+ 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 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())
+ /// <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)
{
- var accumulator = new KeyValueAccumulator();
- while (!_endOfStream)
- {
- await ReadNextPairAsyncImpl(cancellationToken);
- Append(ref accumulator);
- }
- return accumulator.GetResults();
+ ReadNextPairImpl();
+ Append(ref accumulator);
}
+ return accumulator.GetResults();
+ }
- [MemberNotNullWhen(true, nameof(_currentKey), nameof(_currentValue))]
- private bool ReadSucceeded()
+ /// <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)
{
- return _currentKey != null && _currentValue != null;
+ await ReadNextPairAsyncImpl(cancellationToken);
+ Append(ref accumulator);
}
+ return accumulator.GetResults();
+ }
- private void Append(ref KeyValueAccumulator accumulator)
+ [MemberNotNullWhen(true, nameof(_currentKey), nameof(_currentValue))]
+ private bool ReadSucceeded()
+ {
+ return _currentKey != null && _currentValue != null;
+ }
+
+ private void Append(ref KeyValueAccumulator accumulator)
+ {
+ if (ReadSucceeded())
{
- if (ReadSucceeded())
+ accumulator.Append(_currentKey, _currentValue);
+ if (accumulator.ValueCount > ValueCountLimit)
{
- accumulator.Append(_currentKey, _currentValue);
- if (accumulator.ValueCount > ValueCountLimit)
- {
- throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
- }
+ throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
}
}
+ }
- /// <inheritdoc />
- public void Dispose()
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ if (!_disposed)
{
- if (!_disposed)
- {
- _disposed = true;
- _charPool.Return(_buffer);
- }
+ _disposed = true;
+ _charPool.Return(_buffer);
}
}
}
diff --git a/src/Http/WebUtilities/src/HttpRequestStreamReader.cs b/src/Http/WebUtilities/src/HttpRequestStreamReader.cs
index 428d967e7d..c017bd315d 100644
--- a/src/Http/WebUtilities/src/HttpRequestStreamReader.cs
+++ b/src/Http/WebUtilities/src/HttpRequestStreamReader.cs
@@ -10,473 +10,451 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// A <see cref="TextReader"/> to read the HTTP request stream.
+/// </summary>
+public class HttpRequestStreamReader : TextReader
{
- /// <summary>
- /// A <see cref="TextReader"/> to read the HTTP request stream.
- /// </summary>
- public class HttpRequestStreamReader : TextReader
- {
- private const int DefaultBufferSize = 1024;
+ private const int DefaultBufferSize = 1024;
- private readonly Stream _stream;
- private readonly Encoding _encoding;
- private readonly Decoder _decoder;
+ private readonly Stream _stream;
+ private readonly Encoding _encoding;
+ private readonly Decoder _decoder;
- private readonly ArrayPool<byte> _bytePool;
- private readonly ArrayPool<char> _charPool;
+ private readonly ArrayPool<byte> _bytePool;
+ private readonly ArrayPool<char> _charPool;
- private readonly int _byteBufferSize;
- private readonly byte[] _byteBuffer;
- private readonly char[] _charBuffer;
+ private readonly int _byteBufferSize;
+ private readonly byte[] _byteBuffer;
+ private readonly char[] _charBuffer;
- private int _charBufferIndex;
- private int _charsRead;
- private int _bytesRead;
+ private int _charBufferIndex;
+ private int _charsRead;
+ private int _bytesRead;
- private bool _isBlocked;
- private bool _disposed;
+ private bool _isBlocked;
+ private bool _disposed;
- /// <summary>
- /// Initializes a new instance of <see cref="HttpRequestStreamReader"/>.
- /// </summary>
- /// <param name="stream">The HTTP request <see cref="Stream"/>.</param>
- /// <param name="encoding">The character encoding to use.</param>
- public HttpRequestStreamReader(Stream stream, Encoding encoding)
- : this(stream, encoding, DefaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
- {
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="HttpRequestStreamReader"/>.
+ /// </summary>
+ /// <param name="stream">The HTTP request <see cref="Stream"/>.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ public HttpRequestStreamReader(Stream stream, Encoding encoding)
+ : this(stream, encoding, DefaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+ {
+ }
- /// <summary>
- /// Initializes a new instance of <see cref="HttpRequestStreamReader"/>.
- /// </summary>
- /// <param name="stream">The HTTP request <see cref="Stream"/>.</param>
- /// <param name="encoding">The character encoding to use.</param>
- /// <param name="bufferSize">The minimum buffer size.</param>
- public HttpRequestStreamReader(Stream stream, Encoding encoding, int bufferSize)
- : this(stream, encoding, bufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+ /// <summary>
+ /// Initializes a new instance of <see cref="HttpRequestStreamReader"/>.
+ /// </summary>
+ /// <param name="stream">The HTTP request <see cref="Stream"/>.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ /// <param name="bufferSize">The minimum buffer size.</param>
+ public HttpRequestStreamReader(Stream stream, Encoding encoding, int bufferSize)
+ : this(stream, encoding, bufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="HttpRequestStreamReader"/>.
+ /// </summary>
+ /// <param name="stream">The HTTP request <see cref="Stream"/>.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ /// <param name="bufferSize">The minimum buffer size.</param>
+ /// <param name="bytePool">The byte array pool to use.</param>
+ /// <param name="charPool">The char array pool to use.</param>
+ 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));
}
-
- /// <summary>
- /// Initializes a new instance of <see cref="HttpRequestStreamReader"/>.
- /// </summary>
- /// <param name="stream">The HTTP request <see cref="Stream"/>.</param>
- /// <param name="encoding">The character encoding to use.</param>
- /// <param name="bufferSize">The minimum buffer size.</param>
- /// <param name="bytePool">The byte array pool to use.</param>
- /// <param name="charPool">The char array pool to use.</param>
- public HttpRequestStreamReader(
- Stream stream,
- Encoding encoding,
- int bufferSize,
- ArrayPool<byte> bytePool,
- ArrayPool<char> charPool)
+ if (!stream.CanRead)
{
- _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));
+ throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
+ }
- if (bufferSize <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(bufferSize));
- }
- if (!stream.CanRead)
- {
- throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
- }
+ _byteBufferSize = bufferSize;
- _byteBufferSize = bufferSize;
+ _decoder = encoding.GetDecoder();
+ _byteBuffer = _bytePool.Rent(bufferSize);
- _decoder = encoding.GetDecoder();
- _byteBuffer = _bytePool.Rent(bufferSize);
+ try
+ {
+ var requiredLength = encoding.GetMaxCharCount(bufferSize);
+ _charBuffer = _charPool.Rent(requiredLength);
+ }
+ catch
+ {
+ _bytePool.Return(_byteBuffer);
- try
+ if (_charBuffer != null)
{
- var requiredLength = encoding.GetMaxCharCount(bufferSize);
- _charBuffer = _charPool.Rent(requiredLength);
+ _charPool.Return(_charBuffer);
}
- catch
- {
- _bytePool.Return(_byteBuffer);
- if (_charBuffer != null)
- {
- _charPool.Return(_charBuffer);
- }
-
- throw;
- }
+ throw;
}
+ }
- /// <inheritdoc />
- protected override void Dispose(bool disposing)
+ /// <inheritdoc />
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
{
- if (disposing && !_disposed)
- {
- _disposed = true;
+ _disposed = true;
- _bytePool.Return(_byteBuffer);
- _charPool.Return(_charBuffer);
- }
+ _bytePool.Return(_byteBuffer);
+ _charPool.Return(_charBuffer);
+ }
- base.Dispose(disposing);
+ base.Dispose(disposing);
+ }
+
+ /// <inheritdoc />
+ public override int Peek()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
}
- /// <inheritdoc />
- public override int Peek()
+ if (_charBufferIndex == _charsRead)
{
- if (_disposed)
+ if (_isBlocked || ReadIntoBuffer() == 0)
{
- throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+ return -1;
}
+ }
- if (_charBufferIndex == _charsRead)
- {
- if (_isBlocked || ReadIntoBuffer() == 0)
- {
- return -1;
- }
- }
+ return _charBuffer[_charBufferIndex];
+ }
- return _charBuffer[_charBufferIndex];
+ /// <inheritdoc />
+ public override int Read()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
}
- /// <inheritdoc />
- public override int Read()
+ if (_charBufferIndex == _charsRead)
{
- if (_disposed)
+ if (ReadIntoBuffer() == 0)
{
- throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+ return -1;
}
+ }
- if (_charBufferIndex == _charsRead)
- {
- if (ReadIntoBuffer() == 0)
- {
- return -1;
- }
- }
+ return _charBuffer[_charBufferIndex++];
+ }
- return _charBuffer[_charBufferIndex++];
+ /// <inheritdoc />
+ public override int Read(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
}
- /// <inheritdoc />
- public override int Read(char[] buffer, int index, int count)
+ if (index < 0)
{
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
+ if (count < 0 || index + count > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
- if (count < 0 || index + count > buffer.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(count));
- }
+ var span = new Span<char>(buffer, index, count);
+ return Read(span);
+ }
- var span = new Span<char>(buffer, index, count);
- return Read(span);
+ /// <inheritdoc />
+ public override int Read(Span<char> buffer)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
}
- /// <inheritdoc />
- public override int Read(Span<char> buffer)
+ var count = buffer.Length;
+ var charsRead = 0;
+ while (count > 0)
{
- if (buffer == null)
+ var charsRemaining = _charsRead - _charBufferIndex;
+ if (charsRemaining == 0)
{
- throw new ArgumentNullException(nameof(buffer));
+ charsRemaining = ReadIntoBuffer();
}
- if (_disposed)
+ if (charsRemaining == 0)
{
- throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+ break; // We're at EOF
}
- var count = buffer.Length;
- var charsRead = 0;
- while (count > 0)
+ if (charsRemaining > count)
{
- var charsRemaining = _charsRead - _charBufferIndex;
- if (charsRemaining == 0)
- {
- charsRemaining = ReadIntoBuffer();
- }
-
- if (charsRemaining == 0)
- {
- break; // We're at EOF
- }
-
- if (charsRemaining > count)
- {
- charsRemaining = count;
- }
+ charsRemaining = count;
+ }
- var source = new ReadOnlySpan<char>(_charBuffer, _charBufferIndex, charsRemaining);
- source.CopyTo(buffer);
+ var source = new ReadOnlySpan<char>(_charBuffer, _charBufferIndex, charsRemaining);
+ source.CopyTo(buffer);
- _charBufferIndex += charsRemaining;
+ _charBufferIndex += charsRemaining;
- charsRead += charsRemaining;
- count -= charsRemaining;
+ charsRead += charsRemaining;
+ count -= charsRemaining;
- buffer = buffer.Slice(charsRemaining, count);
+ buffer = buffer.Slice(charsRemaining, count);
- // 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;
- }
+ // 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;
+ return charsRead;
+ }
+
+ /// <inheritdoc />
+ public override Task<int> ReadAsync(char[] buffer, int index, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
}
- /// <inheritdoc />
- public override Task<int> ReadAsync(char[] buffer, int index, int count)
+ if (index < 0)
{
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
+ if (count < 0 || index + count > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
- if (count < 0 || index + count > buffer.Length)
- {
- throw new ArgumentOutOfRangeException(nameof(count));
- }
+ var memory = new Memory<char>(buffer, index, count);
+ return ReadAsync(memory).AsTask();
+ }
- var memory = new Memory<char>(buffer, index, count);
- return ReadAsync(memory).AsTask();
+ /// <inheritdoc />
+ [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
+ public override async ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
}
- /// <inheritdoc />
- [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
- public override async ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default)
+ if (_charBufferIndex == _charsRead && await ReadIntoBufferAsync() == 0)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
- }
+ return 0;
+ }
- if (_charBufferIndex == _charsRead && await ReadIntoBufferAsync() == 0)
- {
- return 0;
- }
+ var count = buffer.Length;
- var count = buffer.Length;
+ var charsRead = 0;
+ while (count > 0)
+ {
+ // n is the characters available in _charBuffer
+ var charsRemaining = _charsRead - _charBufferIndex;
- var charsRead = 0;
- while (count > 0)
+ // charBuffer is empty, let's read from the stream
+ if (charsRemaining == 0)
{
- // n is the characters available in _charBuffer
- var charsRemaining = _charsRead - _charBufferIndex;
+ _charsRead = 0;
+ _charBufferIndex = 0;
+ _bytesRead = 0;
- // charBuffer is empty, let's read from the stream
- if (charsRemaining == 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
{
- _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(charsRemaining == 0);
+ _bytesRead = await _stream.ReadAsync(_byteBuffer.AsMemory(0, _byteBufferSize), cancellationToken);
+ if (_bytesRead == 0) // EOF
{
- Debug.Assert(charsRemaining == 0);
- _bytesRead = await _stream.ReadAsync(_byteBuffer.AsMemory(0, _byteBufferSize), cancellationToken);
- if (_bytesRead == 0) // EOF
- {
- _isBlocked = true;
- break;
- }
-
- // _isBlocked == whether we read fewer bytes than we asked for.
- _isBlocked = (_bytesRead < _byteBufferSize);
-
- Debug.Assert(charsRemaining == 0);
-
- _charBufferIndex = 0;
- charsRemaining = _decoder.GetChars(
- _byteBuffer,
- 0,
- _bytesRead,
- _charBuffer,
- 0);
-
- Debug.Assert(charsRemaining > 0);
-
- _charsRead += charsRemaining; // Number of chars in StreamReader's buffer.
+ _isBlocked = true;
+ break;
}
- while (charsRemaining == 0);
- if (charsRemaining == 0)
- {
- break; // We're at EOF
- }
- }
-
- // Got more chars in charBuffer than the user requested
- if (charsRemaining > count)
- {
- charsRemaining = count;
- }
+ // _isBlocked == whether we read fewer bytes than we asked for.
+ _isBlocked = (_bytesRead < _byteBufferSize);
- var source = new Memory<char>(_charBuffer, _charBufferIndex, charsRemaining);
- source.CopyTo(buffer);
+ Debug.Assert(charsRemaining == 0);
- _charBufferIndex += charsRemaining;
+ _charBufferIndex = 0;
+ charsRemaining = _decoder.GetChars(
+ _byteBuffer,
+ 0,
+ _bytesRead,
+ _charBuffer,
+ 0);
- charsRead += charsRemaining;
- count -= charsRemaining;
+ Debug.Assert(charsRemaining > 0);
- buffer = buffer.Slice(charsRemaining, count);
+ _charsRead += charsRemaining; // Number of chars in StreamReader's buffer.
+ }
+ while (charsRemaining == 0);
- // 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)
+ if (charsRemaining == 0)
{
- break;
+ break; // We're at EOF
}
}
- return charsRead;
- }
-
- /// <inheritdoc />
- public override async Task<string?> ReadLineAsync()
- {
- if (_disposed)
+ // Got more chars in charBuffer than the user requested
+ if (charsRemaining > count)
{
- throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+ charsRemaining = count;
}
- StringBuilder? sb = null;
- var consumeLineFeed = false;
+ var source = new Memory<char>(_charBuffer, _charBufferIndex, charsRemaining);
+ source.CopyTo(buffer);
- while (true)
- {
- if (_charBufferIndex == _charsRead)
- {
- if (await ReadIntoBufferAsync() == 0)
- {
- // reached EOF, we need to return null if we were at EOF from the beginning
- return sb?.ToString();
- }
- }
+ _charBufferIndex += charsRemaining;
- var stepResult = ReadLineStep(ref sb, ref consumeLineFeed);
+ charsRead += charsRemaining;
+ count -= charsRemaining;
- if (stepResult.Completed)
- {
- return stepResult.Result ?? sb?.ToString();
- }
+ buffer = buffer.Slice(charsRemaining, count);
- continue;
+ // 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;
}
}
- // Reads a line. A line is defined as a sequence of characters followed by
- // a carriage return ('\r'), a line feed ('\n'), or a carriage return
- // immediately followed by a line feed. The resulting string does not
- // contain the terminating carriage return and/or line feed. The returned
- // value is null if the end of the input stream has been reached.
- /// <inheritdoc />
- public override string? ReadLine()
+ return charsRead;
+ }
+
+ /// <inheritdoc />
+ public override async Task<string?> ReadLineAsync()
+ {
+ if (_disposed)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
- }
+ throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+ }
- StringBuilder? sb = null;
- var consumeLineFeed = false;
+ StringBuilder? sb = null;
+ var consumeLineFeed = false;
- while (true)
+ while (true)
+ {
+ if (_charBufferIndex == _charsRead)
{
- if (_charBufferIndex == _charsRead)
+ if (await ReadIntoBufferAsync() == 0)
{
- if (ReadIntoBuffer() == 0)
- {
- // reached EOF, we need to return null if we were at EOF from the beginning
- return sb?.ToString();
- }
+ // reached EOF, we need to return null if we were at EOF from the beginning
+ return sb?.ToString();
}
+ }
- var stepResult = ReadLineStep(ref sb, ref consumeLineFeed);
+ var stepResult = ReadLineStep(ref sb, ref consumeLineFeed);
- if (stepResult.Completed)
- {
- return stepResult.Result ?? sb?.ToString();
- }
+ if (stepResult.Completed)
+ {
+ return stepResult.Result ?? sb?.ToString();
}
+
+ continue;
}
+ }
- private ReadLineStepResult ReadLineStep(ref StringBuilder? sb, ref bool consumeLineFeed)
+ // Reads a line. A line is defined as a sequence of characters followed by
+ // a carriage return ('\r'), a line feed ('\n'), or a carriage return
+ // immediately followed by a line feed. The resulting string does not
+ // contain the terminating carriage return and/or line feed. The returned
+ // value is null if the end of the input stream has been reached.
+ /// <inheritdoc />
+ public override string? ReadLine()
+ {
+ if (_disposed)
{
- const char carriageReturn = '\r';
- const char lineFeed = '\n';
+ throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+ }
- if (consumeLineFeed)
+ StringBuilder? sb = null;
+ var consumeLineFeed = false;
+
+ while (true)
+ {
+ if (_charBufferIndex == _charsRead)
{
- if (_charBuffer[_charBufferIndex] == lineFeed)
+ if (ReadIntoBuffer() == 0)
{
- _charBufferIndex++;
+ // reached EOF, we need to return null if we were at EOF from the beginning
+ return sb?.ToString();
}
- return ReadLineStepResult.Done;
}
- var span = new Span<char>(_charBuffer, _charBufferIndex, _charsRead - _charBufferIndex);
+ var stepResult = ReadLineStep(ref sb, ref consumeLineFeed);
- var index = span.IndexOfAny(carriageReturn, lineFeed);
+ if (stepResult.Completed)
+ {
+ return stepResult.Result ?? sb?.ToString();
+ }
+ }
+ }
+
+ private ReadLineStepResult ReadLineStep(ref StringBuilder? sb, ref bool consumeLineFeed)
+ {
+ const char carriageReturn = '\r';
+ const char lineFeed = '\n';
- if (index != -1)
+ if (consumeLineFeed)
+ {
+ if (_charBuffer[_charBufferIndex] == lineFeed)
{
- if (span[index] == carriageReturn)
- {
- span = span.Slice(0, index);
- _charBufferIndex += index + 1;
+ _charBufferIndex++;
+ }
+ return ReadLineStepResult.Done;
+ }
- if (_charBufferIndex < _charsRead)
- {
- // consume following line feed
- if (_charBuffer[_charBufferIndex] == lineFeed)
- {
- _charBufferIndex++;
- }
-
- if (sb != null)
- {
- sb.Append(span);
- return ReadLineStepResult.Done;
- }
-
- // perf: if the new line is found in first pass, we skip the StringBuilder
- return ReadLineStepResult.FromResult(span.ToString());
- }
+ var span = new Span<char>(_charBuffer, _charBufferIndex, _charsRead - _charBufferIndex);
- // we where at the end of buffer, we need to read more to check for a line feed to consume
- sb ??= new StringBuilder();
- sb.Append(span);
- consumeLineFeed = true;
- return ReadLineStepResult.Continue;
- }
+ var index = span.IndexOfAny(carriageReturn, lineFeed);
+
+ if (index != -1)
+ {
+ if (span[index] == carriageReturn)
+ {
+ span = span.Slice(0, index);
+ _charBufferIndex += index + 1;
- if (span[index] == lineFeed)
+ if (_charBufferIndex < _charsRead)
{
- span = span.Slice(0, index);
- _charBufferIndex += index + 1;
+ // consume following line feed
+ if (_charBuffer[_charBufferIndex] == lineFeed)
+ {
+ _charBufferIndex++;
+ }
if (sb != null)
{
@@ -487,102 +465,123 @@ namespace Microsoft.AspNetCore.WebUtilities
// perf: if the new line is found in first pass, we skip the StringBuilder
return ReadLineStepResult.FromResult(span.ToString());
}
- }
- sb ??= new StringBuilder();
- sb.Append(span);
- _charBufferIndex = _charsRead;
-
- return ReadLineStepResult.Continue;
- }
-
- private int ReadIntoBuffer()
- {
- _charsRead = 0;
- _charBufferIndex = 0;
- _bytesRead = 0;
+ // we where at the end of buffer, we need to read more to check for a line feed to consume
+ sb ??= new StringBuilder();
+ sb.Append(span);
+ consumeLineFeed = true;
+ return ReadLineStepResult.Continue;
+ }
- do
+ if (span[index] == lineFeed)
{
- _bytesRead = _stream.Read(_byteBuffer, 0, _byteBufferSize);
- if (_bytesRead == 0) // We're at EOF
+ span = span.Slice(0, index);
+ _charBufferIndex += index + 1;
+
+ if (sb != null)
{
- return _charsRead;
+ sb.Append(span);
+ return ReadLineStepResult.Done;
}
- _isBlocked = (_bytesRead < _byteBufferSize);
- _charsRead += _decoder.GetChars(
- _byteBuffer,
- 0,
- _bytesRead,
- _charBuffer,
- _charsRead);
+ // perf: if the new line is found in first pass, we skip the StringBuilder
+ return ReadLineStepResult.FromResult(span.ToString());
}
- while (_charsRead == 0);
-
- return _charsRead;
}
- private async Task<int> ReadIntoBufferAsync()
- {
- _charsRead = 0;
- _charBufferIndex = 0;
- _bytesRead = 0;
+ sb ??= new StringBuilder();
+ sb.Append(span);
+ _charBufferIndex = _charsRead;
- do
- {
- _bytesRead = await _stream.ReadAsync(_byteBuffer.AsMemory(0, _byteBufferSize)).ConfigureAwait(false);
- if (_bytesRead == 0)
- {
- // We're at EOF
- return _charsRead;
- }
+ return ReadLineStepResult.Continue;
+ }
- // _isBlocked == whether we read fewer bytes than we asked for.
- _isBlocked = (_bytesRead < _byteBufferSize);
+ private int ReadIntoBuffer()
+ {
+ _charsRead = 0;
+ _charBufferIndex = 0;
+ _bytesRead = 0;
- _charsRead += _decoder.GetChars(
- _byteBuffer,
- 0,
- _bytesRead,
- _charBuffer,
- _charsRead);
+ do
+ {
+ _bytesRead = _stream.Read(_byteBuffer, 0, _byteBufferSize);
+ if (_bytesRead == 0) // We're at EOF
+ {
+ return _charsRead;
}
- while (_charsRead == 0);
- 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;
- /// <inheritdoc />
- public override async Task<string> ReadToEndAsync()
+ do
{
- StringBuilder sb = new StringBuilder(_charsRead - _charBufferIndex);
- do
+ _bytesRead = await _stream.ReadAsync(_byteBuffer.AsMemory(0, _byteBufferSize)).ConfigureAwait(false);
+ if (_bytesRead == 0)
{
- int tmpCharPos = _charBufferIndex;
- sb.Append(_charBuffer, tmpCharPos, _charsRead - tmpCharPos);
- _charBufferIndex = _charsRead; // We consumed these characters
- await ReadIntoBufferAsync().ConfigureAwait(false);
- } while (_charsRead > 0);
+ // We're at EOF
+ return _charsRead;
+ }
- return sb.ToString();
+ // _isBlocked == whether we read fewer bytes than we asked for.
+ _isBlocked = (_bytesRead < _byteBufferSize);
+
+ _charsRead += _decoder.GetChars(
+ _byteBuffer,
+ 0,
+ _bytesRead,
+ _charBuffer,
+ _charsRead);
}
+ while (_charsRead == 0);
- private readonly struct ReadLineStepResult
+ return _charsRead;
+ }
+
+ /// <inheritdoc />
+ public override async Task<string> ReadToEndAsync()
+ {
+ StringBuilder sb = new StringBuilder(_charsRead - _charBufferIndex);
+ do
{
- public static readonly ReadLineStepResult Done = new ReadLineStepResult(true, null);
- public static readonly ReadLineStepResult Continue = new ReadLineStepResult(false, null);
+ int tmpCharPos = _charBufferIndex;
+ sb.Append(_charBuffer, tmpCharPos, _charsRead - tmpCharPos);
+ _charBufferIndex = _charsRead; // We consumed these characters
+ await ReadIntoBufferAsync().ConfigureAwait(false);
+ } while (_charsRead > 0);
- public static ReadLineStepResult FromResult(string value) => new ReadLineStepResult(true, value);
+ return sb.ToString();
+ }
- private ReadLineStepResult(bool completed, string? result)
- {
- Completed = completed;
- Result = result;
- }
+ private readonly struct ReadLineStepResult
+ {
+ public static readonly ReadLineStepResult Done = new ReadLineStepResult(true, null);
+ public static readonly ReadLineStepResult Continue = new ReadLineStepResult(false, null);
- public bool Completed { get; }
- public string? Result { get; }
+ public static ReadLineStepResult FromResult(string value) => new ReadLineStepResult(true, value);
+
+ private ReadLineStepResult(bool completed, string? result)
+ {
+ Completed = completed;
+ Result = result;
}
+
+ public bool Completed { get; }
+ public string? Result { get; }
}
}
diff --git a/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs b/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
index 2c5316f2ff..6960429e85 100644
--- a/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
+++ b/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
@@ -11,590 +11,589 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Writes to the HTTP response <see cref="Stream"/> using the supplied <see cref="System.Text.Encoding"/>.
+/// It does not write the BOM and also does not close the stream.
+/// </summary>
+public class HttpResponseStreamWriter : TextWriter
{
+ internal const int DefaultBufferSize = 16 * 1024;
+
+ private readonly Stream _stream;
+ private readonly Encoder _encoder;
+ private readonly ArrayPool<byte> _bytePool;
+ private readonly ArrayPool<char> _charPool;
+ private readonly int _charBufferSize;
+
+ private readonly byte[] _byteBuffer;
+ private readonly char[] _charBuffer;
+
+ private int _charBufferCount;
+ private bool _disposed;
+
/// <summary>
- /// Writes to the HTTP response <see cref="Stream"/> using the supplied <see cref="System.Text.Encoding"/>.
- /// It does not write the BOM and also does not close the stream.
+ /// Initializes a new instance of <see cref="HttpResponseStreamWriter"/>.
/// </summary>
- public class HttpResponseStreamWriter : TextWriter
+ /// <param name="stream">The HTTP response <see cref="Stream"/>.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ public HttpResponseStreamWriter(Stream stream, Encoding encoding)
+ : this(stream, encoding, DefaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
{
- internal const int DefaultBufferSize = 16 * 1024;
-
- private readonly Stream _stream;
- private readonly Encoder _encoder;
- private readonly ArrayPool<byte> _bytePool;
- private readonly ArrayPool<char> _charPool;
- private readonly int _charBufferSize;
-
- private readonly byte[] _byteBuffer;
- private readonly char[] _charBuffer;
-
- private int _charBufferCount;
- private bool _disposed;
-
- /// <summary>
- /// Initializes a new instance of <see cref="HttpResponseStreamWriter"/>.
- /// </summary>
- /// <param name="stream">The HTTP response <see cref="Stream"/>.</param>
- /// <param name="encoding">The character encoding to use.</param>
- public HttpResponseStreamWriter(Stream stream, Encoding encoding)
- : this(stream, encoding, DefaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of <see cref="HttpResponseStreamWriter"/>.
- /// </summary>
- /// <param name="stream">The HTTP response <see cref="Stream"/>.</param>
- /// <param name="encoding">The character encoding to use.</param>
- /// <param name="bufferSize">The minimum buffer size.</param>
- public HttpResponseStreamWriter(Stream stream, Encoding encoding, int bufferSize)
- : this(stream, encoding, bufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
- {
- }
-
- /// <summary>
- /// Initializes a new instance of <see cref="HttpResponseStreamWriter"/>.
- /// </summary>
- /// <param name="stream">The HTTP response <see cref="Stream"/>.</param>
- /// <param name="encoding">The character encoding to use.</param>
- /// <param name="bufferSize">The minimum buffer size.</param>
- /// <param name="bytePool">The byte array pool.</param>
- /// <param name="charPool">The char array pool.</param>
- 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);
+ /// <summary>
+ /// Initializes a new instance of <see cref="HttpResponseStreamWriter"/>.
+ /// </summary>
+ /// <param name="stream">The HTTP response <see cref="Stream"/>.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ /// <param name="bufferSize">The minimum buffer size.</param>
+ public HttpResponseStreamWriter(Stream stream, Encoding encoding, int bufferSize)
+ : this(stream, encoding, bufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+ {
+ }
- if (_byteBuffer != null)
- {
- bytePool.Return(_byteBuffer);
- }
+ /// <summary>
+ /// Initializes a new instance of <see cref="HttpResponseStreamWriter"/>.
+ /// </summary>
+ /// <param name="stream">The HTTP response <see cref="Stream"/>.</param>
+ /// <param name="encoding">The character encoding to use.</param>
+ /// <param name="bufferSize">The minimum buffer size.</param>
+ /// <param name="bytePool">The byte array pool.</param>
+ /// <param name="charPool">The char array pool.</param>
+ 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));
- throw;
- }
+ if (bufferSize <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize));
+ }
+ if (!_stream.CanWrite)
+ {
+ throw new ArgumentException(Resources.HttpResponseStreamWriter_StreamNotWritable, nameof(stream));
}
- /// <inheritdoc/>
- public override Encoding Encoding { get; }
+ _charBufferSize = bufferSize;
+
+ _encoder = encoding.GetEncoder();
+ _charBuffer = charPool.Rent(bufferSize);
- /// <inheritdoc/>
- public override void Write(char value)
+ try
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
- }
+ var requiredLength = encoding.GetMaxByteCount(bufferSize);
+ _byteBuffer = bytePool.Rent(requiredLength);
+ }
+ catch
+ {
+ charPool.Return(_charBuffer);
- if (_charBufferCount == _charBufferSize)
+ if (_byteBuffer != null)
{
- FlushInternal(flushEncoder: false);
+ bytePool.Return(_byteBuffer);
}
- _charBuffer[_charBufferCount] = value;
- _charBufferCount++;
+ throw;
}
+ }
- /// <inheritdoc/>
- public override void Write(char[] values, int index, int count)
+ /// <inheritdoc/>
+ public override Encoding Encoding { get; }
+
+ /// <inheritdoc/>
+ public override void Write(char value)
+ {
+ if (_disposed)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
- }
+ throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+ }
- if (values == null)
- {
- return;
- }
+ if (_charBufferCount == _charBufferSize)
+ {
+ FlushInternal(flushEncoder: false);
+ }
- while (count > 0)
- {
- if (_charBufferCount == _charBufferSize)
- {
- FlushInternal(flushEncoder: false);
- }
+ _charBuffer[_charBufferCount] = value;
+ _charBufferCount++;
+ }
- CopyToCharBuffer(values, ref index, ref count);
- }
+ /// <inheritdoc/>
+ public override void Write(char[] values, int index, int count)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
}
- /// <inheritdoc/>
- public override void Write(ReadOnlySpan<char> value)
+ if (values == null)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
- }
+ return;
+ }
- var remaining = value.Length;
- while (remaining > 0)
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
{
- if (_charBufferCount == _charBufferSize)
- {
- FlushInternal(flushEncoder: false);
- }
+ FlushInternal(flushEncoder: false);
+ }
- var written = CopyToCharBuffer(value);
+ CopyToCharBuffer(values, ref index, ref count);
+ }
+ }
- remaining -= written;
- value = value.Slice(written);
- };
+ /// <inheritdoc/>
+ public override void Write(ReadOnlySpan<char> value)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
}
- /// <inheritdoc/>
- public override void Write(string? value)
+ var remaining = value.Length;
+ while (remaining > 0)
{
- if (_disposed)
+ if (_charBufferCount == _charBufferSize)
{
- throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+ FlushInternal(flushEncoder: false);
}
- if (value == null)
- {
- return;
- }
+ var written = CopyToCharBuffer(value);
- var count = value.Length;
- var index = 0;
- while (count > 0)
- {
- if (_charBufferCount == _charBufferSize)
- {
- FlushInternal(flushEncoder: false);
- }
+ remaining -= written;
+ value = value.Slice(written);
+ };
+ }
- CopyToCharBuffer(value, ref index, ref count);
- }
+ /// <inheritdoc/>
+ public override void Write(string? value)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
}
- /// <inheritdoc/>
- public override void WriteLine(ReadOnlySpan<char> value)
+ if (value == null)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
- }
-
- Write(value);
- Write(NewLine);
+ return;
}
- /// <inheritdoc/>
- public override Task WriteAsync(char value)
+ var count = value.Length;
+ var index = 0;
+ while (count > 0)
{
- if (_disposed)
- {
- return GetObjectDisposedTask();
- }
-
if (_charBufferCount == _charBufferSize)
{
- return WriteAsyncAwaited(value);
- }
- else
- {
- // Enough room in buffer, no need to go async
- _charBuffer[_charBufferCount] = value;
- _charBufferCount++;
- return Task.CompletedTask;
+ FlushInternal(flushEncoder: false);
}
+
+ CopyToCharBuffer(value, ref index, ref count);
}
+ }
- private async Task WriteAsyncAwaited(char value)
+ /// <inheritdoc/>
+ public override void WriteLine(ReadOnlySpan<char> value)
+ {
+ if (_disposed)
{
- Debug.Assert(_charBufferCount == _charBufferSize);
+ throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+ }
- await FlushInternalAsync(flushEncoder: false);
+ Write(value);
+ Write(NewLine);
+ }
+ /// <inheritdoc/>
+ public override Task WriteAsync(char value)
+ {
+ if (_disposed)
+ {
+ return GetObjectDisposedTask();
+ }
+
+ if (_charBufferCount == _charBufferSize)
+ {
+ return WriteAsyncAwaited(value);
+ }
+ else
+ {
+ // Enough room in buffer, no need to go async
_charBuffer[_charBufferCount] = value;
_charBufferCount++;
+ return Task.CompletedTask;
}
+ }
- /// <inheritdoc/>
- public override Task WriteAsync(char[] values, int index, int count)
- {
- if (_disposed)
- {
- return GetObjectDisposedTask();
- }
+ private async Task WriteAsyncAwaited(char value)
+ {
+ Debug.Assert(_charBufferCount == _charBufferSize);
- if (values == null || count == 0)
- {
- return Task.CompletedTask;
- }
+ await FlushInternalAsync(flushEncoder: false);
- var remaining = _charBufferSize - _charBufferCount;
- if (remaining >= count)
- {
- // Enough room in buffer, no need to go async
- CopyToCharBuffer(values, ref index, ref count);
- return Task.CompletedTask;
- }
- else
- {
- return WriteAsyncAwaited(values, index, count);
- }
- }
+ _charBuffer[_charBufferCount] = value;
+ _charBufferCount++;
+ }
- private async Task WriteAsyncAwaited(char[] values, int index, int count)
+ /// <inheritdoc/>
+ public override Task WriteAsync(char[] values, int index, int count)
+ {
+ if (_disposed)
{
- Debug.Assert(count > 0);
- Debug.Assert(_charBufferSize - _charBufferCount < count);
-
- while (count > 0)
- {
- if (_charBufferCount == _charBufferSize)
- {
- await FlushInternalAsync(flushEncoder: false);
- }
+ return GetObjectDisposedTask();
+ }
- CopyToCharBuffer(values, ref index, ref count);
- }
+ if (values == null || count == 0)
+ {
+ return Task.CompletedTask;
}
- /// <inheritdoc/>
- public override Task WriteAsync(string? value)
+ var remaining = _charBufferSize - _charBufferCount;
+ if (remaining >= count)
{
- if (_disposed)
- {
- return GetObjectDisposedTask();
- }
+ // Enough room in buffer, no need to go async
+ CopyToCharBuffer(values, ref index, ref count);
+ return Task.CompletedTask;
+ }
+ else
+ {
+ return WriteAsyncAwaited(values, index, count);
+ }
+ }
- if (string.IsNullOrEmpty(value))
- {
- return Task.CompletedTask;
- }
+ private async Task WriteAsyncAwaited(char[] values, int index, int count)
+ {
+ Debug.Assert(count > 0);
+ Debug.Assert(_charBufferSize - _charBufferCount < count);
- var remaining = _charBufferSize - _charBufferCount;
- if (remaining >= value.Length)
- {
- // Enough room in buffer, no need to go async
- CopyToCharBuffer(value);
- return Task.CompletedTask;
- }
- else
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
{
- return WriteAsyncAwaited(value);
+ await FlushInternalAsync(flushEncoder: false);
}
+
+ CopyToCharBuffer(values, ref index, ref count);
}
+ }
- private async Task WriteAsyncAwaited(string value)
+ /// <inheritdoc/>
+ public override Task WriteAsync(string? value)
+ {
+ if (_disposed)
{
- var count = value.Length;
-
- Debug.Assert(count > 0);
- Debug.Assert(_charBufferSize - _charBufferCount < count);
-
- var index = 0;
- while (count > 0)
- {
- if (_charBufferCount == _charBufferSize)
- {
- await FlushInternalAsync(flushEncoder: false);
- }
+ return GetObjectDisposedTask();
+ }
- CopyToCharBuffer(value, ref index, ref count);
- }
+ if (string.IsNullOrEmpty(value))
+ {
+ return Task.CompletedTask;
}
- /// <inheritdoc/>
- [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
- public override Task WriteAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
+ var remaining = _charBufferSize - _charBufferCount;
+ if (remaining >= value.Length)
{
- if (_disposed)
- {
- return GetObjectDisposedTask();
- }
+ // Enough room in buffer, no need to go async
+ CopyToCharBuffer(value);
+ return Task.CompletedTask;
+ }
+ else
+ {
+ return WriteAsyncAwaited(value);
+ }
+ }
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
+ private async Task WriteAsyncAwaited(string value)
+ {
+ var count = value.Length;
- if (value.IsEmpty)
- {
- return Task.CompletedTask;
- }
+ Debug.Assert(count > 0);
+ Debug.Assert(_charBufferSize - _charBufferCount < count);
- var remaining = _charBufferSize - _charBufferCount;
- if (remaining >= value.Length)
- {
- // Enough room in buffer, no need to go async
- CopyToCharBuffer(value.Span);
- return Task.CompletedTask;
- }
- else
+ var index = 0;
+ while (count > 0)
+ {
+ if (_charBufferCount == _charBufferSize)
{
- return WriteAsyncAwaited(value);
+ await FlushInternalAsync(flushEncoder: false);
}
+
+ CopyToCharBuffer(value, ref index, ref count);
}
+ }
- private async Task WriteAsyncAwaited(ReadOnlyMemory<char> value)
+ /// <inheritdoc/>
+ [SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
+ public override Task WriteAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
+ {
+ if (_disposed)
{
- Debug.Assert(value.Length > 0);
- Debug.Assert(_charBufferSize - _charBufferCount < value.Length);
+ return GetObjectDisposedTask();
+ }
- var remaining = value.Length;
- while (remaining > 0)
- {
- if (_charBufferCount == _charBufferSize)
- {
- await FlushInternalAsync(flushEncoder: false);
- }
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
- var written = CopyToCharBuffer(value.Span);
+ if (value.IsEmpty)
+ {
+ return Task.CompletedTask;
+ }
- remaining -= written;
- value = value.Slice(written);
- };
+ var remaining = _charBufferSize - _charBufferCount;
+ if (remaining >= value.Length)
+ {
+ // Enough room in buffer, no need to go async
+ CopyToCharBuffer(value.Span);
+ return Task.CompletedTask;
+ }
+ else
+ {
+ return WriteAsyncAwaited(value);
}
+ }
- /// <inheritdoc/>
- public override Task WriteLineAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
+ private async Task WriteAsyncAwaited(ReadOnlyMemory<char> value)
+ {
+ Debug.Assert(value.Length > 0);
+ Debug.Assert(_charBufferSize - _charBufferCount < value.Length);
+
+ var remaining = value.Length;
+ while (remaining > 0)
{
- if (_disposed)
+ if (_charBufferCount == _charBufferSize)
{
- return GetObjectDisposedTask();
+ await FlushInternalAsync(flushEncoder: false);
}
- if (cancellationToken.IsCancellationRequested)
- {
- return Task.FromCanceled(cancellationToken);
- }
+ var written = CopyToCharBuffer(value.Span);
- if (value.IsEmpty && NewLine.Length == 0)
- {
- return Task.CompletedTask;
- }
+ remaining -= written;
+ value = value.Slice(written);
+ };
+ }
- var remaining = _charBufferSize - _charBufferCount;
- if (remaining >= value.Length + NewLine.Length)
- {
- // Enough room in buffer, no need to go async
- CopyToCharBuffer(value.Span);
- CopyToCharBuffer(NewLine);
- return Task.CompletedTask;
- }
- else
- {
- return WriteLineAsyncAwaited(value);
- }
+ /// <inheritdoc/>
+ public override Task WriteLineAsync(ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
+ {
+ if (_disposed)
+ {
+ return GetObjectDisposedTask();
}
- private async Task WriteLineAsyncAwaited(ReadOnlyMemory<char> value)
+ if (cancellationToken.IsCancellationRequested)
{
- await WriteAsync(value);
- await WriteAsync(NewLine);
+ return Task.FromCanceled(cancellationToken);
}
- // We want to flush the stream when Flush/FlushAsync is explicitly
- // called by the user (example: from a Razor view).
-
- /// <inheritdoc/>
- public override void Flush()
+ if (value.IsEmpty && NewLine.Length == 0)
{
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
- }
-
- FlushInternal(flushEncoder: true);
+ return Task.CompletedTask;
}
- /// <inheritdoc/>
- public override Task FlushAsync()
+ var remaining = _charBufferSize - _charBufferCount;
+ if (remaining >= value.Length + NewLine.Length)
{
- if (_disposed)
- {
- return GetObjectDisposedTask();
- }
-
- return FlushInternalAsync(flushEncoder: true);
+ // Enough room in buffer, no need to go async
+ CopyToCharBuffer(value.Span);
+ CopyToCharBuffer(NewLine);
+ return Task.CompletedTask;
}
-
- /// <inheritdoc/>
- protected override void Dispose(bool disposing)
+ else
{
- if (disposing && !_disposed)
- {
- _disposed = true;
- try
- {
- FlushInternal(flushEncoder: true);
- }
- finally
- {
- _bytePool.Return(_byteBuffer);
- _charPool.Return(_charBuffer);
- }
- }
-
- base.Dispose(disposing);
+ return WriteLineAsyncAwaited(value);
}
+ }
+
+ private async Task WriteLineAsyncAwaited(ReadOnlyMemory<char> value)
+ {
+ await WriteAsync(value);
+ await WriteAsync(NewLine);
+ }
+
+ // We want to flush the stream when Flush/FlushAsync is explicitly
+ // called by the user (example: from a Razor view).
- /// <inheritdoc/>
- public override async ValueTask DisposeAsync()
+ /// <inheritdoc/>
+ public override void Flush()
+ {
+ if (_disposed)
{
- if (!_disposed)
- {
- _disposed = true;
- try
- {
- await FlushInternalAsync(flushEncoder: true);
- }
- finally
- {
- _bytePool.Return(_byteBuffer);
- _charPool.Return(_charBuffer);
- }
- }
+ throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+ }
- await base.DisposeAsync();
+ FlushInternal(flushEncoder: true);
+ }
+
+ /// <inheritdoc/>
+ public override Task FlushAsync()
+ {
+ if (_disposed)
+ {
+ return GetObjectDisposedTask();
}
- // Note: our FlushInternal method does NOT flush the underlying stream. This would result in
- // chunking.
- private void FlushInternal(bool flushEncoder)
+ return FlushInternalAsync(flushEncoder: true);
+ }
+
+ /// <inheritdoc/>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
{
- if (_charBufferCount == 0)
+ _disposed = true;
+ try
{
- return;
+ FlushInternal(flushEncoder: true);
}
-
- var count = _encoder.GetBytes(
- _charBuffer,
- 0,
- _charBufferCount,
- _byteBuffer,
- 0,
- flush: flushEncoder);
-
- _charBufferCount = 0;
-
- if (count > 0)
+ finally
{
- _stream.Write(_byteBuffer, 0, count);
+ _bytePool.Return(_byteBuffer);
+ _charPool.Return(_charBuffer);
}
}
- // Note: our FlushInternalAsync method does NOT flush the underlying stream. This would result in
- // chunking.
- private async Task FlushInternalAsync(bool flushEncoder)
+ base.Dispose(disposing);
+ }
+
+ /// <inheritdoc/>
+ public override async ValueTask DisposeAsync()
+ {
+ if (!_disposed)
{
- if (_charBufferCount == 0)
+ _disposed = true;
+ try
{
- return;
+ await FlushInternalAsync(flushEncoder: true);
}
-
- var count = _encoder.GetBytes(
- _charBuffer,
- 0,
- _charBufferCount,
- _byteBuffer,
- 0,
- flush: flushEncoder);
-
- _charBufferCount = 0;
-
- if (count > 0)
+ finally
{
- await _stream.WriteAsync(_byteBuffer.AsMemory(0, count));
+ _bytePool.Return(_byteBuffer);
+ _charPool.Return(_charBuffer);
}
}
- private void CopyToCharBuffer(string value)
+ await base.DisposeAsync();
+ }
+
+ // Note: our FlushInternal method does NOT flush the underlying stream. This would result in
+ // chunking.
+ private void FlushInternal(bool flushEncoder)
+ {
+ if (_charBufferCount == 0)
{
- Debug.Assert(_charBufferSize - _charBufferCount >= value.Length);
+ return;
+ }
+
+ var count = _encoder.GetBytes(
+ _charBuffer,
+ 0,
+ _charBufferCount,
+ _byteBuffer,
+ 0,
+ flush: flushEncoder);
- value.CopyTo(
- sourceIndex: 0,
- destination: _charBuffer,
- destinationIndex: _charBufferCount,
- count: value.Length);
+ _charBufferCount = 0;
- _charBufferCount += value.Length;
+ if (count > 0)
+ {
+ _stream.Write(_byteBuffer, 0, count);
}
+ }
- private void CopyToCharBuffer(string value, ref int index, ref int 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)
{
- var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
+ return;
+ }
- value.CopyTo(
- sourceIndex: index,
- destination: _charBuffer,
- destinationIndex: _charBufferCount,
- count: remaining);
+ var count = _encoder.GetBytes(
+ _charBuffer,
+ 0,
+ _charBufferCount,
+ _byteBuffer,
+ 0,
+ flush: flushEncoder);
- _charBufferCount += remaining;
- index += remaining;
- count -= remaining;
- }
+ _charBufferCount = 0;
- private void CopyToCharBuffer(char[] values, ref int index, ref int count)
+ if (count > 0)
{
- var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
+ await _stream.WriteAsync(_byteBuffer.AsMemory(0, count));
+ }
+ }
- Buffer.BlockCopy(
- src: values,
- srcOffset: index * sizeof(char),
- dst: _charBuffer,
- dstOffset: _charBufferCount * sizeof(char),
- count: remaining * sizeof(char));
+ private void CopyToCharBuffer(string value)
+ {
+ Debug.Assert(_charBufferSize - _charBufferCount >= value.Length);
- _charBufferCount += remaining;
- index += remaining;
- count -= remaining;
- }
+ value.CopyTo(
+ sourceIndex: 0,
+ destination: _charBuffer,
+ destinationIndex: _charBufferCount,
+ count: value.Length);
- private int CopyToCharBuffer(ReadOnlySpan<char> value)
- {
- var remaining = Math.Min(_charBufferSize - _charBufferCount, value.Length);
+ _charBufferCount += value.Length;
+ }
- var source = value.Slice(0, remaining);
- var destination = new Span<char>(_charBuffer, _charBufferCount, remaining);
- source.CopyTo(destination);
+ private void CopyToCharBuffer(string value, ref int index, ref int count)
+ {
+ var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
- _charBufferCount += remaining;
+ value.CopyTo(
+ sourceIndex: index,
+ destination: _charBuffer,
+ destinationIndex: _charBufferCount,
+ count: remaining);
- return remaining;
- }
+ _charBufferCount += remaining;
+ index += remaining;
+ count -= remaining;
+ }
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static Task GetObjectDisposedTask()
- {
- return Task.FromException(new ObjectDisposedException(nameof(HttpResponseStreamWriter)));
- }
+ 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;
+ }
+
+ private int CopyToCharBuffer(ReadOnlySpan<char> value)
+ {
+ var remaining = Math.Min(_charBufferSize - _charBufferCount, value.Length);
+
+ var source = value.Slice(0, remaining);
+ var destination = new Span<char>(_charBuffer, _charBufferCount, remaining);
+ source.CopyTo(destination);
+
+ _charBufferCount += remaining;
+
+ return remaining;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static Task GetObjectDisposedTask()
+ {
+ return Task.FromException(new ObjectDisposedException(nameof(HttpResponseStreamWriter)));
}
}
diff --git a/src/Http/WebUtilities/src/KeyValueAccumulator.cs b/src/Http/WebUtilities/src/KeyValueAccumulator.cs
index 280e606616..3bc603c7ea 100644
--- a/src/Http/WebUtilities/src/KeyValueAccumulator.cs
+++ b/src/Http/WebUtilities/src/KeyValueAccumulator.cs
@@ -5,106 +5,105 @@ using System;
using System.Collections.Generic;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// This API supports infrastructure and is not intended to be used
+/// directly from your code. This API may change or be removed in future releases.
+/// </summary>
+public struct KeyValueAccumulator
{
+ private Dictionary<string, StringValues> _accumulator;
+ private Dictionary<string, List<string>> _expandingAccumulator;
+
/// <summary>
/// This API supports infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
- public struct KeyValueAccumulator
+ public void Append(string key, string value)
{
- private Dictionary<string, StringValues> _accumulator;
- private Dictionary<string, List<string>> _expandingAccumulator;
+ if (_accumulator == null)
+ {
+ _accumulator = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
+ }
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public void Append(string key, string value)
+ StringValues values;
+ if (_accumulator.TryGetValue(key, out values))
{
- if (_accumulator == null)
+ if (values.Count == 0)
{
- _accumulator = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
+ // Marker entry for this key to indicate entry already in expanding list dictionary
+ _expandingAccumulator[key].Add(value);
}
-
- StringValues values;
- if (_accumulator.TryGetValue(key, out values))
+ else if (values.Count == 1)
{
- 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)
{
- // Second value for this key
- _accumulator[key] = new string[] { values[0]!, value };
+ _expandingAccumulator = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
}
- 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();
+ // 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);
+ 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);
+ _expandingAccumulator[key] = list;
}
-
- ValueCount++;
+ }
+ else
+ {
+ // First value for this key
+ _accumulator[key] = new StringValues(value);
}
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public bool HasValues => ValueCount > 0;
+ ValueCount++;
+ }
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public int KeyCount => _accumulator?.Count ?? 0;
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public bool HasValues => ValueCount > 0;
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public int ValueCount { get; private set; }
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public int KeyCount => _accumulator?.Count ?? 0;
- /// <summary>
- /// This API supports infrastructure and is not intended to be used
- /// directly from your code. This API may change or be removed in future releases.
- /// </summary>
- public Dictionary<string, StringValues> GetResults()
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public int ValueCount { get; private set; }
+
+ /// <summary>
+ /// This API supports infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ /// </summary>
+ public Dictionary<string, StringValues> GetResults()
+ {
+ if (_expandingAccumulator != null)
{
- if (_expandingAccumulator != null)
+ // Coalesce count 3+ multi-value entries into _accumulator dictionary
+ foreach (var entry in _expandingAccumulator)
{
- // Coalesce count 3+ multi-value entries into _accumulator dictionary
- foreach (var entry in _expandingAccumulator)
- {
- _accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
- }
+ _accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
}
-
- return _accumulator ?? new Dictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
}
+
+ return _accumulator ?? new Dictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
}
}
diff --git a/src/Http/WebUtilities/src/MultipartBoundary.cs b/src/Http/WebUtilities/src/MultipartBoundary.cs
index 2b9346d2a5..a5abf1a15c 100644
--- a/src/Http/WebUtilities/src/MultipartBoundary.cs
+++ b/src/Http/WebUtilities/src/MultipartBoundary.cs
@@ -4,69 +4,68 @@
using System;
using System.Text;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+internal class MultipartBoundary
{
- internal class MultipartBoundary
- {
- private readonly int[] _skipTable = new int[256];
- private readonly string _boundary;
- private bool _expectLeadingCrlf;
+ private readonly int[] _skipTable = new int[256];
+ private readonly string _boundary;
+ private bool _expectLeadingCrlf;
- public MultipartBoundary(string boundary, bool expectLeadingCrlf = true)
+ public MultipartBoundary(string boundary, bool expectLeadingCrlf = true)
+ {
+ if (boundary == null)
{
- if (boundary == null)
- {
- throw new ArgumentNullException(nameof(boundary));
- }
-
- _boundary = boundary;
- _expectLeadingCrlf = expectLeadingCrlf;
- Initialize(_boundary, _expectLeadingCrlf);
+ throw new ArgumentNullException(nameof(boundary));
}
- 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.
+ _boundary = boundary;
+ _expectLeadingCrlf = expectLeadingCrlf;
+ Initialize(_boundary, _expectLeadingCrlf);
+ }
- 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);
- }
+ 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.
- public int GetSkipValue(byte input)
+ var length = BoundaryBytes.Length;
+ for (var i = 0; i < _skipTable.Length; ++i)
+ {
+ _skipTable[i] = length;
+ }
+ for (var i = 0; i < length; ++i)
{
- return _skipTable[input];
+ _skipTable[BoundaryBytes[i]] = Math.Max(1, length - 1 - i);
}
+ }
+
+ public int GetSkipValue(byte input)
+ {
+ return _skipTable[input];
+ }
- public bool ExpectLeadingCrlf
+ public bool ExpectLeadingCrlf
+ {
+ get { return _expectLeadingCrlf; }
+ set
{
- get { return _expectLeadingCrlf; }
- set
+ if (value != _expectLeadingCrlf)
{
- if (value != _expectLeadingCrlf)
- {
- _expectLeadingCrlf = value;
- Initialize(_boundary, _expectLeadingCrlf);
- }
+ _expectLeadingCrlf = value;
+ Initialize(_boundary, _expectLeadingCrlf);
}
}
+ }
- public byte[] BoundaryBytes { get; private set; } = default!; // This gets initialized as part of Initialize called from in the ctor.
+ public byte[] BoundaryBytes { get; private set; } = default!; // This gets initialized as part of Initialize called from in the ctor.
- public int FinalBoundaryLength { 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
index b2eb2efdbb..4de7165dc5 100644
--- a/src/Http/WebUtilities/src/MultipartReader.cs
+++ b/src/Http/WebUtilities/src/MultipartReader.cs
@@ -9,139 +9,138 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+// https://www.ietf.org/rfc/rfc2046.txt
+/// <summary>
+/// Reads multipart form content from the specified <see cref="Stream"/>.
+/// </summary>
+public class MultipartReader
{
- // https://www.ietf.org/rfc/rfc2046.txt
/// <summary>
- /// Reads multipart form content from the specified <see cref="Stream"/>.
+ /// Gets the default value for <see cref="HeadersCountLimit"/>.
+ /// Defaults to 16‬.
+ /// </summary>
+ public const int DefaultHeadersCountLimit = 16;
+
+ /// <summary>
+ /// Gets the default value for <see cref="HeadersLengthLimit"/>.
+ /// Defaults to 16,384‬ bytes‬, which is approximately 16KB.
+ /// </summary>
+ public const int DefaultHeadersLengthLimit = 1024 * 16;
+ private const int DefaultBufferSize = 1024 * 4;
+
+ private readonly BufferedReadStream _stream;
+ private readonly MultipartBoundary _boundary;
+ private MultipartReaderStream _currentStream;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="MultipartReader"/>.
+ /// </summary>
+ /// <param name="boundary">The multipart boundary.</param>
+ /// <param name="stream">The <see cref="Stream"/> containing multipart data.</param>
+ public MultipartReader(string boundary, Stream stream)
+ : this(boundary, stream, DefaultBufferSize)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="MultipartReader"/>.
/// </summary>
- public class MultipartReader
+ /// <param name="boundary">The multipart boundary.</param>
+ /// <param name="stream">The <see cref="Stream"/> containing multipart data.</param>
+ /// <param name="bufferSize">The minimum buffer size to use.</param>
+ public MultipartReader(string boundary, Stream stream, int bufferSize)
{
- /// <summary>
- /// Gets the default value for <see cref="HeadersCountLimit"/>.
- /// Defaults to 16‬.
- /// </summary>
- public const int DefaultHeadersCountLimit = 16;
-
- /// <summary>
- /// Gets the default value for <see cref="HeadersLengthLimit"/>.
- /// Defaults to 16,384‬ bytes‬, which is approximately 16KB.
- /// </summary>
- public const int DefaultHeadersLengthLimit = 1024 * 16;
- private const int DefaultBufferSize = 1024 * 4;
-
- private readonly BufferedReadStream _stream;
- private readonly MultipartBoundary _boundary;
- private MultipartReaderStream _currentStream;
-
- /// <summary>
- /// Initializes a new instance of <see cref="MultipartReader"/>.
- /// </summary>
- /// <param name="boundary">The multipart boundary.</param>
- /// <param name="stream">The <see cref="Stream"/> containing multipart data.</param>
- public MultipartReader(string boundary, Stream stream)
- : this(boundary, stream, DefaultBufferSize)
+ if (boundary == null)
{
+ throw new ArgumentNullException(nameof(boundary));
}
- /// <summary>
- /// Initializes a new instance of <see cref="MultipartReader"/>.
- /// </summary>
- /// <param name="boundary">The multipart boundary.</param>
- /// <param name="stream">The <see cref="Stream"/> containing multipart data.</param>
- /// <param name="bufferSize">The minimum buffer size to use.</param>
- public MultipartReader(string boundary, Stream stream, int bufferSize)
+ if (stream == null)
{
- if (boundary == null)
- {
- throw new ArgumentNullException(nameof(boundary));
- }
+ throw new ArgumentNullException(nameof(stream));
+ }
- 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 };
+ }
- 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; }
+
+ /// <summary>
+ /// Reads the next <see cref="MultipartSection"/>.
+ /// </summary>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.
+ /// The default value is <see cref="CancellationToken.None"/>.</param>
+ /// <returns></returns>
+ 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 };
+ }
- /// <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; }
-
- /// <summary>
- /// Reads the next <see cref="MultipartSection"/>.
- /// </summary>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.
- /// The default value is <see cref="CancellationToken.None"/>.</param>
- /// <returns></returns>
- public async Task<MultipartSection?> ReadNextSectionAsync(CancellationToken cancellationToken = new CancellationToken())
+ 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))
{
- // Drain the prior section.
- await _currentStream.DrainAsync(cancellationToken);
- // If we're at the end return null
- if (_currentStream.FinalBoundaryFound)
+ if (HeadersLengthLimit - totalSize < line.Length)
{
- // There may be trailer data after the last boundary.
- await _stream.DrainAsync(HeadersLengthLimit, cancellationToken);
- return null;
+ 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 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))
+ 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)
{
- 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);
+ throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
}
- return accumulator.GetResults();
+ line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken);
}
+
+ return accumulator.GetResults();
}
}
diff --git a/src/Http/WebUtilities/src/MultipartReaderStream.cs b/src/Http/WebUtilities/src/MultipartReaderStream.cs
index 7dc0379ae0..9e472d534d 100644
--- a/src/Http/WebUtilities/src/MultipartReaderStream.cs
+++ b/src/Http/WebUtilities/src/MultipartReaderStream.cs
@@ -8,329 +8,328 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+internal sealed class MultipartReaderStream : Stream
{
- internal sealed 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)
{
- 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)
+ if (stream == null)
{
+ throw new ArgumentNullException(nameof(stream));
}
- /// <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 (boundary == null)
{
- 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;
+ throw new ArgumentNullException(nameof(boundary));
}
- public bool FinalBoundaryFound { get; private set; }
+ _bytePool = bytePool;
+ _innerStream = stream;
+ _innerOffset = _innerStream.CanSeek ? _innerStream.Position : 0;
+ _boundary = boundary;
+ }
- public long? LengthLimit { get; set; }
+ public bool FinalBoundaryFound { get; private set; }
- public override bool CanRead
- {
- get { return true; }
- }
+ public long? LengthLimit { get; set; }
- public override bool CanSeek
- {
- get { return _innerStream.CanSeek; }
- }
+ public override bool CanRead
+ {
+ get { return true; }
+ }
- public override bool CanWrite
- {
- get { return false; }
- }
+ public override bool CanSeek
+ {
+ get { return _innerStream.CanSeek; }
+ }
- public override long Length
- {
- get { return _observedLength; }
- }
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
- 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 Length
+ {
+ get { return _observedLength; }
+ }
- public override long Seek(long offset, SeekOrigin origin)
+ public override long Position
+ {
+ get { return _position; }
+ set
{
- if (origin == SeekOrigin.Begin)
+ if (value < 0)
{
- Position = offset;
+ throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be positive.");
}
- else if (origin == SeekOrigin.Current)
+ if (value > _observedLength)
{
- Position = Position + offset;
+ throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be less than length.");
}
- else // if (origin == SeekOrigin.End)
+ _position = value;
+ if (_position < _observedLength)
{
- Position = Length + offset;
+ _finished = false;
}
- return Position;
}
+ }
- public override void SetLength(long value)
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
{
- throw new NotSupportedException();
+ Position = offset;
}
-
- public override void Write(byte[] buffer, int offset, int count)
+ else if (origin == SeekOrigin.Current)
{
- throw new NotSupportedException();
+ Position = Position + offset;
}
-
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ else // if (origin == SeekOrigin.End)
{
- throw new NotSupportedException();
+ 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 void Flush()
+ 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))
{
- throw new NotSupportedException();
+ _innerStream.Position = _innerOffset + _position;
}
+ }
- private void PositionInnerStream()
+ private int UpdatePosition(int read)
+ {
+ _position += read;
+ if (_observedLength < _position)
{
- if (_innerStream.CanSeek && _innerStream.Position != (_innerOffset + _position))
+ _observedLength = _position;
+ if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
{
- _innerStream.Position = _innerOffset + _position;
+ throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");
}
}
+ return read;
+ }
- private int UpdatePosition(int read)
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (_finished)
{
- _position += read;
- if (_observedLength < _position)
- {
- _observedLength = _position;
- if (LengthLimit.HasValue && _observedLength > LengthLimit.GetValueOrDefault())
- {
- throw new InvalidDataException($"Multipart body length limit {LengthLimit.GetValueOrDefault()} exceeded.");
- }
- }
- return read;
+ return 0;
}
- public override int Read(byte[] buffer, int offset, int count)
+ PositionInnerStream();
+ if (!_innerStream.EnsureBuffered(_boundary.FinalBoundaryLength))
{
- if (_finished)
- {
- return 0;
- }
+ throw new IOException("Unexpected end of Stream, the content may have already been read by another component. ");
+ }
+ var bufferedData = _innerStream.BufferedData;
- PositionInnerStream();
- if (!_innerStream.EnsureBuffered(_boundary.FinalBoundaryLength))
+ // 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)
{
- throw new IOException("Unexpected end of Stream, the content may have already been read by another component. ");
+ read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
+ return UpdatePosition(read);
}
- 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);
+ 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
+ // "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;
+ 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);
+ }
- // 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;
}
- public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ PositionInnerStream();
+ if (!await _innerStream.EnsureBufferedAsync(_boundary.FinalBoundaryLength, cancellationToken))
{
- if (_finished)
- {
- return 0;
- }
+ throw new IOException("Unexpected end of Stream, the content may have already been read by another component. ");
+ }
+ var bufferedData = _innerStream.BufferedData;
- PositionInnerStream();
- if (!await _innerStream.EnsureBufferedAsync(_boundary.FinalBoundaryLength, cancellationToken))
+ // 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)
{
- throw new IOException("Unexpected end of Stream, the content may have already been read by another component. ");
+ // Sync, it's already buffered
+ read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
+ return UpdatePosition(read);
}
- 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);
+ 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
+ // "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;
+ 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);
- // 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);
+ _finished = true;
+ return 0;
}
- // 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)
+ // 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?
{
- // clear matchCount to zero
- matchCount = 0;
+ var matchBytesLengthMinusOne = matchBytes.Length - 1;
+ var matchBytesLastByte = matchBytes[matchBytesLengthMinusOne];
+ var segmentEndMinusMatchBytesLength = segment1.Offset + segment1.Count - matchBytes.Length;
- // case 1: does segment1 fully contain matchBytes?
+ matchOffset = segment1.Offset;
+ while (matchOffset < segmentEndMinusMatchBytesLength)
{
- 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)
{
- 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);
+ 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;
+ // case 2: does segment1 end with the start of matchBytes?
+ var segmentEnd = segment1.Offset + segment1.Count;
- matchCount = 0;
- for (; matchOffset < segmentEnd; matchOffset++)
+ matchCount = 0;
+ for (; matchOffset < segmentEnd; matchOffset++)
+ {
+ var countLimit = segmentEnd - matchOffset;
+ for (matchCount = 0; matchCount < matchBytes.Length && matchCount < countLimit; matchCount++)
{
- 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)
+ if (matchBytes[matchCount] != segment1.Array![matchOffset + matchCount])
{
+ matchCount = 0;
break;
}
}
- return matchCount > 0;
+ if (matchCount > 0)
+ {
+ break;
+ }
}
+ return matchCount > 0;
+ }
- private static int CompareBuffers(byte[] buffer1, int offset1, byte[] buffer2, int offset2, int count)
+ private static int CompareBuffers(byte[] buffer1, int offset1, byte[] buffer2, int offset2, int count)
+ {
+ for (; count-- > 0; offset1++, offset2++)
{
- for (; count-- > 0; offset1++, offset2++)
+ if (buffer1[offset1] != buffer2[offset2])
{
- if (buffer1[offset1] != buffer2[offset2])
- {
- return buffer1[offset1] - buffer2[offset2];
- }
+ return buffer1[offset1] - buffer2[offset2];
}
- return 0;
}
+ return 0;
}
}
diff --git a/src/Http/WebUtilities/src/MultipartSection.cs b/src/Http/WebUtilities/src/MultipartSection.cs
index 4808075a17..6db3c40516 100644
--- a/src/Http/WebUtilities/src/MultipartSection.cs
+++ b/src/Http/WebUtilities/src/MultipartSection.cs
@@ -6,57 +6,56 @@ using System.IO;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// A multipart section read by <see cref="MultipartReader"/>.
+/// </summary>
+public class MultipartSection
{
/// <summary>
- /// A multipart section read by <see cref="MultipartReader"/>.
+ /// Gets the value of the <c>Content-Type</c> header.
/// </summary>
- public class MultipartSection
+ public string? ContentType
{
- /// <summary>
- /// Gets the value of the <c>Content-Type</c> header.
- /// </summary>
- public string? ContentType
+ get
{
- get
+ if (Headers != null && Headers.TryGetValue(HeaderNames.ContentType, out var values))
{
- if (Headers != null && Headers.TryGetValue(HeaderNames.ContentType, out var values))
- {
- return values;
- }
- return null;
+ return values;
}
+ return null;
}
+ }
- /// <summary>
- /// Gets the value of the <c>Content-Disposition</c> header.
- /// </summary>
- public string? ContentDisposition
+ /// <summary>
+ /// Gets the value of the <c>Content-Disposition</c> header.
+ /// </summary>
+ public string? ContentDisposition
+ {
+ get
{
- get
+ if (Headers != null && Headers.TryGetValue(HeaderNames.ContentDisposition, out var values))
{
- if (Headers != null && Headers.TryGetValue(HeaderNames.ContentDisposition, out var values))
- {
- return values;
- }
- return null;
+ return values;
}
+ return null;
}
+ }
- /// <summary>
- /// Gets or sets the multipart header collection.
- /// </summary>
- public Dictionary<string, StringValues>? Headers { get; set; }
+ /// <summary>
+ /// Gets or sets the multipart header collection.
+ /// </summary>
+ public Dictionary<string, StringValues>? Headers { get; set; }
- /// <summary>
- /// Gets or sets the body.
- /// </summary>
- public Stream Body { get; set; } = default!;
+ /// <summary>
+ /// Gets or sets the body.
+ /// </summary>
+ public Stream Body { get; set; } = default!;
- /// <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; }
- }
+ /// <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; }
}
diff --git a/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs b/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs
index e690160ee6..6e53f8e896 100644
--- a/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs
+++ b/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs
@@ -4,70 +4,69 @@
using System;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Various extensions for converting multipart sections
+/// </summary>
+public static class MultipartSectionConverterExtensions
{
/// <summary>
- /// Various extensions for converting multipart sections
+ /// Converts the section to a file section
/// </summary>
- public static class MultipartSectionConverterExtensions
+ /// <param name="section">The section to convert</param>
+ /// <returns>A file section</returns>
+ public static FileMultipartSection? AsFileSection(this MultipartSection section)
{
- /// <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)
{
- if (section == null)
- {
- throw new ArgumentNullException(nameof(section));
- }
-
- try
- {
- return new FileMultipartSection(section);
- }
- catch
- {
- return null;
- }
+ throw new ArgumentNullException(nameof(section));
}
- /// <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)
+ try
{
- if (section == null)
- {
- throw new ArgumentNullException(nameof(section));
- }
+ return new FileMultipartSection(section);
+ }
+ catch
+ {
+ return null;
+ }
+ }
- try
- {
- return new FormMultipartSection(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));
}
- /// <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)
+ try
{
- if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var header))
- {
- return null;
- }
+ return new FormMultipartSection(section);
+ }
+ catch
+ {
+ return null;
+ }
+ }
- return header;
+ /// <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)
+ {
+ if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var header))
+ {
+ return null;
}
+
+ return header;
}
}
diff --git a/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs b/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs
index d692a0341c..3eccad1df7 100644
--- a/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs
+++ b/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs
@@ -7,48 +7,47 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Various extension methods for dealing with the section body stream
+/// </summary>
+public static class MultipartSectionStreamExtensions
{
/// <summary>
- /// Various extension methods for dealing with the section body stream
+ /// Reads the body of the section as a string
/// </summary>
- public static class MultipartSectionStreamExtensions
+ /// <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)
{
- /// <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)
{
- if (section == null)
- {
- throw new ArgumentNullException(nameof(section));
- }
+ throw new ArgumentNullException(nameof(section));
+ }
- if (section.Body is null)
- {
- throw new ArgumentException("Multipart section must have a body to be read.", nameof(section));
- }
+ if (section.Body is null)
+ {
+ throw new ArgumentException("Multipart section must have a body to be read.", nameof(section));
+ }
- MediaTypeHeaderValue.TryParse(section.ContentType, out var sectionMediaType);
+ MediaTypeHeaderValue.TryParse(section.ContentType, out var sectionMediaType);
- var streamEncoding = sectionMediaType?.Encoding;
- // https://docs.microsoft.com/en-us/dotnet/core/compatibility/syslib-warnings/syslib0001
- if (streamEncoding == null || streamEncoding.CodePage == 65000)
- {
- streamEncoding = Encoding.UTF8;
- }
+ var streamEncoding = sectionMediaType?.Encoding;
+ // https://docs.microsoft.com/en-us/dotnet/core/compatibility/syslib-warnings/syslib0001
+ if (streamEncoding == null || streamEncoding.CodePage == 65000)
+ {
+ streamEncoding = Encoding.UTF8;
+ }
- using (var reader = new StreamReader(
- section.Body,
- streamEncoding,
- detectEncodingFromByteOrderMarks: true,
- bufferSize: 1024,
- leaveOpen: true))
- {
- return await reader.ReadToEndAsync();
- }
+ 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/PagedByteBuffer.cs b/src/Http/WebUtilities/src/PagedByteBuffer.cs
index 8410be771a..0a22364e34 100644
--- a/src/Http/WebUtilities/src/PagedByteBuffer.cs
+++ b/src/Http/WebUtilities/src/PagedByteBuffer.cs
@@ -9,142 +9,141 @@ using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+internal sealed class PagedByteBuffer : IDisposable
{
- internal sealed class PagedByteBuffer : IDisposable
- {
- internal const int PageSize = 1024;
- private readonly ArrayPool<byte> _arrayPool;
- private byte[]? _currentPage;
- private int _currentPageIndex;
+ internal const int PageSize = 1024;
+ private readonly ArrayPool<byte> _arrayPool;
+ private byte[]? _currentPage;
+ private int _currentPageIndex;
- public PagedByteBuffer(ArrayPool<byte> arrayPool)
- {
- _arrayPool = arrayPool;
- Pages = new List<byte[]>();
- }
+ public PagedByteBuffer(ArrayPool<byte> arrayPool)
+ {
+ _arrayPool = arrayPool;
+ Pages = new List<byte[]>();
+ }
- public int Length { get; private set; }
+ public int Length { get; private set; }
- internal bool Disposed { get; private set; }
+ internal bool Disposed { get; private set; }
- internal List<byte[]> Pages { get; }
+ internal List<byte[]> Pages { get; }
- private byte[] CurrentPage
+ private byte[] CurrentPage
+ {
+ get
{
- get
+ if (_currentPage == null || _currentPageIndex == _currentPage.Length)
{
- if (_currentPage == null || _currentPageIndex == _currentPage.Length)
- {
- _currentPage = _arrayPool.Rent(PageSize);
- Pages.Add(_currentPage);
- _currentPageIndex = 0;
- }
-
- return _currentPage;
+ _currentPage = _arrayPool.Rent(PageSize);
+ Pages.Add(_currentPage);
+ _currentPageIndex = 0;
}
+
+ return _currentPage;
}
+ }
- public void Add(byte[] buffer, int offset, int count)
- => Add(buffer.AsMemory(offset, count));
+ public void Add(byte[] buffer, int offset, int count)
+ => Add(buffer.AsMemory(offset, count));
- public void Add(ReadOnlyMemory<byte> memory)
- {
- ThrowIfDisposed();
+ public void Add(ReadOnlyMemory<byte> memory)
+ {
+ ThrowIfDisposed();
- while (!memory.IsEmpty)
- {
- var currentPage = CurrentPage;
- var copyLength = Math.Min(memory.Length, currentPage.Length - _currentPageIndex);
+ while (!memory.IsEmpty)
+ {
+ var currentPage = CurrentPage;
+ var copyLength = Math.Min(memory.Length, currentPage.Length - _currentPageIndex);
- memory.Slice(0, copyLength).CopyTo(currentPage.AsMemory(_currentPageIndex, copyLength));
+ memory.Slice(0, copyLength).CopyTo(currentPage.AsMemory(_currentPageIndex, copyLength));
- Length += copyLength;
- _currentPageIndex += copyLength;
+ Length += copyLength;
+ _currentPageIndex += copyLength;
- memory = memory.Slice(copyLength);
- }
+ memory = memory.Slice(copyLength);
}
+ }
+
+ public void MoveTo(Stream stream)
+ {
+ ThrowIfDisposed();
- public void MoveTo(Stream stream)
+ for (var i = 0; i < Pages.Count; i++)
{
- ThrowIfDisposed();
+ var page = Pages[i];
+ var length = (i == Pages.Count - 1) ?
+ _currentPageIndex :
+ page.Length;
- for (var i = 0; i < Pages.Count; i++)
- {
- var page = Pages[i];
- var length = (i == Pages.Count - 1) ?
- _currentPageIndex :
- page.Length;
+ stream.Write(page, 0, length);
+ }
- stream.Write(page, 0, length);
- }
+ ClearBuffers();
+ }
- ClearBuffers();
- }
+ public async Task MoveToAsync(PipeWriter writer, CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
- public async Task MoveToAsync(PipeWriter writer, CancellationToken cancellationToken)
+ for (var i = 0; i < Pages.Count; i++)
{
- ThrowIfDisposed();
+ var page = Pages[i];
+ var length = (i == Pages.Count - 1) ?
+ _currentPageIndex :
+ page.Length;
- for (var i = 0; i < Pages.Count; i++)
- {
- var page = Pages[i];
- var length = (i == Pages.Count - 1) ?
- _currentPageIndex :
- page.Length;
+ await writer.WriteAsync(page.AsMemory(0, length), cancellationToken);
+ }
- await writer.WriteAsync(page.AsMemory(0, length), cancellationToken);
- }
+ ClearBuffers();
+ }
- ClearBuffers();
- }
+ public async Task MoveToAsync(Stream stream, CancellationToken cancellationToken)
+ {
+ ThrowIfDisposed();
- public async Task MoveToAsync(Stream stream, CancellationToken cancellationToken)
+ for (var i = 0; i < Pages.Count; i++)
{
- ThrowIfDisposed();
+ var page = Pages[i];
+ var length = (i == Pages.Count - 1) ?
+ _currentPageIndex :
+ page.Length;
- for (var i = 0; i < Pages.Count; i++)
- {
- var page = Pages[i];
- var length = (i == Pages.Count - 1) ?
- _currentPageIndex :
- page.Length;
+ await stream.WriteAsync(page.AsMemory(0, length), cancellationToken);
+ }
- await stream.WriteAsync(page.AsMemory(0, length), cancellationToken);
- }
+ ClearBuffers();
+ }
+ public void Dispose()
+ {
+ if (!Disposed)
+ {
+ Disposed = true;
ClearBuffers();
}
+ }
- public void Dispose()
+ private void ClearBuffers()
+ {
+ for (var i = 0; i < Pages.Count; i++)
{
- if (!Disposed)
- {
- Disposed = true;
- ClearBuffers();
- }
+ _arrayPool.Return(Pages[i]);
}
- private void ClearBuffers()
- {
- for (var i = 0; i < Pages.Count; i++)
- {
- _arrayPool.Return(Pages[i]);
- }
-
- Pages.Clear();
- _currentPage = null;
- Length = 0;
- _currentPageIndex = 0;
- }
+ Pages.Clear();
+ _currentPage = null;
+ Length = 0;
+ _currentPageIndex = 0;
+ }
- private void ThrowIfDisposed()
+ private void ThrowIfDisposed()
+ {
+ if (Disposed)
{
- if (Disposed)
- {
- throw new ObjectDisposedException(nameof(PagedByteBuffer));
- }
+ throw new ObjectDisposedException(nameof(PagedByteBuffer));
}
}
}
diff --git a/src/Http/WebUtilities/src/QueryHelpers.cs b/src/Http/WebUtilities/src/QueryHelpers.cs
index 48ecd616bf..133cbdcb73 100644
--- a/src/Http/WebUtilities/src/QueryHelpers.cs
+++ b/src/Http/WebUtilities/src/QueryHelpers.cs
@@ -9,183 +9,182 @@ using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Provides methods for parsing and manipulating query strings.
+/// </summary>
+public static class QueryHelpers
{
/// <summary>
- /// Provides methods for parsing and manipulating query strings.
+ /// Append the given query key and value to the URI.
/// </summary>
- public static class QueryHelpers
+ /// <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>
+ /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception>
+ public static string AddQueryString(string uri, string name, string value)
{
- /// <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>
- /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentNullException"><paramref name="value"/> is <c>null</c>.</exception>
- public static string AddQueryString(string uri, string name, string value)
+ if (uri == null)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ throw new ArgumentNullException(nameof(uri));
+ }
- if (name == null)
- {
- throw new ArgumentNullException(nameof(name));
- }
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return AddQueryString(
+ uri, new[] { new KeyValuePair<string, string?>(name, 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 dictionary of query keys and values to append.</param>
+ /// <returns>The combined result.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
+ public static string AddQueryString(string uri, IDictionary<string, string?> queryString)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
}
- /// <summary>
- /// Append the given query keys and values to the URI.
- /// </summary>
- /// <param name="uri">The base URI.</param>
- /// <param name="queryString">A dictionary of query keys and values to append.</param>
- /// <returns>The combined result.</returns>
- /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
- public static string AddQueryString(string uri, IDictionary<string, string?> queryString)
+ if (queryString == null)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ throw new ArgumentNullException(nameof(queryString));
+ }
- if (queryString == null)
- {
- throw new ArgumentNullException(nameof(queryString));
- }
+ return AddQueryString(uri, (IEnumerable<KeyValuePair<string, string?>>)queryString);
+ }
- return AddQueryString(uri, (IEnumerable<KeyValuePair<string, string?>>)queryString);
+ /// <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 query names and values to append.</param>
+ /// <returns>The combined result.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
+ public static string AddQueryString(string uri, IEnumerable<KeyValuePair<string, StringValues>> queryString)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
}
- /// <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 query names and values to append.</param>
- /// <returns>The combined result.</returns>
- /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
- public static string AddQueryString(string uri, IEnumerable<KeyValuePair<string, StringValues>> queryString)
+ if (queryString == null)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
+ throw new ArgumentNullException(nameof(queryString));
+ }
- if (queryString == null)
- {
- throw new ArgumentNullException(nameof(queryString));
- }
+ return AddQueryString(uri, queryString.SelectMany(kvp => kvp.Value, (kvp, v) => KeyValuePair.Create<string, string?>(kvp.Key, v)));
+ }
- return AddQueryString(uri, queryString.SelectMany(kvp => kvp.Value, (kvp, v) => KeyValuePair.Create<string, string?>(kvp.Key, v)));
+ /// <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>
+ /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
+ public static string AddQueryString(
+ string uri,
+ IEnumerable<KeyValuePair<string, string?>> queryString)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException(nameof(uri));
}
- /// <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>
- /// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
- public static string AddQueryString(
- string uri,
- IEnumerable<KeyValuePair<string, string?>> queryString)
+ if (queryString == null)
{
- if (uri == null)
- {
- throw new ArgumentNullException(nameof(uri));
- }
-
- if (queryString == null)
- {
- throw new ArgumentNullException(nameof(queryString));
- }
+ 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 occurrence.
- if (anchorIndex != -1)
- {
- anchorText = uri.Substring(anchorIndex);
- uriToBeAppended = uri.Substring(0, anchorIndex);
- }
+ var anchorIndex = uri.IndexOf('#');
+ var uriToBeAppended = uri;
+ var anchorText = "";
+ // If there is an anchor, then the query string must be inserted before its first occurrence.
+ if (anchorIndex != -1)
+ {
+ anchorText = uri.Substring(anchorIndex);
+ uriToBeAppended = uri.Substring(0, anchorIndex);
+ }
- var queryIndex = uriToBeAppended.IndexOf('?');
- var hasQuery = queryIndex != -1;
+ var queryIndex = uriToBeAppended.IndexOf('?');
+ var hasQuery = queryIndex != -1;
- var sb = new StringBuilder();
- sb.Append(uriToBeAppended);
- foreach (var parameter in queryString)
+ var sb = new StringBuilder();
+ sb.Append(uriToBeAppended);
+ foreach (var parameter in queryString)
+ {
+ if (parameter.Value == null)
{
- if (parameter.Value == null)
- {
- continue;
- }
-
- sb.Append(hasQuery ? '&' : '?');
- sb.Append(UrlEncoder.Default.Encode(parameter.Key));
- sb.Append('=');
- sb.Append(UrlEncoder.Default.Encode(parameter.Value));
- hasQuery = true;
+ continue;
}
- sb.Append(anchorText);
- return sb.ToString();
+ sb.Append(hasQuery ? '&' : '?');
+ sb.Append(UrlEncoder.Default.Encode(parameter.Key));
+ sb.Append('=');
+ sb.Append(UrlEncoder.Default.Encode(parameter.Value));
+ hasQuery = true;
}
- /// <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);
+ sb.Append(anchorText);
+ return sb.ToString();
+ }
- if (result == null)
- {
- return new Dictionary<string, StringValues>();
- }
+ /// <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);
- return result;
+ if (result == null)
+ {
+ return new Dictionary<string, StringValues>();
}
- /// <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();
- var enumerable = new QueryStringEnumerable(queryString);
+ return result;
+ }
- foreach (var pair in enumerable)
- {
- accumulator.Append(pair.DecodeName().ToString(), pair.DecodeValue().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, null if there are no entries.</returns>
+ public static Dictionary<string, StringValues>? ParseNullableQuery(string? queryString)
+ {
+ var accumulator = new KeyValueAccumulator();
+ var enumerable = new QueryStringEnumerable(queryString);
- if (!accumulator.HasValues)
- {
- return null;
- }
+ foreach (var pair in enumerable)
+ {
+ accumulator.Append(pair.DecodeName().ToString(), pair.DecodeValue().ToString());
+ }
- return accumulator.GetResults();
+ if (!accumulator.HasValues)
+ {
+ return null;
}
+
+ return accumulator.GetResults();
}
}
diff --git a/src/Http/WebUtilities/src/ReasonPhrases.cs b/src/Http/WebUtilities/src/ReasonPhrases.cs
index 08178d3ba4..5f0f5161e4 100644
--- a/src/Http/WebUtilities/src/ReasonPhrases.cs
+++ b/src/Http/WebUtilities/src/ReasonPhrases.cs
@@ -3,93 +3,92 @@
using System.Collections.Generic;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// Provides access to HTTP status code reason phrases as listed in
+/// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml.
+/// </summary>
+public static class ReasonPhrases
{
- /// <summary>
- /// Provides access to HTTP status code reason phrases as listed in
- /// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml.
- /// </summary>
- public static class ReasonPhrases
+ // Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ private static readonly Dictionary<int, string> Phrases = new()
{
- // Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
- private static readonly Dictionary<int, string> Phrases = new()
- {
- { 100, "Continue" },
- { 101, "Switching Protocols" },
- { 102, "Processing" },
+ { 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" },
+ { 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" },
+ { 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" },
+ { 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" },
- };
+ { 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" },
+ };
- /// <summary>
- /// Gets the reason phrase for the specified status code.
- /// </summary>
- /// <param name="statusCode">The status code.</param>
- /// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
- public static string GetReasonPhrase(int statusCode)
- {
- return Phrases.TryGetValue(statusCode, out var phrase) ? phrase : string.Empty;
- }
+ /// <summary>
+ /// Gets the reason phrase for the specified status code.
+ /// </summary>
+ /// <param name="statusCode">The status code.</param>
+ /// <returns>The reason phrase, or <see cref="string.Empty"/> if the status code is unknown.</returns>
+ public static string GetReasonPhrase(int statusCode)
+ {
+ return Phrases.TryGetValue(statusCode, out var phrase) ? phrase : string.Empty;
}
}
diff --git a/src/Http/WebUtilities/src/StreamHelperExtensions.cs b/src/Http/WebUtilities/src/StreamHelperExtensions.cs
index 8eb8c44766..9d7679e1aa 100644
--- a/src/Http/WebUtilities/src/StreamHelperExtensions.cs
+++ b/src/Http/WebUtilities/src/StreamHelperExtensions.cs
@@ -7,79 +7,78 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+/// <summary>
+/// HTTP extension methods for <see cref="Stream"/>.
+/// </summary>
+public static class StreamHelperExtensions
{
+ private const int _maxReadBufferSize = 1024 * 4;
+
/// <summary>
- /// HTTP extension methods for <see cref="Stream"/>.
+ /// Reads the specified <paramref name="stream"/> to the end.
+ /// <para>
+ /// This API is effective when used in conjunction with buffering. It allows
+ /// a buffered request stream to be synchronously read after it has been completely drained.
+ /// </para>
/// </summary>
- public static class StreamHelperExtensions
+ /// <param name="stream">The <see cref="Stream"/> to completely read.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ public static Task DrainAsync(this Stream stream, CancellationToken cancellationToken)
{
- private const int _maxReadBufferSize = 1024 * 4;
-
- /// <summary>
- /// Reads the specified <paramref name="stream"/> to the end.
- /// <para>
- /// This API is effective when used in conjunction with buffering. It allows
- /// a buffered request stream to be synchronously read after it has been completely drained.
- /// </para>
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to completely read.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- public static Task DrainAsync(this Stream stream, CancellationToken cancellationToken)
- {
- return stream.DrainAsync(ArrayPool<byte>.Shared, null, cancellationToken);
- }
+ return stream.DrainAsync(ArrayPool<byte>.Shared, null, cancellationToken);
+ }
- /// <summary>
- /// Reads the specified <paramref name="stream"/> to the end.
- /// <para>
- /// This API is effective when used in conjunction with buffering. It allows
- /// a buffered request stream to be synchronously read after it has been completely drained.
- /// </para>
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to completely read.</param>
- /// <param name="limit">The maximum number of bytes to read. Throws if the <see cref="Stream"/> is larger than this limit.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- public static Task DrainAsync(this Stream stream, long? limit, CancellationToken cancellationToken)
- {
- return stream.DrainAsync(ArrayPool<byte>.Shared, limit, cancellationToken);
- }
+ /// <summary>
+ /// Reads the specified <paramref name="stream"/> to the end.
+ /// <para>
+ /// This API is effective when used in conjunction with buffering. It allows
+ /// a buffered request stream to be synchronously read after it has been completely drained.
+ /// </para>
+ /// </summary>
+ /// <param name="stream">The <see cref="Stream"/> to completely read.</param>
+ /// <param name="limit">The maximum number of bytes to read. Throws if the <see cref="Stream"/> is larger than this limit.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ public static Task DrainAsync(this Stream stream, long? limit, CancellationToken cancellationToken)
+ {
+ return stream.DrainAsync(ArrayPool<byte>.Shared, limit, cancellationToken);
+ }
- /// <summary>
- /// Reads the specified <paramref name="stream"/> to the end.
- /// <para>
- /// This API is effective when used in conjunction with buffering. It allows
- /// a buffered request stream to be synchronously read after it has been completely drained.
- /// </para>
- /// </summary>
- /// <param name="stream">The <see cref="Stream"/> to completely read.</param>
- /// <param name="bytePool">The byte array pool to use.</param>
- /// <param name="limit">The maximum number of bytes to read. Throws if the <see cref="Stream"/> is larger than this limit.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- public static async Task DrainAsync(this Stream stream, ArrayPool<byte> bytePool, long? limit, CancellationToken cancellationToken)
+ /// <summary>
+ /// Reads the specified <paramref name="stream"/> to the end.
+ /// <para>
+ /// This API is effective when used in conjunction with buffering. It allows
+ /// a buffered request stream to be synchronously read after it has been completely drained.
+ /// </para>
+ /// </summary>
+ /// <param name="stream">The <see cref="Stream"/> to completely read.</param>
+ /// <param name="bytePool">The byte array pool to use.</param>
+ /// <param name="limit">The maximum number of bytes to read. Throws if the <see cref="Stream"/> is larger than this limit.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ 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
{
- cancellationToken.ThrowIfCancellationRequested();
- var buffer = bytePool.Rent(_maxReadBufferSize);
- long total = 0;
- try
+ var read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken);
+ while (read > 0)
{
- var read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken);
- while (read > 0)
+ // Not all streams support cancellation directly.
+ cancellationToken.ThrowIfCancellationRequested();
+ if (limit.HasValue && limit.GetValueOrDefault() - total < read)
{
- // Not all streams support cancellation directly.
- cancellationToken.ThrowIfCancellationRequested();
- if (limit.HasValue && limit.GetValueOrDefault() - total < read)
- {
- throw new InvalidDataException($"The stream exceeded the data limit {limit.GetValueOrDefault()}.");
- }
- total += read;
- read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken);
+ throw new InvalidDataException($"The stream exceeded the data limit {limit.GetValueOrDefault()}.");
}
+ total += read;
+ read = await stream.ReadAsync(buffer.AsMemory(), cancellationToken);
}
- finally
- {
- bytePool.Return(buffer);
- }
+ }
+ finally
+ {
+ bytePool.Return(buffer);
}
}
}
diff --git a/src/Http/WebUtilities/test/AspNetCoreTempDirectoryTests.cs b/src/Http/WebUtilities/test/AspNetCoreTempDirectoryTests.cs
index f4ec699568..9cdff4273e 100644
--- a/src/Http/WebUtilities/test/AspNetCoreTempDirectoryTests.cs
+++ b/src/Http/WebUtilities/test/AspNetCoreTempDirectoryTests.cs
@@ -4,16 +4,15 @@
using System.IO;
using Xunit;
-namespace Microsoft.AspNetCore.Internal
+namespace Microsoft.AspNetCore.Internal;
+
+public class AspNetCoreTempDirectoryTests
{
- public class AspNetCoreTempDirectoryTests
+ [Fact]
+ public void GetTempDirectory_Returns_Valid_Location()
{
- [Fact]
- public void GetTempDirectory_Returns_Valid_Location()
- {
- var tempDirectory = AspNetCoreTempDirectory.TempDirectory;
- Assert.NotNull(tempDirectory);
- Assert.True(Directory.Exists(tempDirectory));
- }
+ var tempDirectory = AspNetCoreTempDirectory.TempDirectory;
+ Assert.NotNull(tempDirectory);
+ Assert.True(Directory.Exists(tempDirectory));
}
}
diff --git a/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs b/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs
index 7d6c422292..c79bc50585 100644
--- a/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs
+++ b/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs
@@ -9,508 +9,507 @@ using System.Threading.Tasks;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class FileBufferingReadStreamTests
{
- public class FileBufferingReadStreamTests
+ private Stream MakeStream(int size)
{
- private Stream MakeStream(int size)
- {
- // TODO: Fill with random data? Make readonly?
- return new MemoryStream(new byte[size]);
- }
+ // TODO: Fill with random data? Make readonly?
+ return new MemoryStream(new byte[size]);
+ }
- [Fact]
- public void FileBufferingReadStream_Properties_ExpectedValues()
+ [Fact]
+ public void FileBufferingReadStream_Properties_ExpectedValues()
+ {
+ var inner = MakeStream(1024 * 2);
+ using (var stream = new FileBufferingReadStream(inner, 1024, null, Directory.GetCurrentDirectory()))
{
- 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);
- }
+ 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()
+ [Fact]
+ public void FileBufferingReadStream_SyncReadUnderThreshold_DoesntCreateFile()
+ {
+ var inner = MakeStream(1024 * 2);
+ using (var stream = new FileBufferingReadStream(inner, 1024 * 3, null, Directory.GetCurrentDirectory()))
{
- 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);
- }
+ 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()
+ [Fact]
+ public void FileBufferingReadStream_SyncReadOverThreshold_CreatesFile()
+ {
+ var inner = MakeStream(1024 * 2);
+ string tempFileName;
+ using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
{
- 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));
+ 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);
}
- [Fact]
- public void FileBufferingReadStream_SyncReadWithInMemoryLimit_EnforcesLimit()
+ 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 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));
- }
+ 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()
+ [Fact]
+ public void FileBufferingReadStream_SyncReadWithOnDiskLimit_EnforcesLimit()
+ {
+ var inner = MakeStream(1024 * 2);
+ string tempFileName;
+ using (var stream = new FileBufferingReadStream(inner, 512, 1024, GetCurrentDirectory()))
{
- 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));
+ 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));
+ }
+
+ ///////////////////
- [Fact]
- public async Task FileBufferingReadStream_AsyncReadUnderThreshold_DoesntCreateFile()
+ [Fact]
+ public async Task FileBufferingReadStream_AsyncReadUnderThreshold_DoesntCreateFile()
+ {
+ var inner = MakeStream(1024 * 2);
+ using (var stream = new FileBufferingReadStream(inner, 1024 * 3, null, Directory.GetCurrentDirectory()))
{
- 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);
- }
+ 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()
+ [Fact]
+ public async Task FileBufferingReadStream_AsyncReadOverThreshold_CreatesFile()
+ {
+ var inner = MakeStream(1024 * 2);
+ string tempFileName;
+ using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
{
- 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));
+ 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);
}
- [Fact]
- public async Task FileBufferingReadStream_AsyncReadWithInMemoryLimit_EnforcesLimit()
+ 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 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));
- }
+ 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()
+ [Fact]
+ public async Task FileBufferingReadStream_AsyncReadWithOnDiskLimit_EnforcesLimit()
+ {
+ var inner = MakeStream(1024 * 2);
+ string tempFileName;
+ using (var stream = new FileBufferingReadStream(inner, 512, 1024, GetCurrentDirectory()))
{
- 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));
+ 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);
}
- [Fact]
- public void FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRentedBuffer_WhenCopyingFromMemoryStreamDuringRead()
+ Assert.False(File.Exists(tempFileName));
+ }
+
+ [Fact]
+ public void FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRentedBuffer_WhenCopyingFromMemoryStreamDuringRead()
+ {
+ var inner = MakeStream(1024 * 1024 + 25);
+ string tempFileName;
+ var arrayPool = new Mock<ArrayPool<byte>>();
+ arrayPool.Setup(p => p.Rent(It.IsAny<int>()))
+ .Returns((int m) => ArrayPool<byte>.Shared.Rent(m));
+ arrayPool.Setup(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()))
+ .Callback((byte[] bytes, bool clear) => ArrayPool<byte>.Shared.Return(bytes, clear));
+
+ using (var stream = new FileBufferingReadStream(inner, 1024 * 1024 + 1, 2 * 1024 * 1024, GetCurrentDirectory(), arrayPool.Object))
{
- var inner = MakeStream(1024 * 1024 + 25);
- string tempFileName;
- var arrayPool = new Mock<ArrayPool<byte>>();
- arrayPool.Setup(p => p.Rent(It.IsAny<int>()))
- .Returns((int m) => ArrayPool<byte>.Shared.Rent(m));
- arrayPool.Setup(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()))
- .Callback((byte[] bytes, bool clear) => ArrayPool<byte>.Shared.Return(bytes, clear));
-
- using (var stream = new FileBufferingReadStream(inner, 1024 * 1024 + 1, 2 * 1024 * 1024, GetCurrentDirectory(), arrayPool.Object))
- {
- arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Never());
-
- stream.Read(new byte[1024 * 1024]);
- Assert.False(File.Exists(stream.TempFileName), "tempFile should not be created as yet");
-
- stream.Read(new byte[4]);
- Assert.True(File.Exists(stream.TempFileName), "tempFile should be created");
- tempFileName = stream.TempFileName!;
-
- arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Once());
- arrayPool.Verify(v => v.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
- }
-
- Assert.False(File.Exists(tempFileName));
+ arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Never());
+
+ stream.Read(new byte[1024 * 1024]);
+ Assert.False(File.Exists(stream.TempFileName), "tempFile should not be created as yet");
+
+ stream.Read(new byte[4]);
+ Assert.True(File.Exists(stream.TempFileName), "tempFile should be created");
+ tempFileName = stream.TempFileName!;
+
+ arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Once());
+ arrayPool.Verify(v => v.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
}
- [Fact]
- public async Task FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRentedBuffer_WhenCopyingFromMemoryStreamDuringReadAsync()
+ Assert.False(File.Exists(tempFileName));
+ }
+
+ [Fact]
+ public async Task FileBufferingReadStream_UsingMemoryStream_RentsAndReturnsRentedBuffer_WhenCopyingFromMemoryStreamDuringReadAsync()
+ {
+ var inner = MakeStream(1024 * 1024 + 25);
+ string tempFileName;
+ var arrayPool = new Mock<ArrayPool<byte>>();
+ arrayPool.Setup(p => p.Rent(It.IsAny<int>()))
+ .Returns((int m) => ArrayPool<byte>.Shared.Rent(m));
+ arrayPool.Setup(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()))
+ .Callback((byte[] bytes, bool clear) => ArrayPool<byte>.Shared.Return(bytes, clear));
+
+ using (var stream = new FileBufferingReadStream(inner, 1024 * 1024 + 1, 2 * 1024 * 1024, GetCurrentDirectory(), arrayPool.Object))
{
- var inner = MakeStream(1024 * 1024 + 25);
- string tempFileName;
- var arrayPool = new Mock<ArrayPool<byte>>();
- arrayPool.Setup(p => p.Rent(It.IsAny<int>()))
- .Returns((int m) => ArrayPool<byte>.Shared.Rent(m));
- arrayPool.Setup(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()))
- .Callback((byte[] bytes, bool clear) => ArrayPool<byte>.Shared.Return(bytes, clear));
-
- using (var stream = new FileBufferingReadStream(inner, 1024 * 1024 + 1, 2 * 1024 * 1024, GetCurrentDirectory(), arrayPool.Object))
- {
- arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Never());
-
- await stream.ReadAsync(new byte[1024 * 1024]);
- Assert.False(File.Exists(stream.TempFileName), "tempFile should not be created as yet");
-
- await stream.ReadAsync(new byte[4]);
- Assert.True(File.Exists(stream.TempFileName), "tempFile should be created");
- tempFileName = stream.TempFileName!;
-
- arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Once());
- arrayPool.Verify(v => v.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
- }
-
- Assert.False(File.Exists(tempFileName));
+ arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Never());
+
+ await stream.ReadAsync(new byte[1024 * 1024]);
+ Assert.False(File.Exists(stream.TempFileName), "tempFile should not be created as yet");
+
+ await stream.ReadAsync(new byte[4]);
+ Assert.True(File.Exists(stream.TempFileName), "tempFile should be created");
+ tempFileName = stream.TempFileName!;
+
+ arrayPool.Verify(v => v.Rent(It.IsAny<int>()), Times.Once());
+ arrayPool.Verify(v => v.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
}
- [Fact]
- public async Task CopyToAsyncWorks()
- {
- // 4K is the lower bound on buffer sizes
- var bufferSize = 4096;
- var mostExpectedWrites = 8;
- var data = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).ToArray();
- var inner = new MemoryStream(data);
+ Assert.False(File.Exists(tempFileName));
+ }
- using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
+ [Fact]
+ public async Task CopyToAsyncWorks()
+ {
+ // 4K is the lower bound on buffer sizes
+ var bufferSize = 4096;
+ var mostExpectedWrites = 8;
+ var data = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).ToArray();
+ var inner = new MemoryStream(data);
- var withoutBufferMs = new NumberOfWritesMemoryStream();
- await stream.CopyToAsync(withoutBufferMs);
+ using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
- var withBufferMs = new NumberOfWritesMemoryStream();
- stream.Position = 0;
- await stream.CopyToAsync(withBufferMs);
+ var withoutBufferMs = new NumberOfWritesMemoryStream();
+ await stream.CopyToAsync(withoutBufferMs);
- Assert.Equal(data, withoutBufferMs.ToArray());
- Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
- Assert.Equal(data, withBufferMs.ToArray());
- Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
- }
+ var withBufferMs = new NumberOfWritesMemoryStream();
+ stream.Position = 0;
+ await stream.CopyToAsync(withBufferMs);
- [Fact]
- public async Task CopyToAsyncWorksWithFileThreshold()
- {
- // 4K is the lower bound on buffer sizes
- var bufferSize = 4096;
- var mostExpectedWrites = 8;
- var data = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).Reverse().ToArray();
- var inner = new MemoryStream(data);
+ Assert.Equal(data, withoutBufferMs.ToArray());
+ Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
+ Assert.Equal(data, withBufferMs.ToArray());
+ Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
+ }
- using var stream = new FileBufferingReadStream(inner, 100, bufferLimit: null, GetCurrentDirectory());
+ [Fact]
+ public async Task CopyToAsyncWorksWithFileThreshold()
+ {
+ // 4K is the lower bound on buffer sizes
+ var bufferSize = 4096;
+ var mostExpectedWrites = 8;
+ var data = Enumerable.Range(0, bufferSize * mostExpectedWrites).Select(b => (byte)b).Reverse().ToArray();
+ var inner = new MemoryStream(data);
- var withoutBufferMs = new NumberOfWritesMemoryStream();
- await stream.CopyToAsync(withoutBufferMs);
+ using var stream = new FileBufferingReadStream(inner, 100, bufferLimit: null, GetCurrentDirectory());
- var withBufferMs = new NumberOfWritesMemoryStream();
- stream.Position = 0;
- await stream.CopyToAsync(withBufferMs);
+ var withoutBufferMs = new NumberOfWritesMemoryStream();
+ await stream.CopyToAsync(withoutBufferMs);
- Assert.Equal(data, withoutBufferMs.ToArray());
- Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
- Assert.Equal(data, withBufferMs.ToArray());
- Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
- }
+ var withBufferMs = new NumberOfWritesMemoryStream();
+ stream.Position = 0;
+ await stream.CopyToAsync(withBufferMs);
- [Fact]
- public async Task ReadAsyncThenCopyToAsyncWorks()
- {
- var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
- var inner = new MemoryStream(data);
+ Assert.Equal(data, withoutBufferMs.ToArray());
+ Assert.Equal(mostExpectedWrites, withoutBufferMs.NumberOfWrites);
+ Assert.Equal(data, withBufferMs.ToArray());
+ Assert.InRange(withBufferMs.NumberOfWrites, 1, mostExpectedWrites);
+ }
- using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
+ [Fact]
+ public async Task ReadAsyncThenCopyToAsyncWorks()
+ {
+ var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
+ var inner = new MemoryStream(data);
- var withoutBufferMs = new MemoryStream();
- var buffer = new byte[100];
- await stream.ReadAsync(buffer);
- await stream.CopyToAsync(withoutBufferMs);
+ using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
- Assert.Equal(data.AsMemory(0, 100).ToArray(), buffer);
- Assert.Equal(data.AsMemory(100).ToArray(), withoutBufferMs.ToArray());
- }
+ var withoutBufferMs = new MemoryStream();
+ var buffer = new byte[100];
+ await stream.ReadAsync(buffer);
+ await stream.CopyToAsync(withoutBufferMs);
- [Fact]
- public async Task ReadThenCopyToAsyncWorks()
- {
- var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
- var inner = new MemoryStream(data);
+ Assert.Equal(data.AsMemory(0, 100).ToArray(), buffer);
+ Assert.Equal(data.AsMemory(100).ToArray(), withoutBufferMs.ToArray());
+ }
- using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
+ [Fact]
+ public async Task ReadThenCopyToAsyncWorks()
+ {
+ var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
+ var inner = new MemoryStream(data);
- var withoutBufferMs = new MemoryStream();
- var buffer = new byte[100];
- var read = stream.Read(buffer);
- await stream.CopyToAsync(withoutBufferMs);
+ using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
- Assert.Equal(100, read);
- Assert.Equal(data.AsMemory(0, read).ToArray(), buffer);
- Assert.Equal(data.AsMemory(read).ToArray(), withoutBufferMs.ToArray());
- }
+ var withoutBufferMs = new MemoryStream();
+ var buffer = new byte[100];
+ var read = stream.Read(buffer);
+ await stream.CopyToAsync(withoutBufferMs);
- [Fact]
- public async Task ReadThenSeekThenCopyToAsyncWorks()
- {
- var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
- var inner = new MemoryStream(data);
+ Assert.Equal(100, read);
+ Assert.Equal(data.AsMemory(0, read).ToArray(), buffer);
+ Assert.Equal(data.AsMemory(read).ToArray(), withoutBufferMs.ToArray());
+ }
- using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
+ [Fact]
+ public async Task ReadThenSeekThenCopyToAsyncWorks()
+ {
+ var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
+ var inner = new MemoryStream(data);
- var withoutBufferMs = new MemoryStream();
- var buffer = new byte[100];
- var read = stream.Read(buffer);
- stream.Position = 0;
- await stream.CopyToAsync(withoutBufferMs);
+ using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
- Assert.Equal(100, read);
- Assert.Equal(data.AsMemory(0, read).ToArray(), buffer);
- Assert.Equal(data.ToArray(), withoutBufferMs.ToArray());
- }
+ var withoutBufferMs = new MemoryStream();
+ var buffer = new byte[100];
+ var read = stream.Read(buffer);
+ stream.Position = 0;
+ await stream.CopyToAsync(withoutBufferMs);
- [Fact]
- public void PartialReadThenSeekReplaysBuffer()
- {
- var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
- var inner = new MemoryStream(data);
-
- using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
-
- var withoutBufferMs = new MemoryStream();
- var buffer = new byte[100];
- var read1 = stream.Read(buffer);
- stream.Position = 0;
- var buffer2 = new byte[200];
- var read2 = stream.Read(buffer2);
- Assert.Equal(100, read1);
- Assert.Equal(100, read2);
- Assert.Equal(data.AsMemory(0, read1).ToArray(), buffer);
- Assert.Equal(data.AsMemory(0, read2).ToArray(), buffer2.AsMemory(0, read2).ToArray());
- }
+ Assert.Equal(100, read);
+ Assert.Equal(data.AsMemory(0, read).ToArray(), buffer);
+ Assert.Equal(data.ToArray(), withoutBufferMs.ToArray());
+ }
- [Fact]
- public async Task PartialReadAsyncThenSeekReplaysBuffer()
- {
- var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
- var inner = new MemoryStream(data);
-
- using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
-
- var withoutBufferMs = new MemoryStream();
- var buffer = new byte[100];
- var read1 = await stream.ReadAsync(buffer);
- stream.Position = 0;
- var buffer2 = new byte[200];
- var read2 = await stream.ReadAsync(buffer2);
- Assert.Equal(100, read1);
- Assert.Equal(100, read2);
- Assert.Equal(data.AsMemory(0, read1).ToArray(), buffer);
- Assert.Equal(data.AsMemory(0, read2).ToArray(), buffer2.AsMemory(0, read2).ToArray());
- }
+ [Fact]
+ public void PartialReadThenSeekReplaysBuffer()
+ {
+ var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
+ var inner = new MemoryStream(data);
+
+ using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
+
+ var withoutBufferMs = new MemoryStream();
+ var buffer = new byte[100];
+ var read1 = stream.Read(buffer);
+ stream.Position = 0;
+ var buffer2 = new byte[200];
+ var read2 = stream.Read(buffer2);
+ Assert.Equal(100, read1);
+ Assert.Equal(100, read2);
+ Assert.Equal(data.AsMemory(0, read1).ToArray(), buffer);
+ Assert.Equal(data.AsMemory(0, read2).ToArray(), buffer2.AsMemory(0, read2).ToArray());
+ }
+
+ [Fact]
+ public async Task PartialReadAsyncThenSeekReplaysBuffer()
+ {
+ var data = Enumerable.Range(0, 1024).Select(b => (byte)b).ToArray();
+ var inner = new MemoryStream(data);
+
+ using var stream = new FileBufferingReadStream(inner, 1024 * 1024, bufferLimit: null, GetCurrentDirectory());
+
+ var withoutBufferMs = new MemoryStream();
+ var buffer = new byte[100];
+ var read1 = await stream.ReadAsync(buffer);
+ stream.Position = 0;
+ var buffer2 = new byte[200];
+ var read2 = await stream.ReadAsync(buffer2);
+ Assert.Equal(100, read1);
+ Assert.Equal(100, read2);
+ Assert.Equal(data.AsMemory(0, read1).ToArray(), buffer);
+ Assert.Equal(data.AsMemory(0, read2).ToArray(), buffer2.AsMemory(0, read2).ToArray());
+ }
+
+ private static string GetCurrentDirectory()
+ {
+ return AppContext.BaseDirectory;
+ }
+
+ private class NumberOfWritesMemoryStream : MemoryStream
+ {
+ public int NumberOfWrites { get; set; }
- private static string GetCurrentDirectory()
+ public override void Write(byte[] buffer, int offset, int count)
{
- return AppContext.BaseDirectory;
+ NumberOfWrites++;
+ base.Write(buffer, offset, count);
}
- private class NumberOfWritesMemoryStream : MemoryStream
+ public override void Write(ReadOnlySpan<byte> source)
{
- public int NumberOfWrites { get; set; }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- NumberOfWrites++;
- base.Write(buffer, offset, count);
- }
-
- public override void Write(ReadOnlySpan<byte> source)
- {
- NumberOfWrites++;
- base.Write(source);
- }
+ NumberOfWrites++;
+ base.Write(source);
}
}
}
diff --git a/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs b/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs
index 9459a19295..d4d0043cde 100644
--- a/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs
+++ b/src/Http/WebUtilities/test/FileBufferingWriteStreamTests.cs
@@ -10,393 +10,392 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class FileBufferingWriteStreamTests : IDisposable
{
- public class FileBufferingWriteStreamTests : IDisposable
+ private readonly string TempDirectory = Path.Combine(Path.GetTempPath(), "FileBufferingWriteTests", Path.GetRandomFileName());
+
+ public FileBufferingWriteStreamTests()
{
- private readonly string TempDirectory = Path.Combine(Path.GetTempPath(), "FileBufferingWriteTests", Path.GetRandomFileName());
+ Directory.CreateDirectory(TempDirectory);
+ }
- public FileBufferingWriteStreamTests()
- {
- Directory.CreateDirectory(TempDirectory);
- }
+ [Fact]
+ public void Write_BuffersContentToMemory()
+ {
+ // Arrange
+ using var bufferingStream = new FileBufferingWriteStream(tempFileDirectoryAccessor: () => TempDirectory);
+ var input = Encoding.UTF8.GetBytes("Hello world");
- [Fact]
- public void Write_BuffersContentToMemory()
- {
- // Arrange
- using var bufferingStream = new FileBufferingWriteStream(tempFileDirectoryAccessor: () => TempDirectory);
- var input = Encoding.UTF8.GetBytes("Hello world");
+ // Act
+ bufferingStream.Write(input, 0, input.Length);
- // Act
- bufferingStream.Write(input, 0, input.Length);
+ // Assert
+ Assert.Equal(input.Length, bufferingStream.Length);
- // Assert
- Assert.Equal(input.Length, bufferingStream.Length);
+ // We should have written content to memory
+ var pagedByteBuffer = bufferingStream.PagedByteBuffer;
+ Assert.Equal(input, ReadBufferedContent(pagedByteBuffer));
- // We should have written content to memory
- var pagedByteBuffer = bufferingStream.PagedByteBuffer;
- Assert.Equal(input, ReadBufferedContent(pagedByteBuffer));
+ // No files should not have been created.
+ Assert.Null(bufferingStream.FileStream);
+ }
- // No files should not have been created.
- Assert.Null(bufferingStream.FileStream);
- }
+ [Fact]
+ public void Write_BeforeMemoryThresholdIsReached_WritesToMemory()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public void Write_BeforeMemoryThresholdIsReached_WritesToMemory()
- {
- // Arrange
- var input = new byte[] { 1, 2, };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ bufferingStream.Write(input, 0, 2);
- // Act
- bufferingStream.Write(input, 0, 2);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ Assert.Equal(input.Length, bufferingStream.Length);
- Assert.Equal(input.Length, bufferingStream.Length);
+ // File should have been created.
+ Assert.Null(fileStream);
- // File should have been created.
- Assert.Null(fileStream);
+ // No content should be in the memory stream
+ Assert.Equal(2, pageBuffer.Length);
+ Assert.Equal(input, ReadBufferedContent(pageBuffer));
+ }
- // No content should be in the memory stream
- Assert.Equal(2, pageBuffer.Length);
- Assert.Equal(input, ReadBufferedContent(pageBuffer));
- }
+ [Fact]
+ public void Write_BuffersContentToDisk_WhenMemoryThresholdIsReached()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
+ bufferingStream.Write(input, 0, 2);
- [Fact]
- public void Write_BuffersContentToDisk_WhenMemoryThresholdIsReached()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
- bufferingStream.Write(input, 0, 2);
+ // Act
+ bufferingStream.Write(input, 2, 1);
- // Act
- bufferingStream.Write(input, 2, 1);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.NotNull(fileStream);
+ var fileBytes = ReadFileContent(fileStream!);
+ Assert.Equal(input, fileBytes);
- // File should have been created.
- Assert.NotNull(fileStream);
- var fileBytes = ReadFileContent(fileStream!);
- Assert.Equal(input, fileBytes);
+ // No content should be in the memory stream
+ Assert.Equal(0, pageBuffer.Length);
+ }
- // No content should be in the memory stream
- Assert.Equal(0, pageBuffer.Length);
- }
+ [Fact]
+ public void Write_BuffersContentToDisk_WhenWriteWillOverflowMemoryThreshold()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public void Write_BuffersContentToDisk_WhenWriteWillOverflowMemoryThreshold()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ bufferingStream.Write(input, 0, input.Length);
- // Act
- bufferingStream.Write(input, 0, input.Length);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.NotNull(fileStream);
+ var fileBytes = ReadFileContent(fileStream!);
+ Assert.Equal(input, fileBytes);
- // File should have been created.
- Assert.NotNull(fileStream);
- var fileBytes = ReadFileContent(fileStream!);
- Assert.Equal(input, fileBytes);
+ // No content should be in the memory stream
+ Assert.Equal(0, pageBuffer.Length);
+ }
- // No content should be in the memory stream
- Assert.Equal(0, pageBuffer.Length);
- }
+ [Fact]
+ public void Write_AfterMemoryThresholdIsReached_BuffersToMemory()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, 4, 5, 6, 7 };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 4, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public void Write_AfterMemoryThresholdIsReached_BuffersToMemory()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, 4, 5, 6, 7 };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 4, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ bufferingStream.Write(input, 0, 5);
+ bufferingStream.Write(input, 5, 2);
- // Act
- bufferingStream.Write(input, 0, 5);
- bufferingStream.Write(input, 5, 2);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.NotNull(fileStream);
+ var fileBytes = ReadFileContent(fileStream!);
+ Assert.Equal(new byte[] { 1, 2, 3, 4, 5, }, fileBytes);
- // File should have been created.
- Assert.NotNull(fileStream);
- var fileBytes = ReadFileContent(fileStream!);
- Assert.Equal(new byte[] { 1, 2, 3, 4, 5, }, fileBytes);
+ Assert.Equal(new byte[] { 6, 7 }, ReadBufferedContent(pageBuffer));
+ }
- Assert.Equal(new byte[] { 6, 7 }, ReadBufferedContent(pageBuffer));
- }
+ [Fact]
+ public async Task WriteAsync_BuffersContentToMemory()
+ {
+ // Arrange
+ using var bufferingStream = new FileBufferingWriteStream(tempFileDirectoryAccessor: () => TempDirectory);
+ var input = Encoding.UTF8.GetBytes("Hello world");
- [Fact]
- public async Task WriteAsync_BuffersContentToMemory()
- {
- // Arrange
- using var bufferingStream = new FileBufferingWriteStream(tempFileDirectoryAccessor: () => TempDirectory);
- var input = Encoding.UTF8.GetBytes("Hello world");
+ // Act
+ await bufferingStream.WriteAsync(input, 0, input.Length);
- // Act
- await bufferingStream.WriteAsync(input, 0, input.Length);
+ // Assert
+ // We should have written content to memory
+ var pagedByteBuffer = bufferingStream.PagedByteBuffer;
+ Assert.Equal(input, ReadBufferedContent(pagedByteBuffer));
- // Assert
- // We should have written content to memory
- var pagedByteBuffer = bufferingStream.PagedByteBuffer;
- Assert.Equal(input, ReadBufferedContent(pagedByteBuffer));
+ // No files should not have been created.
+ Assert.Null(bufferingStream.FileStream);
+ }
- // No files should not have been created.
- Assert.Null(bufferingStream.FileStream);
- }
+ [Fact]
+ public async Task WriteAsync_BeforeMemoryThresholdIsReached_WritesToMemory()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public async Task WriteAsync_BeforeMemoryThresholdIsReached_WritesToMemory()
- {
- // Arrange
- var input = new byte[] { 1, 2, };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ await bufferingStream.WriteAsync(input, 0, 2);
- // Act
- await bufferingStream.WriteAsync(input, 0, 2);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.Null(fileStream);
- // File should have been created.
- Assert.Null(fileStream);
+ // No content should be in the memory stream
+ Assert.Equal(2, pageBuffer.Length);
+ Assert.Equal(input, ReadBufferedContent(pageBuffer));
+ }
- // No content should be in the memory stream
- Assert.Equal(2, pageBuffer.Length);
- Assert.Equal(input, ReadBufferedContent(pageBuffer));
- }
+ [Fact]
+ public async Task WriteAsync_BuffersContentToDisk_WhenMemoryThresholdIsReached()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
+ bufferingStream.Write(input, 0, 2);
- [Fact]
- public async Task WriteAsync_BuffersContentToDisk_WhenMemoryThresholdIsReached()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
- bufferingStream.Write(input, 0, 2);
+ // Act
+ await bufferingStream.WriteAsync(input, 2, 1);
- // Act
- await bufferingStream.WriteAsync(input, 2, 1);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.NotNull(fileStream);
+ var fileBytes = ReadFileContent(fileStream!);
+ Assert.Equal(input, fileBytes);
- // File should have been created.
- Assert.NotNull(fileStream);
- var fileBytes = ReadFileContent(fileStream!);
- Assert.Equal(input, fileBytes);
+ // No content should be in the memory stream
+ Assert.Equal(0, pageBuffer.Length);
+ }
- // No content should be in the memory stream
- Assert.Equal(0, pageBuffer.Length);
- }
+ [Fact]
+ public async Task WriteAsync_BuffersContentToDisk_WhenWriteWillOverflowMemoryThreshold()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public async Task WriteAsync_BuffersContentToDisk_WhenWriteWillOverflowMemoryThreshold()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ await bufferingStream.WriteAsync(input, 0, input.Length);
- // Act
- await bufferingStream.WriteAsync(input, 0, input.Length);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.NotNull(fileStream);
+ var fileBytes = ReadFileContent(fileStream!);
+ Assert.Equal(input, fileBytes);
- // File should have been created.
- Assert.NotNull(fileStream);
- var fileBytes = ReadFileContent(fileStream!);
- Assert.Equal(input, fileBytes);
+ // No content should be in the memory stream
+ Assert.Equal(0, pageBuffer.Length);
+ }
- // No content should be in the memory stream
- Assert.Equal(0, pageBuffer.Length);
- }
+ [Fact]
+ public async Task WriteAsync_AfterMemoryThresholdIsReached_BuffersToMemory()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, 4, 5, 6, 7 };
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 4, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public async Task WriteAsync_AfterMemoryThresholdIsReached_BuffersToMemory()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, 4, 5, 6, 7 };
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 4, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ await bufferingStream.WriteAsync(input, 0, 5);
+ await bufferingStream.WriteAsync(input, 5, 2);
- // Act
- await bufferingStream.WriteAsync(input, 0, 5);
- await bufferingStream.WriteAsync(input, 5, 2);
+ // Assert
+ var pageBuffer = bufferingStream.PagedByteBuffer;
+ var fileStream = bufferingStream.FileStream;
- // Assert
- var pageBuffer = bufferingStream.PagedByteBuffer;
- var fileStream = bufferingStream.FileStream;
+ // File should have been created.
+ Assert.NotNull(fileStream);
+ var fileBytes = ReadFileContent(fileStream!);
- // File should have been created.
- Assert.NotNull(fileStream);
- var fileBytes = ReadFileContent(fileStream!);
+ Assert.Equal(input.Length, bufferingStream.Length);
- Assert.Equal(input.Length, bufferingStream.Length);
+ Assert.Equal(new byte[] { 1, 2, 3, 4, 5, }, fileBytes);
+ Assert.Equal(new byte[] { 6, 7 }, ReadBufferedContent(pageBuffer));
+ }
- Assert.Equal(new byte[] { 1, 2, 3, 4, 5, }, fileBytes);
- Assert.Equal(new byte[] { 6, 7 }, ReadBufferedContent(pageBuffer));
- }
+ [Fact]
+ public void Write_Throws_IfSingleWriteExceedsBufferLimit()
+ {
+ // Arrange
+ var input = new byte[20];
+ var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public void Write_Throws_IfSingleWriteExceedsBufferLimit()
- {
- // Arrange
- var input = new byte[20];
- var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ var exception = Assert.Throws<IOException>(() => bufferingStream.Write(input, 0, input.Length));
+ Assert.Equal("Buffer limit exceeded.", exception.Message);
- // Act
- var exception = Assert.Throws<IOException>(() => bufferingStream.Write(input, 0, input.Length));
- Assert.Equal("Buffer limit exceeded.", exception.Message);
+ Assert.True(bufferingStream.Disposed);
+ }
- Assert.True(bufferingStream.Disposed);
- }
+ [Fact]
+ public void Write_Throws_IfWriteCumulativeWritesExceedsBuffersLimit()
+ {
+ // Arrange
+ var input = new byte[6];
+ var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public void Write_Throws_IfWriteCumulativeWritesExceedsBuffersLimit()
- {
- // Arrange
- var input = new byte[6];
- var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ bufferingStream.Write(input, 0, input.Length);
+ var exception = Assert.Throws<IOException>(() => bufferingStream.Write(input, 0, input.Length));
+ Assert.Equal("Buffer limit exceeded.", exception.Message);
- // Act
- bufferingStream.Write(input, 0, input.Length);
- var exception = Assert.Throws<IOException>(() => bufferingStream.Write(input, 0, input.Length));
- Assert.Equal("Buffer limit exceeded.", exception.Message);
+ // Verify we return the buffer.
+ Assert.True(bufferingStream.Disposed);
+ }
- // Verify we return the buffer.
- Assert.True(bufferingStream.Disposed);
- }
+ [Fact]
+ public void Write_DoesNotThrow_IfBufferLimitIsReached()
+ {
+ // Arrange
+ var input = new byte[5];
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public void Write_DoesNotThrow_IfBufferLimitIsReached()
- {
- // Arrange
- var input = new byte[5];
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ bufferingStream.Write(input, 0, input.Length);
+ bufferingStream.Write(input, 0, input.Length); // Should get to exactly the buffer limit, which is fine
- // Act
- bufferingStream.Write(input, 0, input.Length);
- bufferingStream.Write(input, 0, input.Length); // Should get to exactly the buffer limit, which is fine
+ // If we got here, the test succeeded.
+ }
- // If we got here, the test succeeded.
- }
+ [Fact]
+ public async Task WriteAsync_Throws_IfSingleWriteExceedsBufferLimit()
+ {
+ // Arrange
+ var input = new byte[20];
+ var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public async Task WriteAsync_Throws_IfSingleWriteExceedsBufferLimit()
- {
- // Arrange
- var input = new byte[20];
- var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ var exception = await Assert.ThrowsAsync<IOException>(() => bufferingStream.WriteAsync(input, 0, input.Length));
+ Assert.Equal("Buffer limit exceeded.", exception.Message);
- // Act
- var exception = await Assert.ThrowsAsync<IOException>(() => bufferingStream.WriteAsync(input, 0, input.Length));
- Assert.Equal("Buffer limit exceeded.", exception.Message);
+ Assert.True(bufferingStream.Disposed);
+ }
- Assert.True(bufferingStream.Disposed);
- }
+ [Fact]
+ public async Task WriteAsync_Throws_IfWriteCumulativeWritesExceedsBuffersLimit()
+ {
+ // Arrange
+ var input = new byte[6];
+ var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public async Task WriteAsync_Throws_IfWriteCumulativeWritesExceedsBuffersLimit()
- {
- // Arrange
- var input = new byte[6];
- var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ await bufferingStream.WriteAsync(input, 0, input.Length);
+ var exception = await Assert.ThrowsAsync<IOException>(() => bufferingStream.WriteAsync(input, 0, input.Length));
+ Assert.Equal("Buffer limit exceeded.", exception.Message);
- // Act
- await bufferingStream.WriteAsync(input, 0, input.Length);
- var exception = await Assert.ThrowsAsync<IOException>(() => bufferingStream.WriteAsync(input, 0, input.Length));
- Assert.Equal("Buffer limit exceeded.", exception.Message);
+ // Verify we return the buffer.
+ Assert.True(bufferingStream.Disposed);
+ }
- // Verify we return the buffer.
- Assert.True(bufferingStream.Disposed);
- }
+ [Fact]
+ public async Task WriteAsync_DoesNotThrow_IfBufferLimitIsReached()
+ {
+ // Arrange
+ var input = new byte[5];
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
- [Fact]
- public async Task WriteAsync_DoesNotThrow_IfBufferLimitIsReached()
- {
- // Arrange
- var input = new byte[5];
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 2, bufferLimit: 10, tempFileDirectoryAccessor: () => TempDirectory);
+ // Act
+ await bufferingStream.WriteAsync(input, 0, input.Length);
+ await bufferingStream.WriteAsync(input, 0, input.Length); // Should get to exactly the buffer limit, which is fine
- // Act
- await bufferingStream.WriteAsync(input, 0, input.Length);
- await bufferingStream.WriteAsync(input, 0, input.Length); // Should get to exactly the buffer limit, which is fine
+ // If we got here, the test succeeded.
+ }
- // If we got here, the test succeeded.
- }
+ [Fact]
+ public async Task DrainBufferAsync_CopiesContentFromMemoryStream()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, 4, 5 };
+ using var bufferingStream = new FileBufferingWriteStream(tempFileDirectoryAccessor: () => TempDirectory);
+ bufferingStream.Write(input, 0, input.Length);
+ var memoryStream = new MemoryStream();
+
+ // Act
+ await bufferingStream.DrainBufferAsync(memoryStream, default);
+
+ // Assert
+ Assert.Equal(input, memoryStream.ToArray());
+ Assert.Equal(0, bufferingStream.Length);
+ }
- [Fact]
- public async Task DrainBufferAsync_CopiesContentFromMemoryStream()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, 4, 5 };
- using var bufferingStream = new FileBufferingWriteStream(tempFileDirectoryAccessor: () => TempDirectory);
- bufferingStream.Write(input, 0, input.Length);
- var memoryStream = new MemoryStream();
-
- // Act
- await bufferingStream.DrainBufferAsync(memoryStream, default);
-
- // Assert
- Assert.Equal(input, memoryStream.ToArray());
- Assert.Equal(0, bufferingStream.Length);
- }
+ [Fact]
+ public async Task DrainBufferAsync_WithContentInDisk_CopiesContentFromMemoryStream()
+ {
+ // Arrange
+ var input = Enumerable.Repeat((byte)0xca, 30).ToArray();
+ using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 21, tempFileDirectoryAccessor: () => TempDirectory);
+ bufferingStream.Write(input, 0, input.Length);
+ var memoryStream = new MemoryStream();
+
+ // Act
+ await bufferingStream.DrainBufferAsync(memoryStream, default);
+
+ // Assert
+ Assert.Equal(input, memoryStream.ToArray());
+ Assert.Equal(0, bufferingStream.Length);
+ }
- [Fact]
- public async Task DrainBufferAsync_WithContentInDisk_CopiesContentFromMemoryStream()
+ public void Dispose()
+ {
+ try
{
- // Arrange
- var input = Enumerable.Repeat((byte)0xca, 30).ToArray();
- using var bufferingStream = new FileBufferingWriteStream(memoryThreshold: 21, tempFileDirectoryAccessor: () => TempDirectory);
- bufferingStream.Write(input, 0, input.Length);
- var memoryStream = new MemoryStream();
-
- // Act
- await bufferingStream.DrainBufferAsync(memoryStream, default);
-
- // Assert
- Assert.Equal(input, memoryStream.ToArray());
- Assert.Equal(0, bufferingStream.Length);
+ Directory.Delete(TempDirectory, recursive: true);
}
-
- public void Dispose()
+ catch
{
- try
- {
- Directory.Delete(TempDirectory, recursive: true);
- }
- catch
- {
- }
}
+ }
- private static byte[] ReadFileContent(FileStream fileStream)
- {
- var fs = new FileStream(fileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite);
- using var memoryStream = new MemoryStream();
- fs.CopyTo(memoryStream);
+ private static byte[] ReadFileContent(FileStream fileStream)
+ {
+ var fs = new FileStream(fileStream.Name, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.ReadWrite);
+ using var memoryStream = new MemoryStream();
+ fs.CopyTo(memoryStream);
- return memoryStream.ToArray();
- }
+ return memoryStream.ToArray();
+ }
- private static byte[] ReadBufferedContent(PagedByteBuffer buffer)
- {
- using var memoryStream = new MemoryStream();
- buffer.MoveTo(memoryStream);
+ private static byte[] ReadBufferedContent(PagedByteBuffer buffer)
+ {
+ using var memoryStream = new MemoryStream();
+ buffer.MoveTo(memoryStream);
- return memoryStream.ToArray();
- }
+ return memoryStream.ToArray();
}
}
diff --git a/src/Http/WebUtilities/test/FormPipeReaderTests.cs b/src/Http/WebUtilities/test/FormPipeReaderTests.cs
index a34deb1d92..07c160da40 100644
--- a/src/Http/WebUtilities/test/FormPipeReaderTests.cs
+++ b/src/Http/WebUtilities/test/FormPipeReaderTests.cs
@@ -6,610 +6,609 @@ using System.IO.Pipelines;
using System.Text;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class FormPipeReaderTests
{
- public class FormPipeReaderTests
+ [Fact]
+ public async Task ReadFormAsync_EmptyKeyAtEndAllowed()
{
- [Fact]
- public async Task ReadFormAsync_EmptyKeyAtEndAllowed()
- {
- var bodyPipe = await MakePipeReader("=bar");
+ var bodyPipe = await MakePipeReader("=bar");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal("bar", formCollection[""].ToString());
- }
+ Assert.Equal("bar", formCollection[""].ToString());
+ }
- [Fact]
- public async Task ReadFormAsync_EmptyKeyWithAdditionalEntryAllowed()
- {
- var bodyPipe = await MakePipeReader("=bar&baz=2");
+ [Fact]
+ public async Task ReadFormAsync_EmptyKeyWithAdditionalEntryAllowed()
+ {
+ var bodyPipe = await MakePipeReader("=bar&baz=2");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal("bar", formCollection[""].ToString());
- Assert.Equal("2", formCollection["baz"].ToString());
- }
+ Assert.Equal("bar", formCollection[""].ToString());
+ Assert.Equal("2", formCollection["baz"].ToString());
+ }
- [Fact]
- public async Task ReadFormAsync_EmptyValueAtEndAllowed()
- {
- var bodyPipe = await MakePipeReader("foo=");
+ [Fact]
+ public async Task ReadFormAsync_EmptyValueAtEndAllowed()
+ {
+ var bodyPipe = await MakePipeReader("foo=");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal("", formCollection["foo"].ToString());
- }
+ Assert.Equal("", formCollection["foo"].ToString());
+ }
- [Fact]
- public async Task ReadFormAsync_EmptyValueWithoutEqualsAtEndAllowed()
- {
- var bodyPipe = await MakePipeReader("foo");
+ [Fact]
+ public async Task ReadFormAsync_EmptyValueWithoutEqualsAtEndAllowed()
+ {
+ var bodyPipe = await MakePipeReader("foo");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal("", formCollection["foo"].ToString());
- }
+ Assert.Equal("", formCollection["foo"].ToString());
+ }
- [Fact]
- public async Task ReadFormAsync_EmptyValueWithAdditionalEntryAllowed()
- {
- var bodyPipe = await MakePipeReader("foo=&baz=2");
+ [Fact]
+ public async Task ReadFormAsync_EmptyValueWithAdditionalEntryAllowed()
+ {
+ var bodyPipe = await MakePipeReader("foo=&baz=2");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal("", formCollection["foo"].ToString());
- Assert.Equal("2", formCollection["baz"].ToString());
- }
+ Assert.Equal("", formCollection["foo"].ToString());
+ Assert.Equal("2", formCollection["baz"].ToString());
+ }
- [Fact]
- public async Task ReadFormAsync_EmptyValueWithoutEqualsWithAdditionalEntryAllowed()
- {
- var bodyPipe = await MakePipeReader("foo&baz=2");
+ [Fact]
+ public async Task ReadFormAsync_EmptyValueWithoutEqualsWithAdditionalEntryAllowed()
+ {
+ var bodyPipe = await MakePipeReader("foo&baz=2");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal("", formCollection["foo"].ToString());
- Assert.Equal("2", formCollection["baz"].ToString());
- }
+ Assert.Equal("", formCollection["foo"].ToString());
+ Assert.Equal("2", formCollection["baz"].ToString());
+ }
- [Fact]
- public async Task ReadFormAsync_ValueContainsInvalidCharacters_Throw()
- {
- var bodyPipe = await MakePipeReader("%00");
+ [Fact]
+ public async Task ReadFormAsync_ValueContainsInvalidCharacters_Throw()
+ {
+ var bodyPipe = await MakePipeReader("%00");
- var exception = await Assert.ThrowsAsync<InvalidDataException>(
- () => ReadFormAsync(new FormPipeReader(bodyPipe)));
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(
+ () => ReadFormAsync(new FormPipeReader(bodyPipe)));
- Assert.Equal("The form value contains invalid characters.", exception.Message);
- Assert.IsType<InvalidOperationException>(exception.InnerException);
- }
+ Assert.Equal("The form value contains invalid characters.", exception.Message);
+ Assert.IsType<InvalidOperationException>(exception.InnerException);
+ }
- [Fact]
- public async Task ReadFormAsync_ValueCountLimitMet_Success()
- {
- var bodyPipe = await MakePipeReader("foo=1&bar=2&baz=3");
+ [Fact]
+ public async Task ReadFormAsync_ValueCountLimitMet_Success()
+ {
+ var bodyPipe = await MakePipeReader("foo=1&bar=2&baz=3");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe) { ValueCountLimit = 3 });
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe) { 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);
- }
+ Assert.Equal("1", formCollection["foo"].ToString());
+ Assert.Equal("2", formCollection["bar"].ToString());
+ Assert.Equal("3", formCollection["baz"].ToString());
+ Assert.Equal(3, formCollection.Count);
+ }
- [Fact]
- public async Task ReadFormAsync_ValueCountLimitExceeded_Throw()
- {
- var content = "foo=1&baz=2&bar=3&baz=4&baf=5";
- var bodyPipe = await MakePipeReader(content);
+ [Fact]
+ public async Task ReadFormAsync_ValueCountLimitExceeded_Throw()
+ {
+ var content = "foo=1&baz=2&bar=3&baz=4&baf=5";
+ var bodyPipe = await MakePipeReader(content);
- var exception = await Assert.ThrowsAsync<InvalidDataException>(
- () => ReadFormAsync(new FormPipeReader(bodyPipe) { ValueCountLimit = 3 }));
- Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(
+ () => ReadFormAsync(new FormPipeReader(bodyPipe) { ValueCountLimit = 3 }));
+ Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
- // The body pipe is still readable and has not advanced.
- var readResult = await bodyPipe.ReadAsync();
- Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
- }
+ // The body pipe is still readable and has not advanced.
+ var readResult = await bodyPipe.ReadAsync();
+ Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
+ }
- [Fact]
- public async Task ReadFormAsync_ValueCountLimitExceededSameKey_Throw()
- {
- var content = "baz=1&baz=2&baz=3&baz=4";
- var bodyPipe = await MakePipeReader(content);
+ [Fact]
+ public async Task ReadFormAsync_ValueCountLimitExceededSameKey_Throw()
+ {
+ var content = "baz=1&baz=2&baz=3&baz=4";
+ var bodyPipe = await MakePipeReader(content);
- var exception = await Assert.ThrowsAsync<InvalidDataException>(
- () => ReadFormAsync(new FormPipeReader(bodyPipe) { ValueCountLimit = 3 }));
- Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(
+ () => ReadFormAsync(new FormPipeReader(bodyPipe) { ValueCountLimit = 3 }));
+ Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
- // The body pipe is still readable and has not advanced.
- var readResult = await bodyPipe.ReadAsync();
- Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
- }
+ // The body pipe is still readable and has not advanced.
+ var readResult = await bodyPipe.ReadAsync();
+ Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
+ }
- [Fact]
- public async Task ReadFormAsync_KeyLengthLimitMet_Success()
- {
- var bodyPipe = await MakePipeReader("fooooooooo=1&bar=2&baz=3&baz=4");
+ [Fact]
+ public async Task ReadFormAsync_KeyLengthLimitMet_Success()
+ {
+ var bodyPipe = await MakePipeReader("fooooooooo=1&bar=2&baz=3&baz=4");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe) { KeyLengthLimit = 10 });
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe) { KeyLengthLimit = 10 });
- Assert.Equal("1", formCollection["fooooooooo"].ToString());
- Assert.Equal("2", formCollection["bar"].ToString());
- Assert.Equal("3,4", formCollection["baz"].ToString());
- Assert.Equal(3, formCollection.Count);
- }
+ Assert.Equal("1", formCollection["fooooooooo"].ToString());
+ Assert.Equal("2", formCollection["bar"].ToString());
+ Assert.Equal("3,4", formCollection["baz"].ToString());
+ Assert.Equal(3, formCollection.Count);
+ }
- [Fact]
- public async Task ReadFormAsync_KeyLengthLimitExceeded_Throw()
- {
- var content = "foo=1&baz12345678=2";
- var bodyPipe = await MakePipeReader(content);
+ [Fact]
+ public async Task ReadFormAsync_KeyLengthLimitExceeded_Throw()
+ {
+ var content = "foo=1&baz12345678=2";
+ var bodyPipe = await MakePipeReader(content);
- var exception = await Assert.ThrowsAsync<InvalidDataException>(
- () => ReadFormAsync(new FormPipeReader(bodyPipe) { KeyLengthLimit = 10 }));
- Assert.Equal("Form key length limit 10 exceeded.", exception.Message);
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(
+ () => ReadFormAsync(new FormPipeReader(bodyPipe) { KeyLengthLimit = 10 }));
+ Assert.Equal("Form key length limit 10 exceeded.", exception.Message);
- // The body pipe is still readable and has not advanced.
- var readResult = await bodyPipe.ReadAsync();
- Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
- }
+ // The body pipe is still readable and has not advanced.
+ var readResult = await bodyPipe.ReadAsync();
+ Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
+ }
- [Fact]
- public async Task ReadFormAsync_ValueLengthLimitMet_Success()
- {
- var bodyPipe = await MakePipeReader("foo=1&bar=1234567890&baz=3&baz=4");
+ [Fact]
+ public async Task ReadFormAsync_ValueLengthLimitMet_Success()
+ {
+ var bodyPipe = await MakePipeReader("foo=1&bar=1234567890&baz=3&baz=4");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe) { ValueLengthLimit = 10 });
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe) { 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);
- }
+ Assert.Equal("1", formCollection["foo"].ToString());
+ Assert.Equal("1234567890", formCollection["bar"].ToString());
+ Assert.Equal("3,4", formCollection["baz"].ToString());
+ Assert.Equal(3, formCollection.Count);
+ }
- [Fact]
- public async Task ReadFormAsync_ValueLengthLimitExceeded_Throw()
- {
- var content = "foo=1&baz=12345678901";
- var bodyPipe = await MakePipeReader(content);
+ [Fact]
+ public async Task ReadFormAsync_ValueLengthLimitExceeded_Throw()
+ {
+ var content = "foo=1&baz=12345678901";
+ var bodyPipe = await MakePipeReader(content);
- var exception = await Assert.ThrowsAsync<InvalidDataException>(
- () => ReadFormAsync(new FormPipeReader(bodyPipe) { ValueLengthLimit = 10 }));
- Assert.Equal("Form value length limit 10 exceeded.", exception.Message);
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(
+ () => ReadFormAsync(new FormPipeReader(bodyPipe) { ValueLengthLimit = 10 }));
+ Assert.Equal("Form value length limit 10 exceeded.", exception.Message);
- // The body pipe is still readable and has not advanced.
- var readResult = await bodyPipe.ReadAsync();
- Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
- }
+ // The body pipe is still readable and has not advanced.
+ var readResult = await bodyPipe.ReadAsync();
+ Assert.Equal(Encoding.UTF8.GetBytes(content), readResult.Buffer.ToArray());
+ }
- [Fact]
- public async Task ReadFormAsync_ValueLengthLimitExceededAcrossBufferBoundary_Throw()
- {
- Pipe bodyPipe = new Pipe();
+ [Fact]
+ public async Task ReadFormAsync_ValueLengthLimitExceededAcrossBufferBoundary_Throw()
+ {
+ Pipe bodyPipe = new Pipe();
- var content1 = "foo=1&baz=1234567890";
- var content2 = "1";
+ var content1 = "foo=1&baz=1234567890";
+ var content2 = "1";
- await bodyPipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(content1));
- await bodyPipe.Writer.FlushAsync();
+ await bodyPipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(content1));
+ await bodyPipe.Writer.FlushAsync();
- var readTask = Assert.ThrowsAsync<InvalidDataException>(
- () => ReadFormAsync(new FormPipeReader(bodyPipe.Reader) { ValueLengthLimit = 10 }));
+ var readTask = Assert.ThrowsAsync<InvalidDataException>(
+ () => ReadFormAsync(new FormPipeReader(bodyPipe.Reader) { ValueLengthLimit = 10 }));
- await bodyPipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(content2));
- bodyPipe.Writer.Complete();
+ await bodyPipe.Writer.WriteAsync(Encoding.UTF8.GetBytes(content2));
+ bodyPipe.Writer.Complete();
- var exception = await readTask;
- Assert.Equal("Form value length limit 10 exceeded.", exception.Message);
+ var exception = await readTask;
+ Assert.Equal("Form value length limit 10 exceeded.", exception.Message);
- // The body pipe is still readable and has not advanced.
- var readResult = await bodyPipe.Reader.ReadAsync();
- Assert.Equal(Encoding.UTF8.GetBytes("baz=12345678901"), readResult.Buffer.ToArray());
- }
+ // The body pipe is still readable and has not advanced.
+ var readResult = await bodyPipe.Reader.ReadAsync();
+ Assert.Equal(Encoding.UTF8.GetBytes("baz=12345678901"), readResult.Buffer.ToArray());
+ }
- // 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 bodyPipe = await MakePipeReader(text: formData);
+ // 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 bodyPipe = await MakePipeReader(text: formData);
- var form = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var form = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Equal(expectedValue, form[key]);
- }
+ Assert.Equal(expectedValue, form[key]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_SingleSegmentWorks(Encoding encoding)
- {
- var readOnlySequence = new ReadOnlySequence<byte>(encoding.GetBytes("foo=bar&baz=boo"));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_SingleSegmentWorks(Encoding encoding)
+ {
+ var readOnlySequence = new ReadOnlySequence<byte>(encoding.GetBytes("foo=bar&baz=boo"));
- KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
+ KeyValueAccumulator accumulator = default;
+ var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(2, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("boo", dict["baz"]);
- }
+ Assert.Equal(2, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("boo", dict["baz"]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_Works(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_Works(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(3, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("boo", dict["baz"]);
- Assert.Equal("", dict["t"]);
- }
+ Assert.Equal(3, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("boo", dict["baz"]);
+ Assert.Equal("", dict["t"]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_LimitsCanBeLarge(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
-
- KeyValueAccumulator accumulator = default;
-
- var formReader = new FormPipeReader(null!, encoding);
- formReader.KeyLengthLimit = int.MaxValue;
- formReader.ValueLengthLimit = int.MaxValue;
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: false);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
-
- Assert.Equal(3, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("boo", dict["baz"]);
- Assert.Equal("", dict["t"]);
- }
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_LimitsCanBeLarge(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
+
+ KeyValueAccumulator accumulator = default;
+
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.KeyLengthLimit = int.MaxValue;
+ formReader.ValueLengthLimit = int.MaxValue;
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: false);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
+
+ Assert.Equal(3, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("boo", dict["baz"]);
+ Assert.Equal("", dict["t"]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_SplitAcrossSegmentsWorks(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_SplitAcrossSegmentsWorks(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(3, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("boo", dict["baz"]);
- Assert.Equal("", dict["t"]);
- }
+ Assert.Equal(3, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("boo", dict["baz"]);
+ Assert.Equal("", dict["t"]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_SplitAcrossSegmentsWorks_LimitsCanBeLarge(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
-
- KeyValueAccumulator accumulator = default;
-
- var formReader = new FormPipeReader(null!, encoding);
- formReader.KeyLengthLimit = int.MaxValue;
- formReader.ValueLengthLimit = int.MaxValue;
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: false);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
-
- Assert.Equal(3, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("boo", dict["baz"]);
- Assert.Equal("", dict["t"]);
- }
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_SplitAcrossSegmentsWorks_LimitsCanBeLarge(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=boo&t="));
+
+ KeyValueAccumulator accumulator = default;
+
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.KeyLengthLimit = int.MaxValue;
+ formReader.ValueLengthLimit = int.MaxValue;
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: false);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
+
+ Assert.Equal(3, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("boo", dict["baz"]);
+ Assert.Equal("", dict["t"]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_MultiSegmentWithArrayPoolAcrossSegmentsWorks(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=bo" + new string('a', 128)));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_MultiSegmentWithArrayPoolAcrossSegmentsWorks(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("foo=bar&baz=bo" + new string('a', 128)));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(2, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("bo" + new string('a', 128), dict["baz"]);
- }
+ Assert.Equal(2, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("bo" + new string('a', 128), dict["baz"]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_MultiSegmentSplitAcrossSegmentsWithPlusesWorks(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("+++=+++&++++=++++&+="));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_MultiSegmentSplitAcrossSegmentsWithPlusesWorks(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("+++=+++&++++=++++&+="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(3, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal(" ", dict[" "]);
- Assert.Equal(" ", dict[" "]);
- Assert.Equal("", dict[" "]);
- }
+ Assert.Equal(3, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal(" ", dict[" "]);
+ Assert.Equal(" ", dict[" "]);
+ Assert.Equal("", dict[" "]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_DecodedPlusesWorks(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("++%2B=+++%2B&++++=++++&+="));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_DecodedPlusesWorks(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(encoding.GetBytes("++%2B=+++%2B&++++=++++&+="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(3, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal(" ", dict[" "]);
- Assert.Equal(" +", dict[" +"]);
- Assert.Equal("", dict[" "]);
- }
+ Assert.Equal(3, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal(" ", dict[" "]);
+ Assert.Equal(" +", dict[" +"]);
+ Assert.Equal("", dict[" "]);
+ }
- [Theory]
- [MemberData(nameof(Encodings))]
- public void TryParseFormValues_SplitAcrossSegmentsThatNeedDecodingWorks(Encoding encoding)
- {
- var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("\"%-.<>\\^_`{|}~=\"%-.<>\\^_`{|}~&\"%-.<>\\^_`{|}=wow"));
+ [Theory]
+ [MemberData(nameof(Encodings))]
+ public void TryParseFormValues_SplitAcrossSegmentsThatNeedDecodingWorks(Encoding encoding)
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SegmentPerByteFactory.CreateWithContent(encoding.GetBytes("\"%-.<>\\^_`{|}~=\"%-.<>\\^_`{|}~&\"%-.<>\\^_`{|}=wow"));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!, encoding);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!, encoding);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(2, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("\"%-.<>\\^_`{|}~", dict["\"%-.<>\\^_`{|}~"]);
- Assert.Equal("wow", dict["\"%-.<>\\^_`{|}"]);
- }
+ Assert.Equal(2, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("\"%-.<>\\^_`{|}~", dict["\"%-.<>\\^_`{|}~"]);
+ Assert.Equal("wow", dict["\"%-.<>\\^_`{|}"]);
+ }
- [Fact]
- public void TryParseFormValues_MultiSegmentFastPathWorks()
- {
- var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=bar&"), Encoding.UTF8.GetBytes("baz=boo"));
+ [Fact]
+ public void TryParseFormValues_MultiSegmentFastPathWorks()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=bar&"), Encoding.UTF8.GetBytes("baz=boo"));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ var formReader = new FormPipeReader(null!);
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- Assert.Equal(2, accumulator.KeyCount);
- var dict = accumulator.GetResults();
- Assert.Equal("bar", dict["foo"]);
- Assert.Equal("boo", dict["baz"]);
- }
+ Assert.Equal(2, accumulator.KeyCount);
+ var dict = accumulator.GetResults();
+ Assert.Equal("bar", dict["foo"]);
+ Assert.Equal("boo", dict["baz"]);
+ }
- [Fact]
- public void TryParseFormValues_ExceedKeyLengthThrows()
- {
- var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(Encoding.UTF8.GetBytes("foo=bar&baz=boo&t="));
+ [Fact]
+ public void TryParseFormValues_ExceedKeyLengthThrows()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.SingleSegmentFactory.CreateWithContent(Encoding.UTF8.GetBytes("foo=bar&baz=boo&t="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.KeyLengthLimit = 2;
+ var formReader = new FormPipeReader(null!);
+ formReader.KeyLengthLimit = 2;
- var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
- Assert.Equal("Form key length limit 2 exceeded.", exception.Message);
- }
+ var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
+ Assert.Equal("Form key length limit 2 exceeded.", exception.Message);
+ }
- [Fact]
- public void TryParseFormValues_ExceedKeyLengthThrowsInSplitSegment()
- {
- var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("fo=bar&ba"), Encoding.UTF8.GetBytes("z=boo&t="));
+ [Fact]
+ public void TryParseFormValues_ExceedKeyLengthThrowsInSplitSegment()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("fo=bar&ba"), Encoding.UTF8.GetBytes("z=boo&t="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.KeyLengthLimit = 2;
+ var formReader = new FormPipeReader(null!);
+ formReader.KeyLengthLimit = 2;
- var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
- Assert.Equal("Form key length limit 2 exceeded.", exception.Message);
- }
+ var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
+ Assert.Equal("Form key length limit 2 exceeded.", exception.Message);
+ }
- [Fact]
- public void TryParseFormValues_ExceedValueLengthThrows()
- {
- var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=bar&baz=boo&t="));
+ [Fact]
+ public void TryParseFormValues_ExceedValueLengthThrows()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=bar&baz=boo&t="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.ValueLengthLimit = 2;
+ var formReader = new FormPipeReader(null!);
+ formReader.ValueLengthLimit = 2;
- var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
- Assert.Equal("Form value length limit 2 exceeded.", exception.Message);
- }
+ var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
+ Assert.Equal("Form value length limit 2 exceeded.", exception.Message);
+ }
- [Fact]
- public void TryParseFormValues_ExceedValueLengthThrowsInSplitSegment()
- {
- var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=ba&baz=bo"), Encoding.UTF8.GetBytes("o&t="));
+ [Fact]
+ public void TryParseFormValues_ExceedValueLengthThrowsInSplitSegment()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=ba&baz=bo"), Encoding.UTF8.GetBytes("o&t="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.ValueLengthLimit = 2;
+ var formReader = new FormPipeReader(null!);
+ formReader.ValueLengthLimit = 2;
- var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
- Assert.Equal("Form value length limit 2 exceeded.", exception.Message);
- }
+ var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
+ Assert.Equal("Form value length limit 2 exceeded.", exception.Message);
+ }
- [Fact]
- public void TryParseFormValues_ExceedKeyLengthThrowsInSplitSegmentEnd()
- {
- var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=ba&baz=bo"), Encoding.UTF8.GetBytes("o&asdfasdfasd="));
+ [Fact]
+ public void TryParseFormValues_ExceedKeyLengthThrowsInSplitSegmentEnd()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=ba&baz=bo"), Encoding.UTF8.GetBytes("o&asdfasdfasd="));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.KeyLengthLimit = 10;
+ var formReader = new FormPipeReader(null!);
+ formReader.KeyLengthLimit = 10;
- var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
- Assert.Equal("Form key length limit 10 exceeded.", exception.Message);
- }
+ var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
+ Assert.Equal("Form key length limit 10 exceeded.", exception.Message);
+ }
- [Fact]
- public void TryParseFormValues_ExceedValueLengthThrowsInSplitSegmentEnd()
- {
- var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=ba&baz=bo"), Encoding.UTF8.GetBytes("o&t=asdfasdfasd"));
+ [Fact]
+ public void TryParseFormValues_ExceedValueLengthThrowsInSplitSegmentEnd()
+ {
+ var readOnlySequence = ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("foo=ba&baz=bo"), Encoding.UTF8.GetBytes("o&t=asdfasdfasd"));
- KeyValueAccumulator accumulator = default;
+ KeyValueAccumulator accumulator = default;
- var formReader = new FormPipeReader(null!);
- formReader.ValueLengthLimit = 10;
+ var formReader = new FormPipeReader(null!);
+ formReader.ValueLengthLimit = 10;
- var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
- Assert.Equal("Form value length limit 10 exceeded.", exception.Message);
- }
+ var exception = Assert.Throws<InvalidDataException>(() => formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true));
+ Assert.Equal("Form value length limit 10 exceeded.", exception.Message);
+ }
- [Fact]
- public async Task ResetPipeWorks()
+ [Fact]
+ public async Task ResetPipeWorks()
+ {
+ // Same test that is in the benchmark
+ var pipe = new Pipe();
+ var bytes = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
+
+ for (var i = 0; i < 1000; i++)
{
- // Same test that is in the benchmark
- var pipe = new Pipe();
- var bytes = Encoding.UTF8.GetBytes("foo=bar&baz=boo");
-
- for (var i = 0; i < 1000; i++)
- {
- pipe.Writer.Write(bytes);
- pipe.Writer.Complete();
- var formReader = new FormPipeReader(pipe.Reader);
- await formReader.ReadFormAsync();
- pipe.Reader.Complete();
- pipe.Reset();
- }
+ pipe.Writer.Write(bytes);
+ pipe.Writer.Complete();
+ var formReader = new FormPipeReader(pipe.Reader);
+ await formReader.ReadFormAsync();
+ pipe.Reader.Complete();
+ pipe.Reset();
}
+ }
+
+ [Theory]
+ [MemberData(nameof(IncompleteFormKeys))]
+ public void ParseFormWithIncompleteKeyWhenIsFinalBlockSucceeds(ReadOnlySequence<byte> readOnlySequence)
+ {
+ KeyValueAccumulator accumulator = default;
- [Theory]
- [MemberData(nameof(IncompleteFormKeys))]
- public void ParseFormWithIncompleteKeyWhenIsFinalBlockSucceeds(ReadOnlySequence<byte> readOnlySequence)
+ var formReader = new FormPipeReader(null!)
{
- KeyValueAccumulator accumulator = default;
+ KeyLengthLimit = 3
+ };
- var formReader = new FormPipeReader(null!)
- {
- KeyLengthLimit = 3
- };
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
+ IDictionary<string, StringValues> values = accumulator.GetResults();
+ Assert.Contains("fo", values);
+ Assert.Equal("bar", values["fo"]);
+ Assert.Contains("ba", values);
+ Assert.Equal("", values["ba"]);
+ }
- IDictionary<string, StringValues> values = accumulator.GetResults();
- Assert.Contains("fo", values);
- Assert.Equal("bar", values["fo"]);
- Assert.Contains("ba", values);
- Assert.Equal("", values["ba"]);
- }
+ [Theory]
+ [MemberData(nameof(IncompleteFormValues))]
+ public void ParseFormWithIncompleteValueWhenIsFinalBlockSucceeds(ReadOnlySequence<byte> readOnlySequence)
+ {
+ KeyValueAccumulator accumulator = default;
- [Theory]
- [MemberData(nameof(IncompleteFormValues))]
- public void ParseFormWithIncompleteValueWhenIsFinalBlockSucceeds(ReadOnlySequence<byte> readOnlySequence)
+ var formReader = new FormPipeReader(null!)
{
- KeyValueAccumulator accumulator = default;
+ ValueLengthLimit = 3
+ };
- var formReader = new FormPipeReader(null!)
- {
- ValueLengthLimit = 3
- };
+ formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
+ Assert.True(readOnlySequence.IsEmpty);
- formReader.ParseFormValues(ref readOnlySequence, ref accumulator, isFinalBlock: true);
- Assert.True(readOnlySequence.IsEmpty);
-
- IDictionary<string, StringValues> values = accumulator.GetResults();
- Assert.Contains("fo", values);
- Assert.Equal("bar", values["fo"]);
- Assert.Contains("b", values);
- Assert.Equal("", values["b"]);
- }
+ IDictionary<string, StringValues> values = accumulator.GetResults();
+ Assert.Contains("fo", values);
+ Assert.Equal("bar", values["fo"]);
+ Assert.Contains("b", values);
+ Assert.Equal("", values["b"]);
+ }
- [Fact]
- public async Task ReadFormAsync_AccumulatesEmptyKeys()
- {
- var bodyPipe = await MakePipeReader("&&&");
+ [Fact]
+ public async Task ReadFormAsync_AccumulatesEmptyKeys()
+ {
+ var bodyPipe = await MakePipeReader("&&&");
- var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
+ var formCollection = await ReadFormAsync(new FormPipeReader(bodyPipe));
- Assert.Single(formCollection);
- }
+ Assert.Single(formCollection);
+ }
- public static TheoryData<ReadOnlySequence<byte>> IncompleteFormKeys =>
- new TheoryData<ReadOnlySequence<byte>>
- {
+ public static TheoryData<ReadOnlySequence<byte>> IncompleteFormKeys =>
+ new TheoryData<ReadOnlySequence<byte>>
+ {
{ ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("fo=bar&b"), Encoding.UTF8.GetBytes("a")) },
{ new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes("fo=bar&ba")) }
- };
+ };
- public static TheoryData<ReadOnlySequence<byte>> IncompleteFormValues =>
- new TheoryData<ReadOnlySequence<byte>>
- {
+ public static TheoryData<ReadOnlySequence<byte>> IncompleteFormValues =>
+ new TheoryData<ReadOnlySequence<byte>>
+ {
{ ReadOnlySequenceFactory.CreateSegments(Encoding.UTF8.GetBytes("fo=bar&b"), Encoding.UTF8.GetBytes("=")) },
{ new ReadOnlySequence<byte>(Encoding.UTF8.GetBytes("fo=bar&b=")) }
- };
+ };
- public static TheoryData<Encoding> Encodings =>
- new TheoryData<Encoding>
- {
+ public static TheoryData<Encoding> Encodings =>
+ new TheoryData<Encoding>
+ {
{ Encoding.UTF8 },
{ Encoding.UTF32 },
{ Encoding.ASCII },
{ Encoding.Unicode }
- };
+ };
- internal virtual Task<Dictionary<string, StringValues>> ReadFormAsync(FormPipeReader reader)
- {
- return reader.ReadFormAsync();
- }
+ internal virtual Task<Dictionary<string, StringValues>> ReadFormAsync(FormPipeReader reader)
+ {
+ return reader.ReadFormAsync();
+ }
- private static async Task<PipeReader> MakePipeReader(string text)
- {
- var formContent = Encoding.UTF8.GetBytes(text);
- Pipe bodyPipe = new Pipe();
+ private static async Task<PipeReader> MakePipeReader(string text)
+ {
+ var formContent = Encoding.UTF8.GetBytes(text);
+ Pipe bodyPipe = new Pipe();
- await bodyPipe.Writer.WriteAsync(formContent);
+ await bodyPipe.Writer.WriteAsync(formContent);
- // Complete the writer so the reader will complete after processing all data.
- bodyPipe.Writer.Complete();
- return bodyPipe.Reader;
- }
+ // Complete the writer so the reader will complete after processing all data.
+ bodyPipe.Writer.Complete();
+ return bodyPipe.Reader;
}
}
diff --git a/src/Http/WebUtilities/test/FormReaderAsyncTest.cs b/src/Http/WebUtilities/test/FormReaderAsyncTest.cs
index ddc6716cfc..5a204d2bbc 100644
--- a/src/Http/WebUtilities/test/FormReaderAsyncTest.cs
+++ b/src/Http/WebUtilities/test/FormReaderAsyncTest.cs
@@ -5,18 +5,17 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class FormReaderAsyncTest : FormReaderTests
{
- public class FormReaderAsyncTest : FormReaderTests
+ protected override async Task<Dictionary<string, StringValues>> ReadFormAsync(FormReader reader)
{
- protected override async Task<Dictionary<string, StringValues>> ReadFormAsync(FormReader reader)
- {
- return await reader.ReadFormAsync();
- }
+ return await reader.ReadFormAsync();
+ }
- protected override async Task<KeyValuePair<string, string>?> ReadPair(FormReader reader)
- {
- return await reader.ReadNextPairAsync();
- }
+ 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
index 116d15ae81..8fc4d07c72 100644
--- a/src/Http/WebUtilities/test/FormReaderTests.cs
+++ b/src/Http/WebUtilities/test/FormReaderTests.cs
@@ -8,223 +8,222 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class FormReaderTests
{
- public class FormReaderTests
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadFormAsync_EmptyKeyAtEndAllowed(bool bufferRequest)
{
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_EmptyKeyAtEndAllowed(bool bufferRequest)
- {
- var body = MakeStream(bufferRequest, "=bar");
+ var body = MakeStream(bufferRequest, "=bar");
- var formCollection = await ReadFormAsync(new FormReader(body));
+ var formCollection = await ReadFormAsync(new FormReader(body));
- Assert.Equal("bar", formCollection[""].ToString());
- }
+ Assert.Equal("bar", formCollection[""].ToString());
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_EmptyKeyWithAdditionalEntryAllowed(bool bufferRequest)
- {
- var body = MakeStream(bufferRequest, "=bar&baz=2");
+ [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));
+ var formCollection = await ReadFormAsync(new FormReader(body));
- Assert.Equal("bar", formCollection[""].ToString());
- Assert.Equal("2", formCollection["baz"].ToString());
- }
+ 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=");
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadFormAsync_EmptyValuedAtEndAllowed(bool bufferRequest)
+ {
+ var body = MakeStream(bufferRequest, "foo=");
- var formCollection = await ReadFormAsync(new FormReader(body));
+ var formCollection = await ReadFormAsync(new FormReader(body));
- Assert.Equal("", formCollection["foo"].ToString());
- }
+ Assert.Equal("", formCollection["foo"].ToString());
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadFormAsync_EmptyValuedWithAdditionalEntryAllowed(bool bufferRequest)
- {
- var body = MakeStream(bufferRequest, "foo=&baz=2");
+ [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));
+ var formCollection = await ReadFormAsync(new FormReader(body));
- Assert.Equal("", formCollection["foo"].ToString());
- Assert.Equal("2", formCollection["baz"].ToString());
- }
+ 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");
+ [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 });
+ 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);
- }
+ 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");
+ [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);
- }
+ 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");
+ [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);
- }
+ 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");
+ [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 });
+ 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);
- }
+ 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");
+ [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);
- }
+ 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");
+ [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 });
+ 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);
- }
+ 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");
+ [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);
- }
+ 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");
+ [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 reader = new FormReader(body);
- var pair = (KeyValuePair<string, string>)await ReadPair(reader);
+ var pair = (KeyValuePair<string, string>)await ReadPair(reader);
- Assert.Equal("foo", pair.Key);
- Assert.Equal("", pair.Value);
+ Assert.Equal("foo", pair.Key);
+ Assert.Equal("", pair.Value);
- pair = (KeyValuePair<string, string>)await ReadPair(reader);
+ pair = (KeyValuePair<string, string>)await ReadPair(reader);
- Assert.Equal("baz", pair.Key);
- Assert.Equal("2", pair.Value);
+ Assert.Equal("baz", pair.Key);
+ Assert.Equal("2", pair.Value);
- Assert.Null(await ReadPair(reader));
- }
+ Assert.Null(await ReadPair(reader));
+ }
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task ReadNextPair_ReturnsNullOnEmptyStream(bool bufferRequest)
- {
- var body = MakeStream(bufferRequest, "");
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadNextPair_ReturnsNullOnEmptyStream(bool bufferRequest)
+ {
+ var body = MakeStream(bufferRequest, "");
- var reader = new FormReader(body);
+ var reader = new FormReader(body);
- Assert.Null(await ReadPair(reader));
- }
+ 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);
+ // 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));
+ var form = await ReadFormAsync(new FormReader(body));
- Assert.Equal(expectedValue, form[key]);
- }
+ Assert.Equal(expectedValue, form[key]);
+ }
- protected virtual Task<Dictionary<string, StringValues>> ReadFormAsync(FormReader reader)
- {
- return Task.FromResult(reader.ReadForm());
- }
+ 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());
- }
+ protected virtual Task<KeyValuePair<string, string>?> ReadPair(FormReader reader)
+ {
+ return Task.FromResult(reader.ReadNextPair());
+ }
- private static Stream MakeStream(bool bufferRequest, string text)
+ private static Stream MakeStream(bool bufferRequest, string text)
+ {
+ var formContent = Encoding.UTF8.GetBytes(text);
+ Stream body = new MemoryStream(formContent);
+ if (!bufferRequest)
{
- var formContent = Encoding.UTF8.GetBytes(text);
- Stream body = new MemoryStream(formContent);
- if (!bufferRequest)
- {
- body = new NonSeekableReadStream(body);
- }
- return body;
+ 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
index 1f970bce91..b31d7f339d 100644
--- a/src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs
+++ b/src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Moq;
using System;
using System.Buffers;
using System.Collections.Generic;
@@ -9,15 +8,16 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class HttpRequestStreamReaderTest
{
- public class HttpRequestStreamReaderTest
+ private static readonly char[] CharData = new char[]
{
- private static readonly char[] CharData = new char[]
- {
char.MinValue,
char.MaxValue,
'\t',
@@ -43,535 +43,534 @@ namespace Microsoft.AspNetCore.WebUtilities
'\n',
'K',
'\u00E6',
- };
+ };
- [Fact]
- public static async Task ReadToEndAsync()
- {
- // Arrange
- var reader = new HttpRequestStreamReader(GetLargeStream(), Encoding.UTF8);
+ [Fact]
+ public static async Task ReadToEndAsync()
+ {
+ // Arrange
+ var reader = new HttpRequestStreamReader(GetLargeStream(), Encoding.UTF8);
- var result = await reader.ReadToEndAsync();
+ var result = await reader.ReadToEndAsync();
- Assert.Equal(5000, result.Length);
- }
+ Assert.Equal(5000, result.Length);
+ }
- [Fact]
- public static async Task ReadToEndAsync_Reads_Asynchronously()
- {
- // Arrange
- var stream = new AsyncOnlyStreamWrapper(GetLargeStream());
- var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
- var streamReader = new StreamReader(GetLargeStream());
- string expected = await streamReader.ReadToEndAsync();
+ [Fact]
+ public static async Task ReadToEndAsync_Reads_Asynchronously()
+ {
+ // Arrange
+ var stream = new AsyncOnlyStreamWrapper(GetLargeStream());
+ var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
+ var streamReader = new StreamReader(GetLargeStream());
+ string expected = await streamReader.ReadToEndAsync();
- // Act
- var actual = await reader.ReadToEndAsync();
+ // Act
+ var actual = await reader.ReadToEndAsync();
- // Assert
- Assert.Equal(expected, actual);
- }
+ // Assert
+ Assert.Equal(expected, actual);
+ }
- [Fact]
- public static void TestRead()
- {
- // Arrange
- var reader = CreateReader();
+ [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);
- }
+ // 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();
+ [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);
+ // Act & Assert
+ for (var i = 0; i < CharData.Length; i++)
+ {
+ var peek = reader.Peek();
+ Assert.Equal((int)CharData[i], peek);
- reader.Read();
- }
+ 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);
+ [Fact]
+ public static void EmptyStream()
+ {
+ // Arrange
+ var reader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8);
+ var buffer = new char[10];
- // Assert
- Assert.Equal(0, read);
- }
+ // Act
+ var read = reader.Read(buffer, 0, 1);
- [Fact]
- public static void Read_ReadAllCharactersAtOnce()
- {
- // Arrange
- var reader = CreateReader();
- var chars = new char[CharData.Length];
+ // Assert
+ Assert.Equal(0, read);
+ }
- // Act
- var read = reader.Read(chars, 0, chars.Length);
+ [Fact]
+ public static void Read_ReadAllCharactersAtOnce()
+ {
+ // Arrange
+ var reader = CreateReader();
+ var chars = new char[CharData.Length];
- // Assert
- Assert.Equal(chars.Length, read);
- for (var i = 0; i < CharData.Length; i++)
- {
- Assert.Equal(CharData[i], chars[i]);
- }
- }
+ // Act
+ var read = reader.Read(chars, 0, chars.Length);
- [Fact]
- public static async Task ReadAsync_ReadInTwoChunks()
+ // Assert
+ Assert.Equal(chars.Length, read);
+ for (var i = 0; i < CharData.Length; i++)
{
- // Arrange
- var reader = CreateReader();
- var chars = new char[CharData.Length];
+ Assert.Equal(CharData[i], chars[i]);
+ }
+ }
- // Act
- var read = await reader.ReadAsync(chars, 4, 3);
+ [Fact]
+ public static async Task ReadAsync_ReadInTwoChunks()
+ {
+ // Arrange
+ var reader = CreateReader();
+ var chars = new char[CharData.Length];
- // Assert
- Assert.Equal(3, read);
- for (var i = 0; i < 3; i++)
- {
- Assert.Equal(CharData[i], chars[i + 4]);
- }
- }
+ // Act
+ var read = await reader.ReadAsync(chars, 4, 3);
- [Theory]
- [MemberData(nameof(ReadLineData))]
- public static async Task ReadLine_ReadMultipleLines(Func<HttpRequestStreamReader, Task<string>> action)
+ // Assert
+ Assert.Equal(3, read);
+ for (var i = 0; i < 3; i++)
{
- // Arrange
- var reader = CreateReader();
- var valueString = new string(CharData);
+ Assert.Equal(CharData[i], chars[i + 4]);
+ }
+ }
- // Act & Assert
- var data = await action(reader);
- Assert.Equal(valueString.Substring(0, valueString.IndexOf('\r')), data);
+ [Theory]
+ [MemberData(nameof(ReadLineData))]
+ public static async Task ReadLine_ReadMultipleLines(Func<HttpRequestStreamReader, Task<string>> action)
+ {
+ // Arrange
+ var reader = CreateReader();
+ var valueString = new string(CharData);
- data = await action(reader);
- Assert.Equal(valueString.Substring(valueString.IndexOf('\r') + 1, 3), data);
+ // Act & Assert
+ var data = await action(reader);
+ Assert.Equal(valueString.Substring(0, valueString.IndexOf('\r')), data);
- data = await action(reader);
- Assert.Equal(valueString.Substring(valueString.IndexOf('\n') + 1, 2), data);
+ data = await action(reader);
+ Assert.Equal(valueString.Substring(valueString.IndexOf('\r') + 1, 3), data);
- data = await action(reader);
- Assert.Equal((valueString.Substring(valueString.LastIndexOf('\n') + 1)), data);
- }
+ data = await action(reader);
+ Assert.Equal(valueString.Substring(valueString.IndexOf('\n') + 1, 2), data);
- [Theory]
- [MemberData(nameof(ReadLineData))]
- public static async Task ReadLine_ReadWithNoNewlines(Func<HttpRequestStreamReader, Task<string>> action)
- {
- // Arrange
- var reader = CreateReader();
- var valueString = new string(CharData);
- var temp = new char[10];
+ data = await action(reader);
+ Assert.Equal((valueString.Substring(valueString.LastIndexOf('\n') + 1)), data);
+ }
- // Act
- reader.Read(temp, 0, 1);
- var data = await action(reader);
+ [Theory]
+ [MemberData(nameof(ReadLineData))]
+ public static async Task ReadLine_ReadWithNoNewlines(Func<HttpRequestStreamReader, Task<string>> action)
+ {
+ // Arrange
+ var reader = CreateReader();
+ var valueString = new string(CharData);
+ var temp = new char[10];
- // Assert
- Assert.Equal(valueString.Substring(1, valueString.IndexOf('\r') - 1), data);
- }
+ // Act
+ reader.Read(temp, 0, 1);
+ var data = await action(reader);
- [Theory]
- [MemberData(nameof(ReadLineData))]
- public static async Task ReadLine_MultipleContinuousLines(Func<HttpRequestStreamReader, Task<string>> action)
- {
- // Arrange
- var stream = new MemoryStream();
- var writer = new StreamWriter(stream);
- writer.Write("\n\n\r\r\n\r");
- writer.Flush();
- stream.Position = 0;
+ // Assert
+ Assert.Equal(valueString.Substring(1, valueString.IndexOf('\r') - 1), data);
+ }
- var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
+ [Theory]
+ [MemberData(nameof(ReadLineData))]
+ public static async Task ReadLine_MultipleContinuousLines(Func<HttpRequestStreamReader, Task<string>> action)
+ {
+ // Arrange
+ var stream = new MemoryStream();
+ var writer = new StreamWriter(stream);
+ writer.Write("\n\n\r\r\n\r");
+ writer.Flush();
+ stream.Position = 0;
- // Act & Assert
- for (var i = 0; i < 5; i++)
- {
- var data = await action(reader);
- Assert.Equal(string.Empty, data);
- }
+ var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
- var eof = await action(reader);
- Assert.Null(eof);
+ // Act & Assert
+ for (var i = 0; i < 5; i++)
+ {
+ var data = await action(reader);
+ Assert.Equal(string.Empty, data);
}
- [Theory]
- [MemberData(nameof(ReadLineData))]
- public static async Task ReadLine_CarriageReturnAndLineFeedAcrossBufferBundaries(Func<HttpRequestStreamReader, Task<string>> action)
- {
- // Arrange
- var stream = new MemoryStream();
- var writer = new StreamWriter(stream);
- writer.Write("123456789\r\nfoo");
- writer.Flush();
- stream.Position = 0;
+ var eof = await action(reader);
+ Assert.Null(eof);
+ }
- var reader = new HttpRequestStreamReader(stream, Encoding.UTF8, 10);
+ [Theory]
+ [MemberData(nameof(ReadLineData))]
+ public static async Task ReadLine_CarriageReturnAndLineFeedAcrossBufferBundaries(Func<HttpRequestStreamReader, Task<string>> action)
+ {
+ // Arrange
+ var stream = new MemoryStream();
+ var writer = new StreamWriter(stream);
+ writer.Write("123456789\r\nfoo");
+ writer.Flush();
+ stream.Position = 0;
- // Act & Assert
- var data = await action(reader);
- Assert.Equal("123456789", data);
+ var reader = new HttpRequestStreamReader(stream, Encoding.UTF8, 10);
- data = await action(reader);
- Assert.Equal("foo", data);
+ // Act & Assert
+ var data = await action(reader);
+ Assert.Equal("123456789", data);
- var eof = await action(reader);
- Assert.Null(eof);
- }
+ data = await action(reader);
+ Assert.Equal("foo", data);
- [Theory]
- [MemberData(nameof(ReadLineData))]
- public static async Task ReadLine_EOF(Func<HttpRequestStreamReader, Task<string>> action)
- {
- // Arrange
- var stream = new MemoryStream();
- var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
+ var eof = await action(reader);
+ Assert.Null(eof);
+ }
- // Act & Assert
- var eof = await action(reader);
- Assert.Null(eof);
- }
+ [Theory]
+ [MemberData(nameof(ReadLineData))]
+ public static async Task ReadLine_EOF(Func<HttpRequestStreamReader, Task<string>> action)
+ {
+ // Arrange
+ var stream = new MemoryStream();
+ var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
- [Theory]
- [MemberData(nameof(ReadLineData))]
- public static async Task ReadLine_NewLineOnly(Func<HttpRequestStreamReader, Task<string>> action)
- {
- // Arrange
- var stream = new MemoryStream();
- var writer = new StreamWriter(stream);
- writer.Write("\r\n");
- writer.Flush();
- stream.Position = 0;
-
- var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
-
- // Act & Assert
- var empty = await action(reader);
- Assert.Equal(string.Empty, empty);
- }
+ // Act & Assert
+ var eof = await action(reader);
+ Assert.Null(eof);
+ }
- [Fact]
- public static void Read_Span_ReadAllCharactersAtOnce()
- {
- // Arrange
- var reader = CreateReader();
- var chars = new char[CharData.Length];
- var span = new Span<char>(chars);
+ [Theory]
+ [MemberData(nameof(ReadLineData))]
+ public static async Task ReadLine_NewLineOnly(Func<HttpRequestStreamReader, Task<string>> action)
+ {
+ // Arrange
+ var stream = new MemoryStream();
+ var writer = new StreamWriter(stream);
+ writer.Write("\r\n");
+ writer.Flush();
+ stream.Position = 0;
+
+ var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
+
+ // Act & Assert
+ var empty = await action(reader);
+ Assert.Equal(string.Empty, empty);
+ }
- // Act
- var read = reader.Read(span);
+ [Fact]
+ public static void Read_Span_ReadAllCharactersAtOnce()
+ {
+ // Arrange
+ var reader = CreateReader();
+ var chars = new char[CharData.Length];
+ var span = new Span<char>(chars);
- // Assert
- Assert.Equal(chars.Length, read);
- for (var i = 0; i < CharData.Length; i++)
- {
- Assert.Equal(CharData[i], chars[i]);
- }
- }
+ // Act
+ var read = reader.Read(span);
- [Fact]
- public static void Read_Span_WithMoreDataThanInternalBufferSize()
+ // Assert
+ Assert.Equal(chars.Length, read);
+ for (var i = 0; i < CharData.Length; i++)
{
- // Arrange
- var reader = CreateReader(10);
- var chars = new char[CharData.Length];
- var span = new Span<char>(chars);
+ Assert.Equal(CharData[i], chars[i]);
+ }
+ }
- // Act
- var read = reader.Read(span);
+ [Fact]
+ public static void Read_Span_WithMoreDataThanInternalBufferSize()
+ {
+ // Arrange
+ var reader = CreateReader(10);
+ var chars = new char[CharData.Length];
+ var span = new Span<char>(chars);
- // Assert
- Assert.Equal(chars.Length, read);
- for (var i = 0; i < CharData.Length; i++)
- {
- Assert.Equal(CharData[i], chars[i]);
- }
- }
+ // Act
+ var read = reader.Read(span);
- [Fact]
- public static async Task ReadAsync_Memory_ReadAllCharactersAtOnce()
+ // Assert
+ Assert.Equal(chars.Length, read);
+ for (var i = 0; i < CharData.Length; i++)
{
- // Arrange
- var reader = CreateReader();
- var chars = new char[CharData.Length];
- var memory = new Memory<char>(chars);
+ Assert.Equal(CharData[i], chars[i]);
+ }
+ }
- // Act
- var read = await reader.ReadAsync(memory);
+ [Fact]
+ public static async Task ReadAsync_Memory_ReadAllCharactersAtOnce()
+ {
+ // Arrange
+ var reader = CreateReader();
+ var chars = new char[CharData.Length];
+ var memory = new Memory<char>(chars);
- // Assert
- Assert.Equal(chars.Length, read);
- for (var i = 0; i < CharData.Length; i++)
- {
- Assert.Equal(CharData[i], chars[i]);
- }
- }
+ // Act
+ var read = await reader.ReadAsync(memory);
- [Fact]
- public static async Task ReadAsync_Memory_WithMoreDataThanInternalBufferSize()
+ // Assert
+ Assert.Equal(chars.Length, read);
+ for (var i = 0; i < CharData.Length; i++)
{
- // Arrange
- var reader = CreateReader(10);
- var chars = new char[CharData.Length];
- var memory = new Memory<char>(chars);
+ Assert.Equal(CharData[i], chars[i]);
+ }
+ }
- // Act
- var read = await reader.ReadAsync(memory);
+ [Fact]
+ public static async Task ReadAsync_Memory_WithMoreDataThanInternalBufferSize()
+ {
+ // Arrange
+ var reader = CreateReader(10);
+ var chars = new char[CharData.Length];
+ var memory = new Memory<char>(chars);
- // Assert
- Assert.Equal(chars.Length, read);
- for (var i = 0; i < CharData.Length; i++)
- {
- Assert.Equal(CharData[i], chars[i]);
- }
- }
+ // Act
+ var read = await reader.ReadAsync(memory);
- [Theory]
- [MemberData(nameof(HttpRequestNullData))]
- public static void NullInputsInConstructor_ExpectArgumentNullException(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, ArrayPool<char> charPool)
+ // Assert
+ Assert.Equal(chars.Length, read);
+ for (var i = 0; i < CharData.Length; i++)
{
- Assert.Throws<ArgumentNullException>(() =>
- {
- var httpRequestStreamReader = new HttpRequestStreamReader(stream, encoding, 1, bytePool, charPool);
- });
+ Assert.Equal(CharData[i], chars[i]);
}
+ }
- [Theory]
- [InlineData(0)]
- [InlineData(-1)]
- public static void NegativeOrZeroBufferSize_ExpectArgumentOutOfRangeException(int size)
+ [Theory]
+ [MemberData(nameof(HttpRequestNullData))]
+ public static void NullInputsInConstructor_ExpectArgumentNullException(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, ArrayPool<char> charPool)
+ {
+ Assert.Throws<ArgumentNullException>(() =>
{
- Assert.Throws<ArgumentOutOfRangeException>(() =>
- {
- var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, size, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
- });
- }
+ var httpRequestStreamReader = new HttpRequestStreamReader(stream, encoding, 1, bytePool, charPool);
+ });
+ }
- [Fact]
- public static void StreamCannotRead_ExpectArgumentException()
+ [Theory]
+ [InlineData(0)]
+ [InlineData(-1)]
+ public static void NegativeOrZeroBufferSize_ExpectArgumentOutOfRangeException(int size)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() =>
{
- 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);
- });
- }
+ var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, size, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+ });
+ }
- [Theory]
- [MemberData(nameof(HttpRequestDisposeData))]
- public static void StreamDisposed_ExpectedObjectDisposedException(Action<HttpRequestStreamReader> action)
+ [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(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
- httpRequestStreamReader.Dispose();
+ var httpRequestStreamReader = new HttpRequestStreamReader(mockStream.Object, Encoding.UTF8, 1, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+ });
+ }
- Assert.Throws<ObjectDisposedException>(() =>
- {
- action(httpRequestStreamReader);
- });
- }
+ [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();
- [Theory]
- [MemberData(nameof(HttpRequestDisposeDataAsync))]
- public static async Task StreamDisposed_ExpectObjectDisposedExceptionAsync(Func<HttpRequestStreamReader, Task> action)
+ Assert.Throws<ObjectDisposedException>(() =>
{
- var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
- httpRequestStreamReader.Dispose();
-
- await Assert.ThrowsAsync<ObjectDisposedException>(() => action(httpRequestStreamReader));
- }
+ action(httpRequestStreamReader);
+ });
+ }
- private static HttpRequestStreamReader CreateReader()
- {
- MemoryStream stream = CreateStream();
- return new HttpRequestStreamReader(stream, Encoding.UTF8);
- }
+ [Theory]
+ [MemberData(nameof(HttpRequestDisposeDataAsync))]
+ public static async Task StreamDisposed_ExpectObjectDisposedExceptionAsync(Func<HttpRequestStreamReader, Task> action)
+ {
+ var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+ httpRequestStreamReader.Dispose();
- private static HttpRequestStreamReader CreateReader(int bufferSize)
- {
- MemoryStream stream = CreateStream();
- return new HttpRequestStreamReader(stream, Encoding.UTF8, bufferSize);
- }
+ await Assert.ThrowsAsync<ObjectDisposedException>(() => action(httpRequestStreamReader));
+ }
- private static MemoryStream CreateStream()
- {
- var stream = new MemoryStream();
- var writer = new StreamWriter(stream);
- writer.Write(CharData);
- writer.Flush();
- stream.Position = 0;
- return stream;
- }
+ private static HttpRequestStreamReader CreateReader()
+ {
+ MemoryStream stream = CreateStream();
+ 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 HttpRequestStreamReader CreateReader(int bufferSize)
+ {
+ MemoryStream stream = CreateStream();
+ return new HttpRequestStreamReader(stream, Encoding.UTF8, bufferSize);
+ }
- private static MemoryStream GetLargeStream()
- {
- var testData = new byte[] { 72, 69, 76, 76, 79 };
- // System.Collections.Generic.
+ private static MemoryStream CreateStream()
+ {
+ var stream = new MemoryStream();
+ var writer = new StreamWriter(stream);
+ writer.Write(CharData);
+ writer.Flush();
+ stream.Position = 0;
+ return stream;
+ }
- var data = new List<byte>();
- for (var i = 0; i < 1000; i++)
- {
- data.AddRange(testData);
- }
+ private static MemoryStream GetSmallStream()
+ {
+ var testData = new byte[] { 72, 69, 76, 76, 79 };
+ return new MemoryStream(testData);
+ }
- return new MemoryStream(data.ToArray());
- }
+ private static MemoryStream GetLargeStream()
+ {
+ var testData = new byte[] { 72, 69, 76, 76, 79 };
+ // System.Collections.Generic.
- public static IEnumerable<object?[]> HttpRequestNullData()
+ var data = new List<byte>();
+ for (var i = 0; i < 1000; i++)
{
- 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 };
+ data.AddRange(testData);
}
- public static IEnumerable<object[]> HttpRequestDisposeData()
- {
- yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
+ 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) =>
+ 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) =>
+ yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
{
var res = httpRequestStreamReader.Read(new Span<char>(new char[10], 0, 1));
})};
- yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
+ yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
{
var res = httpRequestStreamReader.Peek();
})};
- }
+ }
- public static IEnumerable<object[]> HttpRequestDisposeDataAsync()
- {
- yield return new object[] { new Func<HttpRequestStreamReader, Task>(async (httpRequestStreamReader) =>
+ public static IEnumerable<object[]> HttpRequestDisposeDataAsync()
+ {
+ yield return new object[] { new Func<HttpRequestStreamReader, Task>(async (httpRequestStreamReader) =>
{
await httpRequestStreamReader.ReadAsync(new char[10], 0, 1);
})};
- yield return new object[] { new Func<HttpRequestStreamReader, Task>(async (httpRequestStreamReader) =>
+ yield return new object[] { new Func<HttpRequestStreamReader, Task>(async (httpRequestStreamReader) =>
{
await httpRequestStreamReader.ReadAsync(new Memory<char>(new char[10], 0, 1));
})};
- }
+ }
- public static IEnumerable<object[]> ReadLineData()
- {
- yield return new object[] { new Func<HttpRequestStreamReader, Task<string?>>((httpRequestStreamReader) =>
+ public static IEnumerable<object[]> ReadLineData()
+ {
+ yield return new object[] { new Func<HttpRequestStreamReader, Task<string?>>((httpRequestStreamReader) =>
Task.FromResult(httpRequestStreamReader.ReadLine())
)};
- yield return new object[] { new Func<HttpRequestStreamReader, Task<string?>>((httpRequestStreamReader) =>
+ yield return new object[] { new Func<HttpRequestStreamReader, Task<string?>>((httpRequestStreamReader) =>
httpRequestStreamReader.ReadLineAsync()
)};
- }
+ }
- private class AsyncOnlyStreamWrapper : Stream
- {
- private readonly Stream _inner;
+ private class AsyncOnlyStreamWrapper : Stream
+ {
+ private readonly Stream _inner;
- public AsyncOnlyStreamWrapper(Stream inner)
- {
- _inner = inner;
- }
+ public AsyncOnlyStreamWrapper(Stream inner)
+ {
+ _inner = inner;
+ }
- public override bool CanRead => _inner.CanRead;
+ public override bool CanRead => _inner.CanRead;
- public override bool CanSeek => _inner.CanSeek;
+ public override bool CanSeek => _inner.CanSeek;
- public override bool CanWrite => _inner.CanWrite;
+ public override bool CanWrite => _inner.CanWrite;
- public override long Length => _inner.Length;
+ public override long Length => _inner.Length;
- public override long Position
- {
- get => _inner.Position;
- set => _inner.Position = value;
- }
+ public override long Position
+ {
+ get => _inner.Position;
+ set => _inner.Position = value;
+ }
- public override void Flush()
- {
- throw SyncOperationForbiddenException();
- }
+ public override void Flush()
+ {
+ throw SyncOperationForbiddenException();
+ }
- public override Task FlushAsync(CancellationToken cancellationToken)
- {
- return _inner.FlushAsync(cancellationToken);
- }
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ return _inner.FlushAsync(cancellationToken);
+ }
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw SyncOperationForbiddenException();
- }
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw SyncOperationForbiddenException();
+ }
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return _inner.ReadAsync(buffer, offset, count, cancellationToken);
- }
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return _inner.ReadAsync(buffer, offset, count, cancellationToken);
+ }
- public override long Seek(long offset, SeekOrigin origin)
- {
- return _inner.Seek(offset, origin);
- }
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return _inner.Seek(offset, origin);
+ }
- public override void SetLength(long value)
- {
- _inner.SetLength(value);
- }
+ public override void SetLength(long value)
+ {
+ _inner.SetLength(value);
+ }
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw SyncOperationForbiddenException();
- }
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw SyncOperationForbiddenException();
+ }
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return _inner.WriteAsync(buffer, offset, count, cancellationToken);
- }
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ return _inner.WriteAsync(buffer, offset, count, cancellationToken);
+ }
- protected override void Dispose(bool disposing)
- {
- _inner.Dispose();
- }
+ protected override void Dispose(bool disposing)
+ {
+ _inner.Dispose();
+ }
- public override ValueTask DisposeAsync()
- {
- return _inner.DisposeAsync();
- }
+ public override ValueTask DisposeAsync()
+ {
+ return _inner.DisposeAsync();
+ }
- private Exception SyncOperationForbiddenException()
- {
- return new InvalidOperationException("The stream cannot be accessed synchronously");
- }
+ private Exception SyncOperationForbiddenException()
+ {
+ return new InvalidOperationException("The stream cannot be accessed synchronously");
}
}
}
diff --git a/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs b/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs
index 84e3fcdbea..3115cf1ace 100644
--- a/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs
+++ b/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Moq;
using System;
using System.Buffers;
using System.Collections.Generic;
@@ -9,739 +8,739 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class HttpResponseStreamWriterTest
{
- public class HttpResponseStreamWriterTest
+ private const int DefaultCharacterChunkSize = HttpResponseStreamWriter.DefaultBufferSize;
+
+ [Fact]
+ public async Task DoesNotWriteBOM()
{
- private const int DefaultCharacterChunkSize = HttpResponseStreamWriter.DefaultBufferSize;
+ // 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
- [Fact]
- public async Task DoesNotWriteBOM()
+ // Act
+ using (writer)
{
- // 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());
+ await writer.WriteAsync("abcd");
}
- [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(expectedData, memoryStream.ToArray());
+ }
- // Assert
- Assert.Equal(0, stream.DisposeCallCount);
- }
+ [Fact]
+ public async Task DoesNotFlush_UnderlyingStream_OnDisposingWriter()
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- [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
+ await writer.WriteAsync("Hello");
+ writer.Dispose();
- // Act
- writer.Dispose();
+ // Assert
+ Assert.Equal(0, stream.FlushCallCount);
+ Assert.Equal(0, stream.FlushAsyncCallCount);
+ }
- // Assert
- Assert.Equal(0, stream.FlushCallCount);
- Assert.Equal(0, stream.FlushAsyncCallCount);
- Assert.Equal(byteLength, stream.Length);
- }
+ [Fact]
+ public async Task DoesNotDispose_UnderlyingStream_OnDisposingWriter()
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- [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
+ await writer.WriteAsync("Hello world");
+ writer.Dispose();
- // Act
- writer.Dispose();
+ // Assert
+ Assert.Equal(0, stream.DisposeCallCount);
+ }
- // 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_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);
+ }
- [Fact]
- public void NoDataWritten_Flush_DoesNotFlushUnderlyingStream()
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ [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);
+ }
- // Act
- writer.Flush();
+ [Fact]
+ public void NoDataWritten_Flush_DoesNotFlushUnderlyingStream()
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- // Assert
- Assert.Equal(0, stream.FlushCallCount);
- Assert.Equal(0, stream.Length);
- }
+ // Act
+ writer.Flush();
- [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));
+ // Assert
+ Assert.Equal(0, stream.FlushCallCount);
+ Assert.Equal(0, stream.Length);
+ }
- var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
+ [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));
- // Act
- writer.Flush();
+ var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
- // Assert
- Assert.Equal(0, stream.FlushCallCount);
- Assert.Equal(expectedWriteCount, stream.WriteCallCount);
- Assert.Equal(byteLength, stream.Length);
- }
+ // Act
+ writer.Flush();
- [Fact]
- public async Task NoDataWritten_FlushAsync_DoesNotFlushUnderlyingStream()
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ // Assert
+ Assert.Equal(0, stream.FlushCallCount);
+ Assert.Equal(expectedWriteCount, stream.WriteCallCount);
+ Assert.Equal(byteLength, stream.Length);
+ }
- // Act
- await writer.FlushAsync();
+ [Fact]
+ public async Task NoDataWritten_FlushAsync_DoesNotFlushUnderlyingStream()
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- // Assert
- Assert.Equal(0, stream.FlushAsyncCallCount);
- Assert.Equal(0, stream.Length);
- }
+ // Act
+ await writer.FlushAsync();
- [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));
+ // Assert
+ Assert.Equal(0, stream.FlushAsyncCallCount);
+ Assert.Equal(0, stream.Length);
+ }
- var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
+ [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));
- // Act
- await writer.FlushAsync();
+ var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
- // Assert
- Assert.Equal(0, stream.FlushAsyncCallCount);
- Assert.Equal(expectedWriteCount, stream.WriteAsyncCallCount);
- Assert.Equal(byteLength, stream.Length);
- }
+ // Act
+ await writer.FlushAsync();
- [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);
+ // Assert
+ Assert.Equal(0, stream.FlushAsyncCallCount);
+ Assert.Equal(expectedWriteCount, stream.WriteAsyncCallCount);
+ Assert.Equal(byteLength, stream.Length);
+ }
- await writer.WriteAsync(new string('a', byteLength));
- await Assert.ThrowsAsync<IOException>(() => writer.FlushAsync());
+ [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);
+ }
- // 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);
- [Theory]
- [InlineData(1023)]
- [InlineData(1024)]
- [InlineData(1050)]
- [InlineData(2048)]
- public void WriteChar_WritesToStream(int byteLength)
+ // Act
+ using (writer)
{
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
-
- // Act
- using (writer)
+ for (var i = 0; i < byteLength; i++)
{
- for (var i = 0; i < byteLength; i++)
- {
- writer.Write('a');
- }
+ 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);
+ // Assert
+ Assert.Equal(byteLength, stream.Length);
+ }
- // Act
- using (writer)
- {
- writer.Write((new string('a', byteLength)).ToCharArray());
- }
+ [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);
- // Assert
- Assert.Equal(byteLength, stream.Length);
+ // Act
+ using (writer)
+ {
+ writer.Write((new string('a', byteLength)).ToCharArray());
}
- [Theory]
- [InlineData(1023)]
- [InlineData(1024)]
- [InlineData(1050)]
- [InlineData(2048)]
- [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)]
- public void WriteReadOnlySpanChar_WritesToStream(int byteLength)
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ // Assert
+ Assert.Equal(byteLength, stream.Length);
+ }
- // Act
- using (writer)
- {
- var array = new string('a', byteLength).ToCharArray();
- var span = new ReadOnlySpan<char>(array);
- writer.Write(span);
- }
+ [Theory]
+ [InlineData(1023)]
+ [InlineData(1024)]
+ [InlineData(1050)]
+ [InlineData(2048)]
+ [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)]
+ public void WriteReadOnlySpanChar_WritesToStream(int byteLength)
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- // Assert
- Assert.Equal(byteLength, stream.Length);
+ // Act
+ using (writer)
+ {
+ var array = new string('a', byteLength).ToCharArray();
+ var span = new ReadOnlySpan<char>(array);
+ writer.Write(span);
}
- [Theory]
- [InlineData(1022, "\n")]
- [InlineData(1023, "\n")]
- [InlineData(1024, "\n")]
- [InlineData(1050, "\n")]
- [InlineData(2047, "\n")]
- [InlineData(2048, "\n")]
- [InlineData(1021, "\r\n")]
- [InlineData(1022, "\r\n")]
- [InlineData(1023, "\r\n")]
- [InlineData(1024, "\r\n")]
- [InlineData(1050, "\r\n")]
- [InlineData(2046, "\r\n")]
- [InlineData(2048, "\r\n")]
- [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, "\r\n")]
- public void WriteLineReadOnlySpanChar_WritesToStream(int byteLength, string newLine)
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ // Assert
+ Assert.Equal(byteLength, stream.Length);
+ }
- writer.NewLine = newLine;
- // Act
- using (writer)
- {
- var array = new string('a', byteLength).ToCharArray();
- var span = new ReadOnlySpan<char>(array);
- writer.WriteLine(span);
- }
+ [Theory]
+ [InlineData(1022, "\n")]
+ [InlineData(1023, "\n")]
+ [InlineData(1024, "\n")]
+ [InlineData(1050, "\n")]
+ [InlineData(2047, "\n")]
+ [InlineData(2048, "\n")]
+ [InlineData(1021, "\r\n")]
+ [InlineData(1022, "\r\n")]
+ [InlineData(1023, "\r\n")]
+ [InlineData(1024, "\r\n")]
+ [InlineData(1050, "\r\n")]
+ [InlineData(2046, "\r\n")]
+ [InlineData(2048, "\r\n")]
+ [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, "\r\n")]
+ public void WriteLineReadOnlySpanChar_WritesToStream(int byteLength, string newLine)
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- // Assert
- Assert.Equal(byteLength + newLine.Length, stream.Length);
+ writer.NewLine = newLine;
+ // Act
+ using (writer)
+ {
+ var array = new string('a', byteLength).ToCharArray();
+ var span = new ReadOnlySpan<char>(array);
+ writer.WriteLine(span);
}
- [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);
+ // Assert
+ Assert.Equal(byteLength + newLine.Length, stream.Length);
+ }
- // Act
- using (writer)
+ [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++)
{
- for (var i = 0; i < byteLength; i++)
- {
- await writer.WriteAsync('a');
- }
+ 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);
+ // Assert
+ Assert.Equal(byteLength, stream.Length);
+ }
- // Act
- using (writer)
- {
- await writer.WriteAsync((new string('a', byteLength)).ToCharArray());
- }
+ [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);
- // Assert
- Assert.Equal(byteLength, stream.Length);
+ // Act
+ using (writer)
+ {
+ await writer.WriteAsync((new string('a', byteLength)).ToCharArray());
}
- [Theory]
- [InlineData(0)]
- [InlineData(1023)]
- [InlineData(1024)]
- [InlineData(1050)]
- [InlineData(2048)]
- [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)]
- public async Task WriteReadOnlyMemoryAsync_WritesToStream(int byteLength)
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ // Assert
+ Assert.Equal(byteLength, stream.Length);
+ }
- // Act
- using (writer)
- {
- var array = new string('a', byteLength).ToCharArray();
- var memory = new ReadOnlyMemory<char>(array);
- await writer.WriteAsync(memory);
- }
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1023)]
+ [InlineData(1024)]
+ [InlineData(1050)]
+ [InlineData(2048)]
+ [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)]
+ public async Task WriteReadOnlyMemoryAsync_WritesToStream(int byteLength)
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- // Assert
- Assert.Equal(byteLength, stream.Length);
+ // Act
+ using (writer)
+ {
+ var array = new string('a', byteLength).ToCharArray();
+ var memory = new ReadOnlyMemory<char>(array);
+ await writer.WriteAsync(memory);
}
- [Fact]
- public async Task WriteReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
- {
- // Arrange
- var stream = new TestMemoryStream();
- using var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
- var cancellationToken = new CancellationToken(true);
+ // Assert
+ Assert.Equal(byteLength, stream.Length);
+ }
- // Act
- await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteAsync(memory, cancellationToken));
+ [Fact]
+ public async Task WriteReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ using var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
+ var cancellationToken = new CancellationToken(true);
- // Assert
- Assert.Equal(0, stream.Length);
- }
+ // Act
+ await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteAsync(memory, cancellationToken));
- [Theory]
- [InlineData(0, 1)]
- [InlineData(1022, 1)]
- [InlineData(1023, 1)]
- [InlineData(1024, 1)]
- [InlineData(1050, 1)]
- [InlineData(2047, 1)]
- [InlineData(2048, 1)]
- [InlineData(1021, 2)]
- [InlineData(1022, 2)]
- [InlineData(1023, 2)]
- [InlineData(1024, 2)]
- [InlineData(1024, 1023)]
- [InlineData(1024, 1024)]
- [InlineData(1024, 1050)]
- [InlineData(1050, 2)]
- [InlineData(2046, 2)]
- [InlineData(2048, 2)]
- [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, 1)]
- [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, 2)]
- [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, HttpResponseStreamWriter.DefaultBufferSize)]
- public async Task WriteLineReadOnlyMemoryAsync_WritesToStream(int byteLength, int newLineLength)
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- writer.NewLine = new string('\n', newLineLength);
+ // Assert
+ Assert.Equal(0, stream.Length);
+ }
- // Act
- using (writer)
- {
- var array = new string('a', byteLength).ToCharArray();
- var memory = new ReadOnlyMemory<char>(array);
- await writer.WriteLineAsync(memory);
- }
+ [Theory]
+ [InlineData(0, 1)]
+ [InlineData(1022, 1)]
+ [InlineData(1023, 1)]
+ [InlineData(1024, 1)]
+ [InlineData(1050, 1)]
+ [InlineData(2047, 1)]
+ [InlineData(2048, 1)]
+ [InlineData(1021, 2)]
+ [InlineData(1022, 2)]
+ [InlineData(1023, 2)]
+ [InlineData(1024, 2)]
+ [InlineData(1024, 1023)]
+ [InlineData(1024, 1024)]
+ [InlineData(1024, 1050)]
+ [InlineData(1050, 2)]
+ [InlineData(2046, 2)]
+ [InlineData(2048, 2)]
+ [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, 1)]
+ [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, 2)]
+ [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1, HttpResponseStreamWriter.DefaultBufferSize)]
+ public async Task WriteLineReadOnlyMemoryAsync_WritesToStream(int byteLength, int newLineLength)
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ writer.NewLine = new string('\n', newLineLength);
- // Assert
- Assert.Equal(byteLength + newLineLength, stream.Length);
+ // Act
+ using (writer)
+ {
+ var array = new string('a', byteLength).ToCharArray();
+ var memory = new ReadOnlyMemory<char>(array);
+ await writer.WriteLineAsync(memory);
}
- [Fact]
- public async Task WriteLineReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
- {
- // Arrange
- var stream = new TestMemoryStream();
- var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
- var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
- var cancellationToken = new CancellationToken(true);
+ // Assert
+ Assert.Equal(byteLength + newLineLength, stream.Length);
+ }
- // Act
- using (writer)
- {
- await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteLineAsync(memory, cancellationToken));
- }
+ [Fact]
+ public async Task WriteLineReadOnlyMemoryAsync_TokenCanceled_ReturnsCanceledTask()
+ {
+ // Arrange
+ var stream = new TestMemoryStream();
+ var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+ var memory = new ReadOnlyMemory<char>(new char[] { 'a' });
+ var cancellationToken = new CancellationToken(true);
- // Assert
- Assert.Equal(0, stream.Length);
+ // Act
+ using (writer)
+ {
+ await Assert.ThrowsAsync<TaskCanceledException>(async () => await writer.WriteLineAsync(memory, cancellationToken));
}
- [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);
+ // Assert
+ Assert.Equal(0, stream.Length);
+ }
- // Act
- using (writer)
- {
- await writer.WriteAsync(data);
- }
+ [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);
- // Assert
- Assert.Equal(expectedBytes, stream.ToArray());
+ // Act
+ using (writer)
+ {
+ await writer.WriteAsync(data);
}
- [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);
+ // Assert
+ Assert.Equal(expectedBytes, stream.ToArray());
+ }
- // Act
- using (writer)
- {
- await writer.WriteAsync(data);
- }
+ [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);
- // Assert
- Assert.Equal(expectedBytes, stream.ToArray());
+ // Act
+ using (writer)
+ {
+ await writer.WriteAsync(data);
}
- // 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());
+ }
- // 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();
- [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);
+ var expectedBytes = encoding.GetBytes("Hello, World!");
+ using (var writer = new HttpResponseStreamWriter(
+ stream,
+ encoding,
+ 1024,
+ ArrayPool<byte>.Shared,
+ ArrayPool<char>.Shared))
+ {
// 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);
+ writer.Write("Hello, World!");
}
- [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);
- });
- }
+ // 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]
- [InlineData(0)]
- [InlineData(-1)]
- public static void NegativeOrZeroBufferSize_ExpectArgumentOutOfRangeException(int size)
+ [Theory]
+ [MemberData(nameof(HttpResponseStreamWriterData))]
+ public static void NullInputsInConstructor_ExpectArgumentNullException(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, ArrayPool<char> charPool)
+ {
+ Assert.Throws<ArgumentNullException>(() =>
{
- Assert.Throws<ArgumentOutOfRangeException>(() =>
- {
- var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, size, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
- });
- }
+ var httpRequestStreamReader = new HttpResponseStreamWriter(stream, encoding, 1, bytePool, charPool);
+ });
+ }
- [Fact]
- public static void StreamCannotRead_ExpectArgumentException()
+ [Theory]
+ [InlineData(0)]
+ [InlineData(-1)]
+ public static void NegativeOrZeroBufferSize_ExpectArgumentOutOfRangeException(int size)
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() =>
{
- 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);
- });
- }
+ var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, size, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+ });
+ }
- [Theory]
- [MemberData(nameof(HttpResponseDisposeData))]
- public static void StreamDisposed_ExpectedObjectDisposedException(Action<HttpResponseStreamWriter> action)
+ [Fact]
+ public static void StreamCannotRead_ExpectArgumentException()
+ {
+ var mockStream = new Mock<Stream>();
+ mockStream.Setup(m => m.CanWrite).Returns(false);
+ Assert.Throws<ArgumentException>(() =>
{
- var httpResponseStreamWriter = new HttpResponseStreamWriter(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
- httpResponseStreamWriter.Dispose();
+ var httpRequestStreamReader = new HttpRequestStreamReader(mockStream.Object, Encoding.UTF8, 1, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+ });
+ }
- Assert.Throws<ObjectDisposedException>(() =>
- {
- action(httpResponseStreamWriter);
- });
- }
+ [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();
- [Theory]
- [MemberData(nameof(HttpResponseDisposeDataAsync))]
- public static async Task StreamDisposed_ExpectedObjectDisposedExceptionAsync(Func<HttpResponseStreamWriter, Task> function)
+ Assert.Throws<ObjectDisposedException>(() =>
{
- var httpResponseStreamWriter = new HttpResponseStreamWriter(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
- httpResponseStreamWriter.Dispose();
-
- await Assert.ThrowsAsync<ObjectDisposedException>(() =>
- {
- return function(httpResponseStreamWriter);
- });
- }
+ 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();
- private class TestMemoryStream : MemoryStream
+ await Assert.ThrowsAsync<ObjectDisposedException>(() =>
{
- public int FlushCallCount { get; private set; }
+ return function(httpResponseStreamWriter);
+ });
+ }
- public int FlushAsyncCallCount { get; private set; }
- public int CloseCallCount { get; private set; }
+ private class TestMemoryStream : MemoryStream
+ {
+ public int FlushCallCount { get; private set; }
- public int DisposeCallCount { get; private set; }
+ public int FlushAsyncCallCount { get; private set; }
- public int WriteCallCount { get; private set; }
+ public int CloseCallCount { get; private set; }
- public int WriteAsyncCallCount { get; private set; }
+ public int DisposeCallCount { get; private set; }
- public bool ThrowOnWrite { get; set; }
+ public int WriteCallCount { get; private set; }
- public override void Flush()
- {
- FlushCallCount++;
- base.Flush();
- }
+ public int WriteAsyncCallCount { get; private set; }
- public override Task FlushAsync(CancellationToken cancellationToken)
- {
- FlushAsyncCallCount++;
- return base.FlushAsync(cancellationToken);
- }
+ public bool ThrowOnWrite { get; set; }
- 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 void Flush()
+ {
+ FlushCallCount++;
+ base.Flush();
+ }
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- => WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ FlushAsyncCallCount++;
+ return base.FlushAsync(cancellationToken);
+ }
- public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ WriteCallCount++;
+ if (ThrowOnWrite)
{
- WriteAsyncCallCount++;
- if (ThrowOnWrite)
- {
- throw new IOException("Test IOException");
- }
- return base.WriteAsync(buffer, cancellationToken);
+ throw new IOException("Test IOException");
}
+ base.Write(buffer, offset, count);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ => WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
- protected override void Dispose(bool disposing)
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ WriteAsyncCallCount++;
+ if (ThrowOnWrite)
{
- DisposeCallCount++;
- base.Dispose(disposing);
+ throw new IOException("Test IOException");
}
+ return base.WriteAsync(buffer, cancellationToken);
}
- public static IEnumerable<object?[]> HttpResponseStreamWriterData()
+ protected override void Dispose(bool disposing)
{
- 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 };
+ DisposeCallCount++;
+ base.Dispose(disposing);
}
+ }
- public static IEnumerable<object[]> HttpResponseDisposeData()
- {
- yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+ 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) =>
+ yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
{
httpResponseStreamWriter.Write(new char[] { 'a', 'b' }, 0, 1);
})};
- yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+ yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
{
httpResponseStreamWriter.Write("hello");
})};
- yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+ yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
{
httpResponseStreamWriter.Write(new ReadOnlySpan<char>(new char[] { 'a', 'b' }));
})};
- yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+ 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) =>
+ 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) =>
+ 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) =>
+ yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.WriteAsync("hello");
})};
- yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+ yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.WriteAsync(new ReadOnlyMemory<char>(new char[] { 'a', 'b' }));
})};
- yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+ yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.WriteLineAsync(new ReadOnlyMemory<char>(new char[] { 'a', 'b' }));
})};
- yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+ yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
{
await httpResponseStreamWriter.FlushAsync();
})};
- }
}
}
diff --git a/src/Http/WebUtilities/test/MultipartReaderTests.cs b/src/Http/WebUtilities/test/MultipartReaderTests.cs
index 36ffa5653a..206c02831a 100644
--- a/src/Http/WebUtilities/test/MultipartReaderTests.cs
+++ b/src/Http/WebUtilities/test/MultipartReaderTests.cs
@@ -9,39 +9,39 @@ using System.Text;
using System.Threading.Tasks;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class MultipartReaderTests
{
- 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 =
+ 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 =
+ 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 =
+ 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 =
+ // 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 =
+ private const string TwoPartBody =
"--9051914041544843365972754266\r\n" +
"Content-Disposition: form-data; name=\"text\"\r\n" +
"\r\n" +
@@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.WebUtilities
"Content of a.txt.\r\n" +
"\r\n" +
"--9051914041544843365972754266--\r\n";
- private const string TwoPartBodyWithUnicodeFileName =
+ private const string TwoPartBodyWithUnicodeFileName =
"--9051914041544843365972754266\r\n" +
"Content-Disposition: form-data; name=\"text\"\r\n" +
"\r\n" +
@@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.WebUtilities
"Content of a.txt.\r\n" +
"\r\n" +
"--9051914041544843365972754266--\r\n";
- private const string ThreePartBody =
+ private const string ThreePartBody =
"--9051914041544843365972754266\r\n" +
"Content-Disposition: form-data; name=\"text\"\r\n" +
"\r\n" +
@@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.WebUtilities
"\r\n" +
"--9051914041544843365972754266--\r\n";
- private const string TwoPartBodyIncompleteBuffer =
+ private const string TwoPartBodyIncompleteBuffer =
"--9051914041544843365972754266\r\n" +
"Content-Disposition: form-data; name=\"text\"\r\n" +
"\r\n" +
@@ -97,289 +97,288 @@ namespace Microsoft.AspNetCore.WebUtilities
"\r\n" +
"--9051914041544843365";
- private static MemoryStream MakeStream(string text)
- {
- return new MemoryStream(Encoding.UTF8.GetBytes(text));
- }
+ 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);
- }
+ private static string GetString(byte[] buffer, int count)
+ {
+ return Encoding.ASCII.GetString(buffer, 0, count);
+ }
- [Fact]
- public async Task MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_ReadTwoPartBodyWithUnicodeFileName_Success()
+ [Fact]
+ public async Task MultipartReader_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 MultipartReader_HeaderCountExceeded_Throws()
+ {
+ var stream = MakeStream(OnePartBodyTwoHeaders);
+ var reader = new MultipartReader(Boundary, stream)
{
- 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 MultipartReader_ThreePartBody_Success()
+ HeadersCountLimit = 1,
+ };
+
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(() => reader.ReadNextSectionAsync());
+ Assert.Equal("Multipart headers count limit 1 exceeded.", exception.Message);
+ }
+
+ [Fact]
+ public async Task MultipartReader_HeadersLengthExceeded_Throws()
+ {
+ var stream = MakeStream(OnePartBodyTwoHeaders);
+ var reader = new MultipartReader(Boundary, stream)
{
- 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 MultipartReader_BufferSizeMustBeLargerThanBoundary_Throws()
+ HeadersLengthLimit = 60,
+ };
+
+ var exception = await Assert.ThrowsAsync<InvalidDataException>(() => reader.ReadNextSectionAsync());
+ Assert.Equal("Line length limit 17 exceeded.", exception.Message);
+ }
+
+ [Fact]
+ public async Task MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_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 MultipartReader_BufferSizeMustBeLargerThanBoundary_Throws()
+ {
+ var stream = MakeStream(ThreePartBody);
+ Assert.Throws<ArgumentOutOfRangeException>(() =>
{
- var stream = MakeStream(ThreePartBody);
- Assert.Throws<ArgumentOutOfRangeException>(() =>
- {
- var reader = new MultipartReader(Boundary, stream, 5);
- });
- }
-
- [Fact]
- public async Task MultipartReader_TwoPartBodyIncompleteBuffer_TwoSectionsReadSuccessfullyThirdSectionThrows()
+ var reader = new MultipartReader(Boundary, stream, 5);
+ });
+ }
+
+ [Fact]
+ public async Task MultipartReader_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 () =>
{
- 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 MultipartReader_ReadInvalidUtf8Header_ReplacementCharacters()
- {
- var body1 =
+ [Fact]
+ public async Task MultipartReader_ReadInvalidUtf8Header_ReplacementCharacters()
+ {
+ var body1 =
"--9051914041544843365972754266\r\n" +
"Content-Disposition: form-data; name=\"text\" filename=\"a";
- var body2 =
+ 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 MultipartReader_ReadInvalidUtf8SurrogateHeader_ReplacementCharacters()
- {
- var body1 =
+ 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 MultipartReader_ReadInvalidUtf8SurrogateHeader_ReplacementCharacters()
+ {
+ var body1 =
"--9051914041544843365972754266\r\n" +
"Content-Disposition: form-data; name=\"text\" filename=\"a";
- var body2 =
+ 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\uFFFD\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());
- }
+ 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\uFFFD\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());
}
}
diff --git a/src/Http/WebUtilities/test/NonSeekableReadStream.cs b/src/Http/WebUtilities/test/NonSeekableReadStream.cs
index 74467d4ea0..80b18da18c 100644
--- a/src/Http/WebUtilities/test/NonSeekableReadStream.cs
+++ b/src/Http/WebUtilities/test/NonSeekableReadStream.cs
@@ -6,69 +6,68 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class NonSeekableReadStream : Stream
{
- public class NonSeekableReadStream : Stream
+ private readonly 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)
{
- private readonly 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);
- }
+ count = Math.Max(count, 1);
+ return _inner.ReadAsync(buffer, offset, count, cancellationToken);
}
}
diff --git a/src/Http/WebUtilities/test/PagedByteBufferTest.cs b/src/Http/WebUtilities/test/PagedByteBufferTest.cs
index fbbadb4ba1..93993b4b54 100644
--- a/src/Http/WebUtilities/test/PagedByteBufferTest.cs
+++ b/src/Http/WebUtilities/test/PagedByteBufferTest.cs
@@ -9,240 +9,239 @@ using System.Threading.Tasks;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class PagedByteBufferTest
{
- public class PagedByteBufferTest
+ [Fact]
+ public void Add_CreatesNewPage()
{
- [Fact]
- public void Add_CreatesNewPage()
- {
- // Arrange
- var input = Encoding.UTF8.GetBytes("Hello world");
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ // Arrange
+ var input = Encoding.UTF8.GetBytes("Hello world");
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- // Act
- buffer.Add(input, 0, input.Length);
+ // Act
+ buffer.Add(input, 0, input.Length);
- // Assert
- Assert.Single(buffer.Pages);
- Assert.Equal(input.Length, buffer.Length);
- Assert.Equal(input, ReadBufferedContent(buffer));
- }
+ // Assert
+ Assert.Single(buffer.Pages);
+ Assert.Equal(input.Length, buffer.Length);
+ Assert.Equal(input, ReadBufferedContent(buffer));
+ }
- [Fact]
- public void Add_AppendsToExistingPage()
- {
- // Arrange
- var input1 = Encoding.UTF8.GetBytes("Hello");
- var input2 = Encoding.UTF8.GetBytes("world");
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- buffer.Add(input1, 0, input1.Length);
+ [Fact]
+ public void Add_AppendsToExistingPage()
+ {
+ // Arrange
+ var input1 = Encoding.UTF8.GetBytes("Hello");
+ var input2 = Encoding.UTF8.GetBytes("world");
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ buffer.Add(input1, 0, input1.Length);
+
+ // Act
+ buffer.Add(input2, 0, input2.Length);
+
+ // Assert
+ Assert.Single(buffer.Pages);
+ Assert.Equal(10, buffer.Length);
+ Assert.Equal(Enumerable.Concat(input1, input2).ToArray(), ReadBufferedContent(buffer));
+ }
- // Act
- buffer.Add(input2, 0, input2.Length);
+ [Fact]
+ public void Add_WithOffsets()
+ {
+ // Arrange
+ var input = new byte[] { 1, 2, 3, 4, 5 };
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- // Assert
- Assert.Single(buffer.Pages);
- Assert.Equal(10, buffer.Length);
- Assert.Equal(Enumerable.Concat(input1, input2).ToArray(), ReadBufferedContent(buffer));
- }
+ // Act
+ buffer.Add(input, 1, 3);
- [Fact]
- public void Add_WithOffsets()
- {
- // Arrange
- var input = new byte[] { 1, 2, 3, 4, 5 };
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ // Assert
+ Assert.Single(buffer.Pages);
+ Assert.Equal(3, buffer.Length);
+ Assert.Equal(new byte[] { 2, 3, 4 }, ReadBufferedContent(buffer));
+ }
- // Act
- buffer.Add(input, 1, 3);
+ [Fact]
+ public void Add_FillsUpBuffer()
+ {
+ // Arrange
+ var input1 = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize - 1).ToArray();
+ var input2 = new byte[] { 0xca };
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ buffer.Add(input1, 0, input1.Length);
+
+ // Act
+ buffer.Add(input2, 0, 1);
+
+ // Assert
+ Assert.Single(buffer.Pages);
+ Assert.Equal(PagedByteBuffer.PageSize, buffer.Length);
+ Assert.Equal(Enumerable.Concat(input1, input2).ToArray(), ReadBufferedContent(buffer));
+ }
- // Assert
- Assert.Single(buffer.Pages);
- Assert.Equal(3, buffer.Length);
- Assert.Equal(new byte[] { 2, 3, 4 }, ReadBufferedContent(buffer));
- }
+ [Fact]
+ public void Add_AppendsToMultiplePages()
+ {
+ // Arrange
+ var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize + 10).ToArray();
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- [Fact]
- public void Add_FillsUpBuffer()
- {
- // Arrange
- var input1 = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize - 1).ToArray();
- var input2 = new byte[] { 0xca };
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- buffer.Add(input1, 0, input1.Length);
+ // Act
+ buffer.Add(input, 0, input.Length);
- // Act
- buffer.Add(input2, 0, 1);
+ // Assert
+ Assert.Equal(2, buffer.Pages.Count);
+ Assert.Equal(PagedByteBuffer.PageSize + 10, buffer.Length);
+ Assert.Equal(input.ToArray(), ReadBufferedContent(buffer));
+ }
- // Assert
- Assert.Single(buffer.Pages);
- Assert.Equal(PagedByteBuffer.PageSize, buffer.Length);
- Assert.Equal(Enumerable.Concat(input1, input2).ToArray(), ReadBufferedContent(buffer));
- }
+ [Fact]
+ public void MoveTo_CopiesContentToStream()
+ {
+ // Arrange
+ var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ buffer.Add(input, 0, input.Length);
+ var stream = new MemoryStream();
- [Fact]
- public void Add_AppendsToMultiplePages()
- {
- // Arrange
- var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize + 10).ToArray();
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ // Act
+ buffer.MoveTo(stream);
- // Act
- buffer.Add(input, 0, input.Length);
+ // Assert
+ Assert.Equal(input, stream.ToArray());
- // Assert
- Assert.Equal(2, buffer.Pages.Count);
- Assert.Equal(PagedByteBuffer.PageSize + 10, buffer.Length);
- Assert.Equal(input.ToArray(), ReadBufferedContent(buffer));
- }
+ // Verify moving new content works.
+ var newInput = Enumerable.Repeat((byte)0xcb, PagedByteBuffer.PageSize * 2 + 13).ToArray();
+ buffer.Add(newInput, 0, newInput.Length);
- [Fact]
- public void MoveTo_CopiesContentToStream()
- {
- // Arrange
- var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- buffer.Add(input, 0, input.Length);
- var stream = new MemoryStream();
+ stream.SetLength(0);
+ buffer.MoveTo(stream);
- // Act
- buffer.MoveTo(stream);
+ Assert.Equal(newInput, stream.ToArray());
+ }
- // Assert
- Assert.Equal(input, stream.ToArray());
+ [Fact]
+ public async Task MoveToAsync_CopiesContentToStream()
+ {
+ // Arrange
+ var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ buffer.Add(input, 0, input.Length);
+ var stream = new MemoryStream();
- // Verify moving new content works.
- var newInput = Enumerable.Repeat((byte)0xcb, PagedByteBuffer.PageSize * 2 + 13).ToArray();
- buffer.Add(newInput, 0, newInput.Length);
+ // Act
+ await buffer.MoveToAsync(stream, default);
- stream.SetLength(0);
- buffer.MoveTo(stream);
+ // Assert
+ Assert.Equal(input, stream.ToArray());
- Assert.Equal(newInput, stream.ToArray());
- }
+ // Verify adding and moving new content works.
+ var newInput = Enumerable.Repeat((byte)0xcb, PagedByteBuffer.PageSize * 2 + 13).ToArray();
+ buffer.Add(newInput, 0, newInput.Length);
+ stream.SetLength(0);
+ await buffer.MoveToAsync(stream, default);
- [Fact]
- public async Task MoveToAsync_CopiesContentToStream()
- {
- // Arrange
- var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- buffer.Add(input, 0, input.Length);
- var stream = new MemoryStream();
+ Assert.Equal(newInput, stream.ToArray());
+ }
+
+ [Fact]
+ public async Task MoveToAsync_ClearsBuffers()
+ {
+ // Arrange
+ var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
+ using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
+ buffer.Add(input, 0, input.Length);
+ var stream = new MemoryStream();
+
+ // Act
+ await buffer.MoveToAsync(stream, default);
+
+ // Assert
+ Assert.Equal(input, stream.ToArray());
+
+ // Verify copying it again works.
+ Assert.Equal(0, buffer.Length);
+ Assert.False(buffer.Disposed);
+ Assert.Empty(buffer.Pages);
+ }
+ [Fact]
+ public void MoveTo_WithClear_ReturnsBuffers()
+ {
+ // Arrange
+ var input = new byte[] { 1, };
+ var arrayPool = new Mock<ArrayPool<byte>>();
+ var byteArray = new byte[PagedByteBuffer.PageSize];
+ arrayPool.Setup(p => p.Rent(PagedByteBuffer.PageSize))
+ .Returns(byteArray);
+ arrayPool.Setup(p => p.Return(byteArray, false)).Verifiable();
+ var memoryStream = new MemoryStream();
+
+ using (var buffer = new PagedByteBuffer(arrayPool.Object))
+ {
// Act
- await buffer.MoveToAsync(stream, default);
+ buffer.Add(input, 0, input.Length);
+ buffer.MoveTo(memoryStream);
// Assert
- Assert.Equal(input, stream.ToArray());
-
- // Verify adding and moving new content works.
- var newInput = Enumerable.Repeat((byte)0xcb, PagedByteBuffer.PageSize * 2 + 13).ToArray();
- buffer.Add(newInput, 0, newInput.Length);
- stream.SetLength(0);
- await buffer.MoveToAsync(stream, default);
-
- Assert.Equal(newInput, stream.ToArray());
+ Assert.Equal(input, memoryStream.ToArray());
}
- [Fact]
- public async Task MoveToAsync_ClearsBuffers()
- {
- // Arrange
- var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
- using var buffer = new PagedByteBuffer(ArrayPool<byte>.Shared);
- buffer.Add(input, 0, input.Length);
- var stream = new MemoryStream();
+ arrayPool.Verify(p => p.Rent(It.IsAny<int>()), Times.Once());
+ arrayPool.Verify(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
+ }
+ [Fact]
+ public async Task MoveToAsync_ReturnsBuffers()
+ {
+ // Arrange
+ var input = new byte[] { 1, };
+ var arrayPool = new Mock<ArrayPool<byte>>();
+ var byteArray = new byte[PagedByteBuffer.PageSize];
+ arrayPool.Setup(p => p.Rent(PagedByteBuffer.PageSize))
+ .Returns(byteArray);
+ var memoryStream = new MemoryStream();
+
+ using (var buffer = new PagedByteBuffer(arrayPool.Object))
+ {
// Act
- await buffer.MoveToAsync(stream, default);
+ buffer.Add(input, 0, input.Length);
+ await buffer.MoveToAsync(memoryStream, default);
// Assert
- Assert.Equal(input, stream.ToArray());
-
- // Verify copying it again works.
- Assert.Equal(0, buffer.Length);
- Assert.False(buffer.Disposed);
- Assert.Empty(buffer.Pages);
+ Assert.Equal(input, memoryStream.ToArray());
}
- [Fact]
- public void MoveTo_WithClear_ReturnsBuffers()
- {
- // Arrange
- var input = new byte[] { 1, };
- var arrayPool = new Mock<ArrayPool<byte>>();
- var byteArray = new byte[PagedByteBuffer.PageSize];
- arrayPool.Setup(p => p.Rent(PagedByteBuffer.PageSize))
- .Returns(byteArray);
- arrayPool.Setup(p => p.Return(byteArray, false)).Verifiable();
- var memoryStream = new MemoryStream();
-
- using (var buffer = new PagedByteBuffer(arrayPool.Object))
- {
- // Act
- buffer.Add(input, 0, input.Length);
- buffer.MoveTo(memoryStream);
-
- // Assert
- Assert.Equal(input, memoryStream.ToArray());
- }
-
- arrayPool.Verify(p => p.Rent(It.IsAny<int>()), Times.Once());
- arrayPool.Verify(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
- }
-
- [Fact]
- public async Task MoveToAsync_ReturnsBuffers()
- {
- // Arrange
- var input = new byte[] { 1, };
- var arrayPool = new Mock<ArrayPool<byte>>();
- var byteArray = new byte[PagedByteBuffer.PageSize];
- arrayPool.Setup(p => p.Rent(PagedByteBuffer.PageSize))
- .Returns(byteArray);
- var memoryStream = new MemoryStream();
-
- using (var buffer = new PagedByteBuffer(arrayPool.Object))
- {
- // Act
- buffer.Add(input, 0, input.Length);
- await buffer.MoveToAsync(memoryStream, default);
-
- // Assert
- Assert.Equal(input, memoryStream.ToArray());
- }
-
- arrayPool.Verify(p => p.Rent(It.IsAny<int>()), Times.Once());
- arrayPool.Verify(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
- }
+ arrayPool.Verify(p => p.Rent(It.IsAny<int>()), Times.Once());
+ arrayPool.Verify(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Once());
+ }
- [Fact]
- public void Dispose_ReturnsBuffers_ExactlyOnce()
- {
- // Arrange
- var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
- var arrayPool = new Mock<ArrayPool<byte>>();
- arrayPool.Setup(p => p.Rent(PagedByteBuffer.PageSize))
- .Returns(new byte[PagedByteBuffer.PageSize]);
+ [Fact]
+ public void Dispose_ReturnsBuffers_ExactlyOnce()
+ {
+ // Arrange
+ var input = Enumerable.Repeat((byte)0xba, PagedByteBuffer.PageSize * 3 + 10).ToArray();
+ var arrayPool = new Mock<ArrayPool<byte>>();
+ arrayPool.Setup(p => p.Rent(PagedByteBuffer.PageSize))
+ .Returns(new byte[PagedByteBuffer.PageSize]);
- var buffer = new PagedByteBuffer(arrayPool.Object);
+ var buffer = new PagedByteBuffer(arrayPool.Object);
- // Act
- buffer.Add(input, 0, input.Length);
- buffer.Dispose();
- buffer.Dispose();
+ // Act
+ buffer.Add(input, 0, input.Length);
+ buffer.Dispose();
+ buffer.Dispose();
- arrayPool.Verify(p => p.Rent(It.IsAny<int>()), Times.Exactly(4));
- arrayPool.Verify(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Exactly(4));
- }
+ arrayPool.Verify(p => p.Rent(It.IsAny<int>()), Times.Exactly(4));
+ arrayPool.Verify(p => p.Return(It.IsAny<byte[]>(), It.IsAny<bool>()), Times.Exactly(4));
+ }
- private static byte[] ReadBufferedContent(PagedByteBuffer buffer)
- {
- using var stream = new MemoryStream();
- buffer.MoveTo(stream);
- return stream.ToArray();
- }
+ private static byte[] ReadBufferedContent(PagedByteBuffer buffer)
+ {
+ using var stream = new MemoryStream();
+ buffer.MoveTo(stream);
+ return stream.ToArray();
}
}
diff --git a/src/Http/WebUtilities/test/QueryHelpersTests.cs b/src/Http/WebUtilities/test/QueryHelpersTests.cs
index 7c395d9e6c..2db84da7e9 100644
--- a/src/Http/WebUtilities/test/QueryHelpersTests.cs
+++ b/src/Http/WebUtilities/test/QueryHelpersTests.cs
@@ -7,159 +7,159 @@ using System.Linq;
using Microsoft.Extensions.Primitives;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class QueryHelperTests
{
- 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[""]);
+ }
+
+ [Fact]
+ public void ParseQueryWithEncodedKeyWorks()
+ {
+ var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D");
+ Assert.Single(collection);
+ Assert.Equal("", collection["fields [todoItems]"].FirstOrDefault());
+ }
+
+ [Fact]
+ public void ParseQueryWithEncodedValueWorks()
+ {
+ var collection = QueryHelpers.ParseQuery("?=fields+%5BtodoItems%5D");
+ Assert.Single(collection);
+ Assert.Equal("fields [todoItems]", collection[""].FirstOrDefault());
+ }
+
+ [Fact]
+ public void ParseQueryWithEncodedKeyEmptyValueWorks()
{
- [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[""]);
- }
-
- [Fact]
- public void ParseQueryWithEncodedKeyWorks()
- {
- var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D");
- Assert.Single(collection);
- Assert.Equal("", collection["fields [todoItems]"].FirstOrDefault());
- }
-
- [Fact]
- public void ParseQueryWithEncodedValueWorks()
- {
- var collection = QueryHelpers.ParseQuery("?=fields+%5BtodoItems%5D");
- Assert.Single(collection);
- Assert.Equal("fields [todoItems]", collection[""].FirstOrDefault());
- }
-
- [Fact]
- public void ParseQueryWithEncodedKeyEmptyValueWorks()
- {
- var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D=");
- Assert.Single(collection);
- Assert.Equal("", collection["fields [todoItems]"].FirstOrDefault());
- }
-
- [Fact]
- public void ParseQueryWithEncodedKeyEncodedValueWorks()
- {
- var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D=%5B+1+%5D");
- Assert.Single(collection);
- Assert.Equal("[ 1 ]", collection["fields [todoItems]"].FirstOrDefault());
- }
-
- [Fact]
- public void ParseQueryWithEncodedKeyEncodedValuesWorks()
- {
- var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D=%5B+1+%5D&fields+%5BtodoItems%5D=%5B+2+%5D");
- Assert.Single(collection);
- Assert.Equal(new[] { "[ 1 ]", "[ 2 ]" }, collection["fields [todoItems]"]);
- }
-
- [Theory]
- [InlineData("?")]
- [InlineData("")]
- [InlineData(null)]
- public void ParseEmptyOrNullQueryWorks(string? queryString)
- {
- var collection = QueryHelpers.ParseQuery(queryString);
- Assert.Empty(collection);
- }
-
- [Fact]
- public void AddQueryStringWithNullValueThrows()
- {
- Assert.Throws<ArgumentNullException>("value" ,() => QueryHelpers.AddQueryString("http://contoso.com/", "hello", null!));
- }
-
- [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&another=")]
- [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?hello=world&some=text&another=")]
- [InlineData("http://contoso.com/someaction?q=1", "http://contoso.com/someaction?q=1&hello=world&some=text&another=")]
- [InlineData("http://contoso.com/some#action", "http://contoso.com/some?hello=world&some=text&another=#action")]
- [InlineData("http://contoso.com/some?q=1#action", "http://contoso.com/some?q=1&hello=world&some=text&another=#action")]
- [InlineData("http://contoso.com/#action", "http://contoso.com/?hello=world&some=text&another=#action")]
- [InlineData(
- "http://contoso.com/someaction?q=test#anchor?value",
- "http://contoso.com/someaction?q=test&hello=world&some=text&another=#anchor?value")]
- [InlineData(
- "http://contoso.com/someaction#anchor?stuff",
- "http://contoso.com/someaction?hello=world&some=text&another=#anchor?stuff")]
- [InlineData(
- "http://contoso.com/someaction?name?something",
- "http://contoso.com/someaction?name?something&hello=world&some=text&another=")]
- [InlineData(
- "http://contoso.com/someaction#name#something",
- "http://contoso.com/someaction?hello=world&some=text&another=#name#something")]
- public void AddQueryStringWithDictionary(string uri, string expectedUri)
- {
- var queryStrings = new Dictionary<string, string?>()
+ var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D=");
+ Assert.Single(collection);
+ Assert.Equal("", collection["fields [todoItems]"].FirstOrDefault());
+ }
+
+ [Fact]
+ public void ParseQueryWithEncodedKeyEncodedValueWorks()
+ {
+ var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D=%5B+1+%5D");
+ Assert.Single(collection);
+ Assert.Equal("[ 1 ]", collection["fields [todoItems]"].FirstOrDefault());
+ }
+
+ [Fact]
+ public void ParseQueryWithEncodedKeyEncodedValuesWorks()
+ {
+ var collection = QueryHelpers.ParseQuery("?fields+%5BtodoItems%5D=%5B+1+%5D&fields+%5BtodoItems%5D=%5B+2+%5D");
+ Assert.Single(collection);
+ Assert.Equal(new[] { "[ 1 ]", "[ 2 ]" }, collection["fields [todoItems]"]);
+ }
+
+ [Theory]
+ [InlineData("?")]
+ [InlineData("")]
+ [InlineData(null)]
+ public void ParseEmptyOrNullQueryWorks(string? queryString)
+ {
+ var collection = QueryHelpers.ParseQuery(queryString);
+ Assert.Empty(collection);
+ }
+
+ [Fact]
+ public void AddQueryStringWithNullValueThrows()
+ {
+ Assert.Throws<ArgumentNullException>("value", () => QueryHelpers.AddQueryString("http://contoso.com/", "hello", null!));
+ }
+
+ [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&another=")]
+ [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?hello=world&some=text&another=")]
+ [InlineData("http://contoso.com/someaction?q=1", "http://contoso.com/someaction?q=1&hello=world&some=text&another=")]
+ [InlineData("http://contoso.com/some#action", "http://contoso.com/some?hello=world&some=text&another=#action")]
+ [InlineData("http://contoso.com/some?q=1#action", "http://contoso.com/some?q=1&hello=world&some=text&another=#action")]
+ [InlineData("http://contoso.com/#action", "http://contoso.com/?hello=world&some=text&another=#action")]
+ [InlineData(
+ "http://contoso.com/someaction?q=test#anchor?value",
+ "http://contoso.com/someaction?q=test&hello=world&some=text&another=#anchor?value")]
+ [InlineData(
+ "http://contoso.com/someaction#anchor?stuff",
+ "http://contoso.com/someaction?hello=world&some=text&another=#anchor?stuff")]
+ [InlineData(
+ "http://contoso.com/someaction?name?something",
+ "http://contoso.com/someaction?name?something&hello=world&some=text&another=")]
+ [InlineData(
+ "http://contoso.com/someaction#name#something",
+ "http://contoso.com/someaction?hello=world&some=text&another=#name#something")]
+ public void AddQueryStringWithDictionary(string uri, string expectedUri)
+ {
+ var queryStrings = new Dictionary<string, string?>()
{
{ "hello", "world" },
{ "some", "text" },
@@ -167,40 +167,39 @@ namespace Microsoft.AspNetCore.WebUtilities
{ "invisible", null }
};
- var result = QueryHelpers.AddQueryString(uri, queryStrings);
- Assert.Equal(expectedUri, result);
- }
-
- [Theory]
- [InlineData("http://contoso.com/", "http://contoso.com/?param1=value1&param1=&param1=value3&param2=")]
- [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?param1=value1&param1=&param1=value3&param2=")]
- [InlineData("http://contoso.com/someaction?param2=1", "http://contoso.com/someaction?param2=1&param1=value1&param1=&param1=value3&param2=")]
- [InlineData("http://contoso.com/some#action", "http://contoso.com/some?param1=value1&param1=&param1=value3&param2=#action")]
- [InlineData("http://contoso.com/some?param2=1#action", "http://contoso.com/some?param2=1&param1=value1&param1=&param1=value3&param2=#action")]
- [InlineData("http://contoso.com/#action", "http://contoso.com/?param1=value1&param1=&param1=value3&param2=#action")]
- [InlineData(
- "http://contoso.com/someaction?q=test#anchor?value",
- "http://contoso.com/someaction?q=test&param1=value1&param1=&param1=value3&param2=#anchor?value")]
- [InlineData(
- "http://contoso.com/someaction#anchor?stuff",
- "http://contoso.com/someaction?param1=value1&param1=&param1=value3&param2=#anchor?stuff")]
- [InlineData(
- "http://contoso.com/someaction?name?something",
- "http://contoso.com/someaction?name?something&param1=value1&param1=&param1=value3&param2=")]
- [InlineData(
- "http://contoso.com/someaction#name#something",
- "http://contoso.com/someaction?param1=value1&param1=&param1=value3&param2=#name#something")]
- public void AddQueryStringWithEnumerableOfKeysAndStringValues(string uri, string expectedUri)
- {
- var queryStrings = new Dictionary<string, StringValues>()
+ var result = QueryHelpers.AddQueryString(uri, queryStrings);
+ Assert.Equal(expectedUri, result);
+ }
+
+ [Theory]
+ [InlineData("http://contoso.com/", "http://contoso.com/?param1=value1&param1=&param1=value3&param2=")]
+ [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?param1=value1&param1=&param1=value3&param2=")]
+ [InlineData("http://contoso.com/someaction?param2=1", "http://contoso.com/someaction?param2=1&param1=value1&param1=&param1=value3&param2=")]
+ [InlineData("http://contoso.com/some#action", "http://contoso.com/some?param1=value1&param1=&param1=value3&param2=#action")]
+ [InlineData("http://contoso.com/some?param2=1#action", "http://contoso.com/some?param2=1&param1=value1&param1=&param1=value3&param2=#action")]
+ [InlineData("http://contoso.com/#action", "http://contoso.com/?param1=value1&param1=&param1=value3&param2=#action")]
+ [InlineData(
+ "http://contoso.com/someaction?q=test#anchor?value",
+ "http://contoso.com/someaction?q=test&param1=value1&param1=&param1=value3&param2=#anchor?value")]
+ [InlineData(
+ "http://contoso.com/someaction#anchor?stuff",
+ "http://contoso.com/someaction?param1=value1&param1=&param1=value3&param2=#anchor?stuff")]
+ [InlineData(
+ "http://contoso.com/someaction?name?something",
+ "http://contoso.com/someaction?name?something&param1=value1&param1=&param1=value3&param2=")]
+ [InlineData(
+ "http://contoso.com/someaction#name#something",
+ "http://contoso.com/someaction?param1=value1&param1=&param1=value3&param2=#name#something")]
+ public void AddQueryStringWithEnumerableOfKeysAndStringValues(string uri, string expectedUri)
+ {
+ var queryStrings = new Dictionary<string, StringValues>()
{
{ "param1", new StringValues(new [] { "value1", string.Empty, "value3" }) },
{ "param2", string.Empty },
{ "param3", StringValues.Empty }
};
- var result = QueryHelpers.AddQueryString(uri, queryStrings);
- Assert.Equal(expectedUri, result);
- }
+ var result = QueryHelpers.AddQueryString(uri, queryStrings);
+ Assert.Equal(expectedUri, result);
}
}
diff --git a/src/Http/WebUtilities/test/WebEncodersTests.cs b/src/Http/WebUtilities/test/WebEncodersTests.cs
index e619c3d597..e261c54aa9 100644
--- a/src/Http/WebUtilities/test/WebEncodersTests.cs
+++ b/src/Http/WebUtilities/test/WebEncodersTests.cs
@@ -4,61 +4,60 @@
using System;
using Xunit;
-namespace Microsoft.AspNetCore.WebUtilities
+namespace Microsoft.AspNetCore.WebUtilities;
+
+public class WebEncodersTests
{
- 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)
+ [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>(() =>
{
- // Act & assert
- Assert.ThrowsAny<ArgumentException>(() =>
- {
- var retVal = WebEncoders.Base64UrlDecode(input, offset, count);
- });
- }
+ 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];
+ [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);
- });
- }
+ // Act & assert
+ Assert.ThrowsAny<ArgumentException>(() =>
+ {
+ var retVal = WebEncoders.Base64UrlEncode(input, offset, count);
+ });
+ }
- [Fact]
- public void DataOfVariousLengthRoundTripCorrectly()
+ [Fact]
+ public void DataOfVariousLengthRoundTripCorrectly()
+ {
+ for (int length = 0; length != 256; ++length)
{
- for (int length = 0; length != 256; ++length)
+ var data = new byte[length];
+ for (int index = 0; index != length; ++index)
{
- 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);
+ 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]);
- }
+ for (int index = 0; index != length; ++index)
+ {
+ Assert.Equal(data[index], result[index]);
}
}
}
diff --git a/src/Http/samples/SampleApp/Program.cs b/src/Http/samples/SampleApp/Program.cs
index 6631cc36ce..55ffa3ebff 100644
--- a/src/Http/samples/SampleApp/Program.cs
+++ b/src/Http/samples/SampleApp/Program.cs
@@ -5,20 +5,19 @@ using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
-namespace SampleApp
+namespace SampleApp;
+
+public class Program
{
- public class Program
+ public static void Main(string[] args)
{
- public static void Main(string[] args)
- {
- var query = new QueryBuilder()
+ var query = new QueryBuilder()
{
{ "hello", "world" }
}.ToQueryString();
- var uri = UriHelper.BuildAbsolute("http", new HostString("contoso.com"), query: query);
+ var uri = UriHelper.BuildAbsolute("http", new HostString("contoso.com"), query: query);
- Console.WriteLine(uri);
- }
+ Console.WriteLine(uri);
}
}