Fix link generation of routes with default values (#8616)

This commit is contained in:
James Newton-King 2018-10-23 16:10:43 +13:00 committed by GitHub
parent 76a30b0911
commit 40959a97e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 985 additions and 174 deletions

View File

@ -7,7 +7,7 @@
is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product
dependencies. Do not use these properties elsewhere. dependencies. Do not use these properties elsewhere.
--> -->
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion> <AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion> <BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>2.1.1</BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion> <BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>2.1.1</BenchmarksOnlyMicrosoftEntityFrameworkCoreDesignPackageVersion>
@ -16,90 +16,90 @@
<BenchmarksOnlyMySqlConnectorPackageVersion>0.43.0</BenchmarksOnlyMySqlConnectorPackageVersion> <BenchmarksOnlyMySqlConnectorPackageVersion>0.43.0</BenchmarksOnlyMySqlConnectorPackageVersion>
<BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>2.1.1.1</BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion> <BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>2.1.1.1</BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>
<BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>2.1.1</BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion> <BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>2.1.1</BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>
<InternalAspNetCoreAnalyzersPackageVersion>2.2.0-preview3-35359</InternalAspNetCoreAnalyzersPackageVersion> <InternalAspNetCoreAnalyzersPackageVersion>2.2.0-rtm-35519</InternalAspNetCoreAnalyzersPackageVersion>
<MicrosoftAspNetCoreAllPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAllPackageVersion> <MicrosoftAspNetCoreAllPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAllPackageVersion>
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAnalyzerTestingPackageVersion> <MicrosoftAspNetCoreAnalyzerTestingPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
<MicrosoftAspNetCoreAntiforgeryPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAntiforgeryPackageVersion> <MicrosoftAspNetCoreAntiforgeryPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAntiforgeryPackageVersion>
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion> <MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAuthenticationCorePackageVersion> <MicrosoftAspNetCoreAuthenticationCorePackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion> <MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>
<MicrosoftAspNetCoreAuthenticationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAuthenticationPackageVersion> <MicrosoftAspNetCoreAuthenticationPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAuthenticationPackageVersion>
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion> <MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion> <MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion> <MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>
<MicrosoftAspNetCoreCookiePolicyPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreCookiePolicyPackageVersion> <MicrosoftAspNetCoreCookiePolicyPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreCookiePolicyPackageVersion>
<MicrosoftAspNetCoreCorsPackageVersion>2.2.0-a-preview3-22cors-16556</MicrosoftAspNetCoreCorsPackageVersion> <MicrosoftAspNetCoreCorsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreCorsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion> <MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreDiagnosticsPackageVersion> <MicrosoftAspNetCoreDiagnosticsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractions20PackageVersion>2.0.0</MicrosoftAspNetCoreHostingAbstractions20PackageVersion> <MicrosoftAspNetCoreHostingAbstractions20PackageVersion>2.0.0</MicrosoftAspNetCoreHostingAbstractions20PackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHostingPackageVersion> <MicrosoftAspNetCoreHostingAbstractionsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion> <MicrosoftAspNetCoreHostingPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpExtensionsPackageVersion> <MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreHttpPackageVersion> <MicrosoftAspNetCoreHttpExtensionsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreJsonPatchPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreJsonPatchPackageVersion> <MicrosoftAspNetCoreHttpPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreLocalizationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreLocalizationPackageVersion> <MicrosoftAspNetCoreJsonPatchPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreJsonPatchPackageVersion>
<MicrosoftAspNetCoreLocalizationRoutingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreLocalizationRoutingPackageVersion> <MicrosoftAspNetCoreLocalizationPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreLocalizationPackageVersion>
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion> <MicrosoftAspNetCoreLocalizationRoutingPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreLocalizationRoutingPackageVersion>
<MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion> <MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
<MicrosoftAspNetCoreRazorDesignPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRazorDesignPackageVersion> <MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>
<MicrosoftAspNetCoreRazorLanguagePackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRazorLanguagePackageVersion> <MicrosoftAspNetCoreRazorDesignPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreRazorDesignPackageVersion>
<MicrosoftAspNetCoreRazorRuntimePackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRazorRuntimePackageVersion> <MicrosoftAspNetCoreRazorLanguagePackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreRazorLanguagePackageVersion>
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion> <MicrosoftAspNetCoreRazorRuntimePackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreRazorRuntimePackageVersion>
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion> <MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreResponseCachingPackageVersion> <MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-preview3-address-scheme-17059</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion> <MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreResponseCachingPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-preview3-address-scheme-17059</MicrosoftAspNetCoreRoutingPackageVersion> <MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-rtm-allow-required-parameters-17081</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerIISIntegrationPackageVersion> <MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-rtm-allow-required-parameters-17081</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerKestrelPackageVersion> <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreSessionPackageVersion> <MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreStaticFilesPackageVersion> <MicrosoftAspNetCoreSessionPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreSessionPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestHostPackageVersion> <MicrosoftAspNetCoreStaticFilesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreTestingPackageVersion> <MicrosoftAspNetCoreTestHostPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreWebUtilitiesPackageVersion> <MicrosoftAspNetCoreTestingPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>2.2.0-rtm-35519</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion> <MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion>
<MicrosoftBuildUtilitiesCorePackageVersion>15.6.82</MicrosoftBuildUtilitiesCorePackageVersion> <MicrosoftBuildUtilitiesCorePackageVersion>15.6.82</MicrosoftBuildUtilitiesCorePackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpPackageVersion> <MicrosoftCodeAnalysisCSharpPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion> <MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<MicrosoftCodeAnalysisRazorPackageVersion>2.2.0-preview3-35359</MicrosoftCodeAnalysisRazorPackageVersion> <MicrosoftCodeAnalysisRazorPackageVersion>2.2.0-rtm-35519</MicrosoftCodeAnalysisRazorPackageVersion>
<MicrosoftDiaSymReaderNativePackageVersion>1.7.0</MicrosoftDiaSymReaderNativePackageVersion> <MicrosoftDiaSymReaderNativePackageVersion>1.7.0</MicrosoftDiaSymReaderNativePackageVersion>
<MicrosoftExtensionsCachingMemoryPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsCachingMemoryPackageVersion> <MicrosoftExtensionsCachingMemoryPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsCachingMemoryPackageVersion>
<MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion> <MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsConfigurationCommandLinePackageVersion> <MicrosoftExtensionsConfigurationCommandLinePackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsConfigurationJsonPackageVersion> <MicrosoftExtensionsConfigurationJsonPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsConfigurationPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsConfigurationPackageVersion> <MicrosoftExtensionsConfigurationPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsConfigurationPackageVersion>
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion> <MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsDependencyInjectionPackageVersion> <MicrosoftExtensionsDependencyInjectionPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsDependencyModelPackageVersion>2.1.0</MicrosoftExtensionsDependencyModelPackageVersion> <MicrosoftExtensionsDependencyModelPackageVersion>2.1.0</MicrosoftExtensionsDependencyModelPackageVersion>
<MicrosoftExtensionsDiagnosticAdapterPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsDiagnosticAdapterPackageVersion> <MicrosoftExtensionsDiagnosticAdapterPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsDiagnosticAdapterPackageVersion>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion> <MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<MicrosoftExtensionsFileProvidersCompositePackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsFileProvidersCompositePackageVersion> <MicrosoftExtensionsFileProvidersCompositePackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsFileProvidersCompositePackageVersion>
<MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion> <MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsFileSystemGlobbingPackageVersion> <MicrosoftExtensionsFileSystemGlobbingPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion> <MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsLocalizationPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLocalizationPackageVersion> <MicrosoftExtensionsLocalizationPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsLocalizationPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingAbstractionsPackageVersion> <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingConsolePackageVersion> <MicrosoftExtensionsLoggingConsolePackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingDebugPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingDebugPackageVersion> <MicrosoftExtensionsLoggingDebugPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsLoggingDebugPackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingPackageVersion> <MicrosoftExtensionsLoggingPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsLoggingTestingPackageVersion> <MicrosoftExtensionsLoggingTestingPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion> <MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsOptionsPackageVersion> <MicrosoftExtensionsOptionsPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion> <MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>
<MicrosoftExtensionsPrimitivesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsPrimitivesPackageVersion> <MicrosoftExtensionsPrimitivesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsPrimitivesPackageVersion>
<MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion> <MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsPropertyHelperSourcesPackageVersion> <MicrosoftExtensionsPropertyHelperSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
<MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsSecurityHelperSourcesPackageVersion> <MicrosoftExtensionsSecurityHelperSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion> <MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
<MicrosoftExtensionsValueStopwatchSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsValueStopwatchSourcesPackageVersion> <MicrosoftExtensionsValueStopwatchSourcesPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-preview3-35359</MicrosoftExtensionsWebEncodersPackageVersion> <MicrosoftExtensionsWebEncodersPackageVersion>2.2.0-rtm-35519</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion> <MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion> <MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-26927-02</MicrosoftNETCoreApp22PackageVersion> <MicrosoftNETCoreApp22PackageVersion>2.2.0-preview3-27014-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>2.2.0-preview3-35359</MicrosoftNetHttpHeadersPackageVersion> <MicrosoftNetHttpHeadersPackageVersion>2.2.0-rtm-35519</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETSdkRazorPackageVersion>2.2.0-preview3-35359</MicrosoftNETSdkRazorPackageVersion> <MicrosoftNETSdkRazorPackageVersion>2.2.0-rtm-35519</MicrosoftNETSdkRazorPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion> <MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion> <MoqPackageVersion>4.10.0</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion> <NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonBsonPackageVersion>1.0.1</NewtonsoftJsonBsonPackageVersion> <NewtonsoftJsonBsonPackageVersion>1.0.1</NewtonsoftJsonBsonPackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion> <NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>

View File

@ -222,26 +222,63 @@ namespace Microsoft.AspNetCore.Mvc.Internal
bool suppressPathMatching) bool suppressPathMatching)
{ {
var newPathSegments = routePattern.PathSegments.ToList(); var newPathSegments = routePattern.PathSegments.ToList();
var hasLinkGenerationEndpoint = false;
// Create a mutable copy
var nonInlineDefaultsCopy = nonInlineDefaults != null
? new RouteValueDictionary(nonInlineDefaults)
: null;
var resolvedRouteValues = ResolveActionRouteValues(action, allDefaults);
for (var i = 0; i < newPathSegments.Count; i++) for (var i = 0; i < newPathSegments.Count; i++)
{ {
// Check if the pattern can be shortened because the remaining parameters are optional // Check if the pattern can be shortened because the remaining parameters are optional
// //
// e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index // e.g. Matching pattern {controller=Home}/{action=Index} against HomeController.Index
// can resolve to the following endpoints: // can resolve to the following endpoints: (sorted by RouteEndpoint.Order)
// - /Home/Index/{id?}
// - /Home
// - / // - /
if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, allDefaults, newPathSegments)) // - /Home
// - /Home/Index
if (UseDefaultValuePlusRemainingSegmentsOptional(
i,
action,
resolvedRouteValues,
allDefaults,
ref nonInlineDefaultsCopy,
newPathSegments))
{ {
// The route pattern has matching default values AND an optional parameter
// For link generation we need to include an endpoint with parameters and default values
// so the link is correctly shortened
// e.g. {controller=Home}/{action=Index}/{id=17}
if (!hasLinkGenerationEndpoint)
{
var ep = CreateEndpoint(
action,
resolvedRouteValues,
name,
GetPattern(ref patternStringBuilder, newPathSegments),
newPathSegments,
nonInlineDefaultsCopy,
routeOrder++,
dataTokens,
suppressLinkGeneration,
true);
endpoints.Add(ep);
hasLinkGenerationEndpoint = true;
}
var subPathSegments = newPathSegments.Take(i); var subPathSegments = newPathSegments.Take(i);
var subEndpoint = CreateEndpoint( var subEndpoint = CreateEndpoint(
action, action,
resolvedRouteValues,
name, name,
GetPattern(ref patternStringBuilder, subPathSegments), GetPattern(ref patternStringBuilder, subPathSegments),
subPathSegments, subPathSegments,
nonInlineDefaults, nonInlineDefaultsCopy,
routeOrder++, routeOrder++,
dataTokens, dataTokens,
suppressLinkGeneration, suppressLinkGeneration,
@ -249,15 +286,83 @@ namespace Microsoft.AspNetCore.Mvc.Internal
endpoints.Add(subEndpoint); endpoints.Add(subEndpoint);
} }
List<RoutePatternPart> segmentParts = null; // Initialize only as needed UpdatePathSegments(i, action, resolvedRouteValues, routePattern, newPathSegments, ref allParameterPolicies);
var segment = newPathSegments[i]; }
for (var j = 0; j < segment.Parts.Count; j++)
{
var part = segment.Parts[j];
if (part.IsParameter && var finalEndpoint = CreateEndpoint(
part is RoutePatternParameterPart parameterPart && action,
action.RouteValues.ContainsKey(parameterPart.Name)) resolvedRouteValues,
name,
GetPattern(ref patternStringBuilder, newPathSegments),
newPathSegments,
nonInlineDefaultsCopy,
routeOrder++,
dataTokens,
suppressLinkGeneration,
suppressPathMatching);
endpoints.Add(finalEndpoint);
return routeOrder;
string GetPattern(ref StringBuilder sb, IEnumerable<RoutePatternPathSegment> segments)
{
if (sb == null)
{
sb = new StringBuilder();
}
RoutePatternWriter.WriteString(sb, segments);
var rawPattern = sb.ToString();
sb.Length = 0;
return rawPattern;
}
}
private static IDictionary<string, string> ResolveActionRouteValues(ActionDescriptor action, IReadOnlyDictionary<string, object> allDefaults)
{
Dictionary<string, string> resolvedRequiredValues = null;
foreach (var kvp in action.RouteValues)
{
// Check whether there is a matching default value with a different case
// e.g. {controller=HOME}/{action} with HomeController.Index will have route values:
// - controller = HOME
// - action = Index
if (allDefaults.TryGetValue(kvp.Key, out var value) &&
value is string defaultValue &&
!string.Equals(kvp.Value, defaultValue, StringComparison.Ordinal) &&
string.Equals(kvp.Value, defaultValue, StringComparison.OrdinalIgnoreCase))
{
if (resolvedRequiredValues == null)
{
resolvedRequiredValues = new Dictionary<string, string>(action.RouteValues, StringComparer.OrdinalIgnoreCase);
}
resolvedRequiredValues[kvp.Key] = defaultValue;
}
}
return resolvedRequiredValues ?? action.RouteValues;
}
private void UpdatePathSegments(
int i,
ActionDescriptor action,
IDictionary<string, string> resolvedRequiredValues,
RoutePattern routePattern,
List<RoutePatternPathSegment> newPathSegments,
ref IDictionary<string, IList<IParameterPolicy>> allParameterPolicies)
{
List<RoutePatternPart> segmentParts = null; // Initialize only as needed
var segment = newPathSegments[i];
for (var j = 0; j < segment.Parts.Count; j++)
{
var part = segment.Parts[j];
if (part is RoutePatternParameterPart parameterPart)
{
if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue))
{ {
if (segmentParts == null) if (segmentParts == null)
{ {
@ -268,9 +373,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory);
} }
// Replace parameter with literal value
var parameterRouteValue = action.RouteValues[parameterPart.Name];
// Route value could be null if it is a "known" route value. // Route value could be null if it is a "known" route value.
// Do not use the null value to de-normalize the route pattern, // Do not use the null value to de-normalize the route pattern,
// instead leave the parameter unchanged. // instead leave the parameter unchanged.
@ -297,47 +399,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
} }
} }
// A parameter part was replaced so replace segment with updated parts
if (segmentParts != null)
{
newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
}
} }
var endpoint = CreateEndpoint( // A parameter part was replaced so replace segment with updated parts
action, if (segmentParts != null)
name,
GetPattern(ref patternStringBuilder, newPathSegments),
newPathSegments,
nonInlineDefaults,
routeOrder++,
dataTokens,
suppressLinkGeneration,
suppressPathMatching);
endpoints.Add(endpoint);
return routeOrder;
string GetPattern(ref StringBuilder sb, IEnumerable<RoutePatternPathSegment> segments)
{ {
if (sb == null) newPathSegments[i] = RoutePatternFactory.Segment(segmentParts);
{
sb = new StringBuilder();
}
RoutePatternWriter.WriteString(sb, segments);
var rawPattern = sb.ToString();
sb.Length = 0;
return rawPattern;
} }
} }
private bool UseDefaultValuePlusRemainingSegmentsOptional( private bool UseDefaultValuePlusRemainingSegmentsOptional(
int segmentIndex, int segmentIndex,
ActionDescriptor action, ActionDescriptor action,
IDictionary<string, string> resolvedRequiredValues,
IReadOnlyDictionary<string, object> allDefaults, IReadOnlyDictionary<string, object> allDefaults,
ref RouteValueDictionary nonInlineDefaults,
List<RoutePatternPathSegment> pathSegments) List<RoutePatternPathSegment> pathSegments)
{ {
// Check whether the remaining segments are all optional and one or more of them is // Check whether the remaining segments are all optional and one or more of them is
@ -352,22 +428,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var part = segment.Parts[j]; var part = segment.Parts[j];
if (part.IsParameter && part is RoutePatternParameterPart parameterPart) if (part.IsParameter && part is RoutePatternParameterPart parameterPart)
{ {
if (parameterPart.IsOptional || parameterPart.IsCatchAll) if (allDefaults.TryGetValue(parameterPart.Name, out var v))
{ {
continue; if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var routeValue))
}
if (action.RouteValues.ContainsKey(parameterPart.Name))
{
if (allDefaults.TryGetValue(parameterPart.Name, out var v)
&& v is string defaultValue
&& action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue)
&& string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase))
{ {
if (string.Equals(v as string, routeValue, StringComparison.OrdinalIgnoreCase))
{
usedDefaultValue = true;
continue;
}
}
else
{
if (nonInlineDefaults == null)
{
nonInlineDefaults = new RouteValueDictionary();
}
nonInlineDefaults.TryAdd(parameterPart.Name, v);
usedDefaultValue = true; usedDefaultValue = true;
continue; continue;
} }
} }
if (parameterPart.IsOptional || parameterPart.IsCatchAll)
{
continue;
}
} }
else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart
&& separatorPart.Content == ".") && separatorPart.Content == ".")
@ -441,6 +528,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private RouteEndpoint CreateEndpoint( private RouteEndpoint CreateEndpoint(
ActionDescriptor action, ActionDescriptor action,
IDictionary<string, string> actionRouteValues,
string routeName, string routeName,
string patternRawText, string patternRawText,
IEnumerable<RoutePatternPathSegment> segments, IEnumerable<RoutePatternPathSegment> segments,
@ -461,12 +549,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}; };
var defaults = new RouteValueDictionary(nonInlineDefaults); var defaults = new RouteValueDictionary(nonInlineDefaults);
EnsureRequiredValuesInDefaults(action.RouteValues, defaults); EnsureRequiredValuesInDefaults(actionRouteValues, defaults, segments);
var metadataCollection = BuildEndpointMetadata( var metadataCollection = BuildEndpointMetadata(
action, action,
routeName, routeName,
new RouteValueDictionary(action.RouteValues), new RouteValueDictionary(actionRouteValues),
dataTokens, dataTokens,
suppressLinkGeneration, suppressLinkGeneration,
suppressPathMatching); suppressPathMatching);
@ -554,7 +642,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return metadataCollection; return metadataCollection;
} }
// Ensure required values are a subset of defaults // Ensure route values are a subset of defaults
// Examples: // Examples:
// //
// Template: {controller}/{action}/{category}/{id?} // Template: {controller}/{action}/{category}/{id?}
@ -568,9 +656,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Required values: controller=foo, action=bar // Required values: controller=foo, action=bar
// Final constructed pattern: foo/bar/{category}/{id?} // Final constructed pattern: foo/bar/{category}/{id?}
// Final defaults: controller=foo, action=bar, category=products // Final defaults: controller=foo, action=bar, category=products
private void EnsureRequiredValuesInDefaults(IDictionary<string, string> requiredValues, RouteValueDictionary defaults) private void EnsureRequiredValuesInDefaults(
IDictionary<string, string> routeValues,
RouteValueDictionary defaults,
IEnumerable<RoutePatternPathSegment> segments)
{ {
foreach (var kvp in requiredValues) foreach (var kvp in routeValues)
{ {
if (kvp.Value != null) if (kvp.Value != null)
{ {

View File

@ -40,6 +40,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
if (parameterPart.IsCatchAll) if (parameterPart.IsCatchAll)
{ {
sb.Append("*"); sb.Append("*");
if (!parameterPart.EncodeSlashes)
{
sb.Append("*");
}
} }
sb.Append(parameterPart.Name); sb.Append(parameterPart.Name);
foreach (var item in parameterPart.ParameterPolicies) foreach (var item in parameterPart.ParameterPolicies)

View File

@ -146,22 +146,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private static TheoryData GetSingleActionData(bool isConventionalRouting) private static TheoryData GetSingleActionData(bool isConventionalRouting)
{ {
var data = new TheoryData<string, string[]> var data = new TheoryData<string, string, string[]>
{ {
{"{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" }}, {"{controller}/{action}/{id?}", null, new[] { "TestController/TestAction/{id?}" }},
{"{controller}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }}, {"{controller}/{id?}", null, isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }},
{"{action}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }}, {"{action}/{id?}", null, isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }},
{"{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" }}, {"{Controller}/{Action}/{id?}", null, new[] { "TestController/TestAction/{id?}" }},
{"{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" }}, {"{Controller}/{Action}/{id?}/{more?}", null, new[] { "TestController/TestAction/{id?}/{more?}" }},
{"{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }}, {"{CONTROLLER}/{ACTION}/{id?}", null, new[] { "TestController/TestAction/{id?}" }},
{"{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }}, {"{controller}/{action=TestAction}", "TestController/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }},
{"{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }}, {"{controller}/{action=TestAction}/{id?}", "TestController/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }},
{"{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" }}, {"{controller}/{action=TESTACTION}/{id?}", "TestController/{action=TESTACTION}/{id?}", new[] { "TestController", "TestController/TESTACTION/{id?}" }},
{"{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }}, {"{controller}/{action=TestAction}/{id?}/{more}", null, new[] { "TestController/TestAction/{id?}/{more}" }},
{"{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }}, {"{controller=TestController}/{action=TestAction}/{id?}", "{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }},
{"{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" }}, {"{controller=TestController}/{action=TestAction}/{id?}/{more?}", "{controller=TestController}/{action=TestAction}/{id?}/{more?}", new[] { "", "TestController", "TestController/TestAction/{id?}/{more?}" }},
{"{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }}, {"{controller}/{action}/{*catchAll}", null, new[] { "TestController/TestAction/{*catchAll}" }},
{"{controller:upper-case}/{action=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TestAction.{ext?}" }}, {"{controller}/{action=TestAction}/{*catchAll}", "TestController/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }},
{"{controller}/{action=TestAction}/{id?}/{*catchAll}", "TestController/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }},
{"{controller}/{action=TestAction}/{id?}/{**catchAll}", "TestController/{action=TestAction}/{id?}/{**catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{**catchAll}" }},
{"{controller}/{action}.{ext?}", null, new[] { "TestController/TestAction.{ext?}" }},
{"{controller}/{action=TestAction}.{ext?}", "TestController/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }},
{"{controller}/{action=TestAction}.{ext?}/{more?}", "TestController/{action=TestAction}.{ext?}/{more?}", new[] { "TestController", "TestController/TestAction.{ext?}/{more?}" }},
{"{controller}/{action=TestAction}.{ext?}/{more}", null, new[] { "TestController/TestAction.{ext?}/{more}" }},
{"{controller:upper-case}/{action:upper-case=TestAction}.{ext?}", "TESTCONTROLLER/{action:upper-case=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TESTACTION.{ext?}" }},
}; };
return data; return data;
@ -169,7 +176,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
[Theory] [Theory]
[MemberData(nameof(GetSingleActionData_Conventional))] [MemberData(nameof(GetSingleActionData_Conventional))]
public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointPatterns)
{ {
// Arrange // Arrange
var actionDescriptorCollection = GetActionDescriptorCollection( var actionDescriptorCollection = GetActionDescriptorCollection(
@ -178,9 +185,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute));
// Act // Act
var endpoints = dataSource.Endpoints; var endpoints = dataSource.Endpoints.ToList();
// Assert // Assert
// Ensure there are no endpoints with duplicate Order values
Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType<RouteEndpoint>(e).Order), g => g.Count() > 1);
endpoints = endpoints.OrderBy(e => Assert.IsType<RouteEndpoint>(e).Order).ToList();
AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints);
var inspectors = finalEndpointPatterns var inspectors = finalEndpointPatterns
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText))) .Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
.ToArray(); .ToArray();
@ -191,7 +206,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
[Theory] [Theory]
[MemberData(nameof(GetSingleActionData_Attribute))] [MemberData(nameof(GetSingleActionData_Attribute))]
public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointPatterns)
{ {
// Arrange // Arrange
var actionDescriptorCollection = GetActionDescriptorCollection( var actionDescriptorCollection = GetActionDescriptorCollection(
@ -200,7 +215,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
// Act // Act
var endpoints = dataSource.Endpoints; var endpoints = dataSource.Endpoints.ToList();
// Ensure there are no endpoints with duplicate Order values
Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType<RouteEndpoint>(e).Order), g => g.Count() > 1);
endpoints = endpoints.OrderBy(e => Assert.IsType<RouteEndpoint>(e).Order).ToList();
AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints);
// Assert // Assert
var inspectors = finalEndpointPatterns var inspectors = finalEndpointPatterns
@ -212,14 +234,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
[Theory] [Theory]
[InlineData("{area}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })]
[InlineData("{controller}/{action}/{id?}", new string[] { })] [InlineData("{controller}/{action}/{id?}", null, new string[] { })]
[InlineData("{area=TestArea}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area=TestArea}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })]
[InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", "TestArea/TestController/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}"})]
[InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", "{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })]
[InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area:exists}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })]
[InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", new[] { "TESTAREA/TestController/TestAction/{id?}" })] [InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", null, new[] { "TESTAREA/TestController/TestAction/{id?}" })]
public void Endpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) public void Endpoints_AreaSingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointTemplates)
{ {
// Arrange // Arrange
var actionDescriptorCollection = GetActionDescriptorCollection( var actionDescriptorCollection = GetActionDescriptorCollection(
@ -240,9 +262,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider())); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider()));
// Act // Act
var endpoints = dataSource.Endpoints; var endpoints = dataSource.Endpoints.ToList();
// Assert // Assert
// Ensure there are no endpoints with duplicate Order values
Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType<RouteEndpoint>(e).Order), g => g.Count() > 1);
endpoints = endpoints.OrderBy(e => Assert.IsType<RouteEndpoint>(e).Order).ToList();
AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints);
var inspectors = finalEndpointTemplates var inspectors = finalEndpointTemplates
.Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText))) .Select(t => new Action<Endpoint>(e => Assert.Equal(t, Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)))
.ToArray(); .ToArray();
@ -251,6 +281,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Collection(endpoints, inspectors); Assert.Collection(endpoints, inspectors);
} }
private static void AssertSuppressMatchingTemplate(string suppressMatchingTemplate, List<Endpoint> endpoints)
{
if (suppressMatchingTemplate != null)
{
var suppressMatchingEndpoint = endpoints.First();
Assert.True(suppressMatchingEndpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching);
Assert.Equal(suppressMatchingTemplate, Assert.IsType<RouteEndpoint>(suppressMatchingEndpoint).RoutePattern.RawText);
endpoints.Remove(suppressMatchingEndpoint);
}
}
[Fact] [Fact]
public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue() public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue()
{ {
@ -304,6 +345,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Assert // Assert
Assert.Collection(endpoints, Assert.Collection(endpoints,
(e) =>
{
Assert.Equal("TestController/{action=TestAction}", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText);
Assert.True(e.Metadata.GetMetadata<ISuppressMatchingMetadata>().SuppressMatching);
},
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText), (e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)); (e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
} }
@ -332,6 +378,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
// Assert // Assert
Assert.Collection(endpoints1, Assert.Collection(endpoints1,
(e) => Assert.Equal("TestController/{action=TestAction}", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText), (e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)); (e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
Assert.Same(endpoints1, endpoints2); Assert.Same(endpoints1, endpoints2);
@ -373,6 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var endpoints = dataSource.Endpoints; var endpoints = dataSource.Endpoints;
Assert.Collection(endpoints, Assert.Collection(endpoints,
(e) => Assert.Equal("TestController/{action=TestAction}", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
(e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText), (e) => Assert.Equal("TestController", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText),
(e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText)); (e) => Assert.Equal("TestController/TestAction", Assert.IsType<RouteEndpoint>(e).RoutePattern.RawText));
@ -720,10 +768,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var endpoints = dataSource.Endpoints; var endpoints = dataSource.Endpoints;
// Assert // Assert
var endpoint = Assert.Single(endpoints); Assert.Collection(
var matcherEndpoint = Assert.IsType<RouteEndpoint>(endpoint); endpoints,
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText); (ep) =>
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); {
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
} }
[Fact] [Fact]
@ -780,6 +847,464 @@ namespace Microsoft.AspNetCore.Mvc.Internal
AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults);
} }
[Fact]
public void Endpoints_ConventionalRoutes_NonDefaultAndDefaultValuesEndingWithOptional_IncludeFullRouteAsHighPriority()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "Home", action = "Index" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller}/{action=Index}/{id?}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Home/{action=Index}/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Home", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_ConventionalRoutes_DefaultValuesEndingWithOptional_IncludeFullRouteAsHighPriority()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "Home", action = "Index" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller=Home}/{action=Index}/{id?}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("{controller=Home}/{action=Index}/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Home", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(4, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_ConventionalRoutes_DefaultValues_Shortened()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller=TestController}/{action=TestAction}/{id=17}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(1, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(2, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(3, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(4, matcherEndpoint.Order);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction/{id=17}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(5, matcherEndpoint.Order);
});
}
[Fact]
public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_EndpointInfoDefaultsNotModified()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
var endpointInfo = CreateEndpointInfo(
name: string.Empty,
defaults: new RouteValueDictionary(),
template: "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}");
dataSource.ConventionalEndpointInfos.Add(endpointInfo);
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Empty(endpointInfo.Defaults);
}
[Fact]
public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_Shortened()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(4, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction/{id=17}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(5, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_ConventionalRoutes_DefaultValuesAndOptional_Shortened()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller=TestController}/{action=TestAction}/{id=17}/{more?}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}/{more?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(4, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction/{id=17}/{more?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]);
Assert.Equal(5, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_ConventionalRoutes_OptionalExtension_IncludeFullRouteAsHighPriority()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller}/{action=TestAction}.{ext?}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/{action=TestAction}.{ext?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction.{ext?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_ConventionalRoutes_MultipleOptionalAndCatchAll_IncludeFullRouteAsHighPriority()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(
name: string.Empty,
template: "{controller=TestController}/{action=TestAction}/{id?}/{more?}/{**catchAll}"));
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("{controller=TestController}/{action=TestAction}/{id?}/{more?}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(3, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TestAction/{id?}/{more?}/{**catchAll}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(4, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_AttributeRoutes_CatchAllWithDefault_IncludeFullRouteAsHighPriority()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
"/TeamName/{*Name=DefaultName}/",
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TeamName/{*Name=DefaultName}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal(0, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TeamName", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("DefaultName", matcherEndpoint.RoutePattern.Defaults["Name"]);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TeamName/{*Name=DefaultName}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("DefaultName", matcherEndpoint.RoutePattern.Defaults["Name"]);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
});
}
[Fact]
public void Endpoints_AttributeRoutes_DefaultDifferentCaseFromRouteValue_UseDefaultCase()
{
// Arrange
var actionDescriptorCollection = GetActionDescriptorCollection(
"{controller}/{action=TESTACTION}/{id?}",
new { controller = "TestController", action = "TestAction" });
var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection);
// Act
var endpoints = dataSource.Endpoints;
// Assert
Assert.Collection(
endpoints,
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/{action=TESTACTION}/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
Assert.Equal(0, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, true);
var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
Assert.Equal(1, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]);
},
(ep) =>
{
var matcherEndpoint = Assert.IsType<RouteEndpoint>(ep);
Assert.Equal("TestController/TESTACTION/{id?}", matcherEndpoint.RoutePattern.RawText);
Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]);
Assert.Equal(2, matcherEndpoint.Order);
AssertMatchingSuppressed(matcherEndpoint, false);
var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]);
});
}
private MvcEndpointDataSource CreateMvcEndpointDataSource( private MvcEndpointDataSource CreateMvcEndpointDataSource(
IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null,
MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null)
@ -898,5 +1423,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(subsetPair.Value, fullSetPairValue); Assert.Equal(subsetPair.Value, fullSetPairValue);
} }
} }
private void AssertMatchingSuppressed(Endpoint endpoint, bool suppressed)
{
var isEndpointSuppressed = endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching ?? false;
Assert.Equal(suppressed, isEndpointSuppressed);
}
} }
} }

View File

@ -1041,6 +1041,136 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("Departments", result.RouteName); Assert.Equal("Departments", result.RouteName);
} }
[Fact]
public async Task ConventionalRoutedAction_DefaultValues_OptionalParameter_LinkToDefaultValuePath()
{
// Arrange
var url = LinkFrom("http://localhost/DefaultValuesRoute/Optional")
.To(new { });
// Act
var response = await Client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Equal("DefaultValues", result.Controller);
Assert.Equal("OptionalParameter", result.Action);
Assert.Equal("/DefaultValuesRoute/Optional", result.Link);
}
[Fact]
public async Task ConventionalRoutedAction_DefaultValues_OptionalParameter_LinkToFullPath()
{
// Arrange
var url = LinkFrom("http://localhost/DefaultValuesRoute/Optional")
.To(new { id = "123" });
// Act
var response = await Client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Equal("DefaultValues", result.Controller);
Assert.Equal("OptionalParameter", result.Action);
Assert.Equal("/DefaultValuesRoute/Optional/DEFAULTVALUES/OPTIONALPARAMETER/123", result.Link);
}
[Fact]
public async Task ConventionalRoutedAction_DefaultValues_DefaultParameter_LinkToDefaultValuePath()
{
// Arrange
var url = LinkFrom("http://localhost/DefaultValuesRoute/Default")
.To(new { });
// Act
var response = await Client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Equal("DefaultValues", result.Controller);
Assert.Equal("DefaultParameter", result.Action);
Assert.Equal("17", result.RouteValues["id"]);
Assert.Equal("/DefaultValuesRoute/Default", result.Link);
}
[Fact]
public async Task ConventionalRoutedAction_DefaultValues_DefaultParameterWithCatchAll_LinkToDefaultValuePath()
{
// Arrange
var url = LinkFrom("http://localhost/DefaultValuesRoute/Default")
.To(new { catchAll = "CatchAll" });
// Act
var response = await Client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Equal("DefaultValues", result.Controller);
Assert.Equal("DefaultParameter", result.Action);
Assert.Equal("17", result.RouteValues["id"]);
Assert.Equal("/DefaultValuesRoute/Default/DEFAULTVALUES/DEFAULTPARAMETER/17/CatchAll", result.Link);
}
[Fact]
public async Task ConventionalRoutedAction_DefaultValues_DefaultParameter_LinkToFullPath()
{
// Arrange
var url = LinkFrom("http://localhost/DefaultValuesRoute/Default")
.To(new { id = "123" });
// Act
var response = await Client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Equal("DefaultValues", result.Controller);
Assert.Equal("DefaultParameter", result.Action);
Assert.Equal("17", result.RouteValues["id"]);
Assert.Equal("/DefaultValuesRoute/Default/DEFAULTVALUES/DEFAULTPARAMETER/123", result.Link);
}
[Fact]
public async Task ConventionalRoutedAction_DefaultValues_DefaultParameterMatches_LinkToShortenedPath()
{
// Arrange
var url = LinkFrom("http://localhost/DefaultValuesRoute/Default/DefaultValues/DefaultParameter/123")
.To(new { id = "17" });
// Act
var response = await Client.GetAsync(url);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
Assert.Equal("DefaultValues", result.Controller);
Assert.Equal("DefaultParameter", result.Action);
Assert.Equal("123", result.RouteValues["id"]);
Assert.Equal("/DefaultValuesRoute/Default", result.Link);
}
[Fact] [Fact]
public virtual async Task ConventionalRoutedAction_LinkToArea() public virtual async Task ConventionalRoutedAction_LinkToArea()
{ {

View File

@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
namespace RoutingWebSite
{
public class DefaultValuesController : Controller
{
private readonly TestResponseGenerator _generator;
public DefaultValuesController(TestResponseGenerator generator)
{
_generator = generator;
}
public IActionResult DefaultParameter(string id)
{
return _generator.Generate(id == null
? "/DefaultValuesRoute/DefaultValues"
: "/DefaultValuesRoute/DefaultValues/DefaultParameter/Index/" + id);
}
public IActionResult OptionalParameter(string id)
{
return _generator.Generate(id == "17"
? "/DefaultValuesRoute/DefaultValues"
: "/DefaultValuesRoute/DefaultValues/OptionalParameter/Index/" + id);
}
}
}

View File

@ -62,6 +62,18 @@ namespace RoutingWebSite
defaults: null, defaults: null,
constraints: new { controller = "ConventionalTransformer" }); constraints: new { controller = "ConventionalTransformer" });
routes.MapRoute(
"DefaultValuesRoute_OptionalParameter",
"DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}",
defaults: null,
constraints: new { controller = "DefaultValues", action = "OptionalParameter" });
routes.MapRoute(
"DefaultValuesRoute_DefaultParameter",
"DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}",
defaults: null,
constraints: new { controller = "DefaultValues", action = "DefaultParameter" });
routes.MapAreaRoute( routes.MapAreaRoute(
"flightRoute", "flightRoute",
"adminRoute", "adminRoute",

View File

@ -1,14 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
@ -48,7 +45,7 @@ namespace RoutingWebSite
new ControllerToRemove new ControllerToRemove
{ {
ControllerType = typeof(PageRouteController), ControllerType = typeof(PageRouteController),
Actions = new [] { nameof(PageRouteController.AttributeRoute) } Actions = new[] { nameof(PageRouteController.AttributeRoute) }
}); });
services.TryAddEnumerable(ServiceDescriptor.Singleton<IActionDescriptorProvider>(actionDescriptorProvider)); services.TryAddEnumerable(ServiceDescriptor.Singleton<IActionDescriptorProvider>(actionDescriptorProvider));
} }
@ -64,6 +61,18 @@ namespace RoutingWebSite
constraints: new { controller = "DataTokens" }, constraints: new { controller = "DataTokens" },
dataTokens: new { hasDataTokens = true }); dataTokens: new { hasDataTokens = true });
routes.MapRoute(
"DefaultValuesRoute_OptionalParameter",
"DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}",
defaults: null,
constraints: new { controller = "DefaultValues", action = "OptionalParameter" });
routes.MapRoute(
"DefaultValuesRoute_DefaultParameter",
"DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}",
defaults: null,
constraints: new { controller = "DefaultValues", action = "DefaultParameter" });
routes.MapAreaRoute( routes.MapAreaRoute(
"flightRoute", "flightRoute",
"adminRoute", "adminRoute",