diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index b0c48605..e5d8caad 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -2,10 +2,10 @@
"version": 1,
"isRoot": true,
"tools": {
- "signclient": {
- "version": "1.3.155",
+ "sign": {
+ "version": "0.9.1-beta.23530.1",
"commands": [
- "SignClient"
+ "sign"
]
}
}
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 378b3123..e5eb2a2a 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,5 +1,5 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/dotnet/.devcontainer/base.Dockerfile
# [Choice] .NET version: 7.0, 6.0, 5.0
-ARG VARIANT="6.0"
+ARG VARIANT="8.0"
FROM mcr.microsoft.com/vscode/devcontainers/dotnet
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index c40d7ef5..0ee6f006 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -20,7 +20,7 @@
"remoteEnv": {
"PATH": "${containerWorkspaceFolder}/.dotnet:${containerEnv:PATH}",
"DOTNET_MULTILEVEL_LOOKUP": "0",
- "TARGET": "net6.0",
+ "TARGET": "net8.0",
"DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER": "true"
},
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
diff --git a/.editorconfig b/.editorconfig
index fe5a4a9e..68505e1c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -6,6 +6,8 @@ root = true
# don't use tabs for indentation
[*]
indent_style = space
+vsspell_section_id = 41b65011239a40959ccaae2a4ec7044a
+vsspell_ignored_words_41b65011239a40959ccaae2a4ec7044a = Accessor|app|clr|Edm|inline|middleware|Mvc|odata|Validator|Deconstruct
# code files
[*.{cs,csx,vb,vbx}]
@@ -88,8 +90,8 @@ csharp_space_between_method_declaration_parameter_list_parentheses = true
csharp_space_between_method_call_parameter_list_parentheses = true
csharp_space_between_parentheses = control_flow_statements, expressions
-# ide code suppressions
-# dotnet_diagnostic.IDE0079.severity = none
+# primary construcrtors
+csharp_style_prefer_primary_constructors = false:none
# style code suppressions
dotnet_diagnostic.SA1002.severity = none
@@ -110,6 +112,13 @@ dotnet_diagnostic.SA1502.severity = none
dotnet_diagnostic.SA1516.severity = none
dotnet_diagnostic.SA1600.severity = none
+# TEMP: currently suppressed rules due to false positives
+# REF: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687
+# REF: https://github.com/dotnet/aspnetcore/issues/52556
+dotnet_diagnostic.SA1010.severity = none # Opening square brackets should be spaced correctly
+dotnet_diagnostic.ASP0022.severity = none # Route conflict detected between route handlers
+dotnet_diagnostic.ASP0023.severity = none # Route conflict detected between route handlers
+
# test settings
# Default severity for analyzer diagnostics with category 'Reliability'
@@ -128,6 +137,7 @@ dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CA1711.severity = none
dotnet_diagnostic.CA1716.severity = none
dotnet_diagnostic.CA1806.severity = none
+dotnet_diagnostic.CA1861.severity = none
dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA2234.severity = none
dotnet_code_quality.CA2000.excluded_symbol_names = HttpRequestMessage|HttpResponseMessage|HttpConfiguration|HttpRouteCollection|HostedHttpRouteCollection|HttpServer|HttpClient
@@ -138,6 +148,13 @@ dotnet_diagnostic.SA1300.severity = none
dotnet_diagnostic.SA1507.severity = none
dotnet_diagnostic.SA1601.severity = none
+# TEMP: currently suppressed rules due to false positives
+# REF: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687
+# REF: https://github.com/dotnet/aspnetcore/issues/52556
+dotnet_diagnostic.SA1010.severity = none # Opening square brackets should be spaced correctly
+dotnet_diagnostic.ASP0022.severity = none # Route conflict detected between route handlers
+dotnet_diagnostic.ASP0023.severity = none # Route conflict detected between route handlers
+
# test methods should use all lowercase characters
dotnet_naming_symbols.test_methods.applicable_kinds = method
dotnet_naming_symbols.test_methods.applicable_accessibilities = public
@@ -180,4 +197,4 @@ dotnet_naming_style.test_methods.word_separator = _
dotnet_naming_rule.test_methods.style = test_methods
dotnet_naming_rule.test_methods.symbols = test_methods
-dotnet_naming_rule.test_methods.severity = error
\ No newline at end of file
+dotnet_naming_rule.test_methods.severity = error
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6978ee55..14290721 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -14,20 +14,4 @@ updates:
- "commonsensesoftware"
commit-message:
prefix: "[main] "
- include: scope
-
- # only servicing 5.x release
- - package-ecosystem: "nuget"
- directory: "/"
- target-branch: "release/5.0"
- schedule:
- interval: "monthly"
- allow:
- - dependency-type: "all"
- assignees:
- - "commonsensesoftware"
- reviewers:
- - "commonsensesoftware"
- commit-message:
- prefix: "[release/5.0] "
include: scope
\ No newline at end of file
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index f9b40dff..ec3be18c 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -29,8 +29,12 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
+ id: installdotnet
with:
- dotnet-version: 7.0.x
+ dotnet-version: 8.0.x
+
+ - name: Create temporary global.json
+ run: echo '{"sdk":{"version":"${{ steps.installdotnet.outputs.dotnet-version }}"}}' > ./global.json
# build a temporary *.slnf file that only contains source projects and put it in ~/obj
# so that it is not tracked by git. then run 'dotnet build' using the *.slnf, which
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4bce8bd4..86a81124 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -26,10 +26,10 @@
"dotnet-test-explorer.runInParallel": true,
"dotnet-test-explorer.testProjectPath": "**/*Tests.csproj",
"editor.formatOnType": true,
- "omnisharp.enableImportCompletion": true,
"omnisharp.enableRoslynAnalyzers": true,
"omnisharp.organizeImportsOnFormat": true,
"omnisharp.useModernNet": true,
"omnisharp.enableMsBuildLoadProjectsOnDemand": true,
- "omnisharp.enableEditorConfigSupport": true
+ "omnisharp.enableEditorConfigSupport": true,
+ "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true
}
\ No newline at end of file
diff --git a/README.md b/README.md
index c0284787..d6828747 100644
--- a/README.md
+++ b/README.md
@@ -23,22 +23,6 @@ versioning in the past or supported API versioning with semantics that are diffe
The supported flavors of ASP.NET are:
-* **ASP.NET Web API**
-
Adds API versioning to your Web API applications
-
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi)
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi)
- [](../../wiki/New-Services-Quick-Start#aspnet-web-api)
- [](../../tree/main/examples/AspNet/WebApi)
-
-* **ASP.NET Web API and OData**
- Adds API versioning to your Web API applications using OData v4.0
-
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData)
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData)
- [](../../wiki/New-Services-Quick-Start#aspnet-web-api-with-odata-v40)
- [](../../tree/main/examples/AspNet/OData)
-
* **ASP.NET Core**
Adds API versioning to your ASP.NET Core Minimal API applications
@@ -63,23 +47,23 @@ The supported flavors of ASP.NET are:
[](../../wiki/New-Services-Quick-Start#aspnet-core-with-odata-v40)
[](../../tree/main/examples/AspNetCore/OData)
-This is also the home of the ASP.NET API versioning API explorers that you can use to easily document your REST APIs with OpenAPI:
+* **ASP.NET Web API**
+ Adds API versioning to your Web API applications
-* **ASP.NET Web API Versioned API Explorer**
- Replaces the default API explorer in your Web API applications
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi)
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi)
+ [](../../wiki/New-Services-Quick-Start#aspnet-web-api)
+ [](../../tree/main/examples/AspNet/WebApi)
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi.ApiExplorer)
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi.ApiExplorer)
- [](../../wiki/API-Documentation#aspnet-web-api)
- [](../../tree/main/examples/AspNet/WebApi/OpenApiWebApiSample)
+* **ASP.NET Web API and OData**
+ Adds API versioning to your Web API applications using OData v4.0
-* **ASP.NET Web API with OData API Explorer**
- Adds an API explorer to your Web API applications using OData v4.0
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData)
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData)
+ [](../../wiki/New-Services-Quick-Start#aspnet-web-api-with-odata-v40)
+ [](../../tree/main/examples/AspNet/OData)
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData.ApiExplorer)
- [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData.ApiExplorer)
- [](../../wiki/API-Documentation#aspnet-web-api-with-odata)
- [](../../tree/main/examples/AspNet/OData/OpenApiODataWebApiSample)
+This is also the home of the ASP.NET API versioning API explorers that you can use to easily document your REST APIs with OpenAPI:
* **ASP.NET Core Versioned API Explorer**
Adds additional API explorer support to your ASP.NET Core applications
@@ -97,6 +81,22 @@ This is also the home of the ASP.NET API versioning API explorers that you can u
[](../../wiki/API-Documentation#aspnet-core-with-odata)
[](../../tree/main/examples/AspNetCore/OData/OpenApiODataSample)
+* **ASP.NET Web API Versioned API Explorer**
+ Replaces the default API explorer in your Web API applications
+
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi.ApiExplorer)
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi.ApiExplorer)
+ [](../../wiki/API-Documentation#aspnet-web-api)
+ [](../../tree/main/examples/AspNet/WebApi/OpenApiWebApiSample)
+
+* **ASP.NET Web API with OData API Explorer**
+ Adds an API explorer to your Web API applications using OData v4.0
+
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData.ApiExplorer)
+ [](https://www.nuget.org/packages/Asp.Versioning.WebApi.OData.ApiExplorer)
+ [](../../wiki/API-Documentation#aspnet-web-api-with-odata)
+ [](../../tree/main/examples/AspNet/OData/OpenApiODataWebApiSample)
+
The client-side libraries make it simple to create API version-aware HTTP clients.
* **HTTP Client API Versioning Extensions**
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index a9aa3dc4..f75e041a 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -2,6 +2,7 @@ trigger:
branches:
include:
- main
+ - release/*
paths:
exclude:
- .config
@@ -16,6 +17,7 @@ trigger:
pr:
- main
+- release/*
# build at least once a month so the build badge is up-to-date
schedules:
diff --git a/build/nuget.props b/build/nuget.props
index c913c57b..f679d6d3 100644
--- a/build/nuget.props
+++ b/build/nuget.props
@@ -26,16 +26,17 @@
+ true
+ snupkg
true
true
- $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
true
-
+
diff --git a/build/signing.json b/build/signing.json
deleted file mode 100644
index 3276a45d..00000000
--- a/build/signing.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "SignClient": {
- "AzureAd": {
- "AADInstance": "https://login.microsoftonline.com/",
- "ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8",
- "TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e"
- },
- "Service": {
- "Url": "https://codesign.dotnetfoundation.org/",
- "ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001"
- }
- }
-}
\ No newline at end of file
diff --git a/build/steps-ci.yml b/build/steps-ci.yml
index 7ec23ad6..c2304d7d 100644
--- a/build/steps-ci.yml
+++ b/build/steps-ci.yml
@@ -11,7 +11,7 @@ steps:
displayName: Install .NET SDK
inputs:
packageType: sdk
- version: 7.0.x # https://github.com/dotnet/core/blob/main/release-notes/releases-index.json
+ version: 8.0.x # https://github.com/dotnet/core/blob/main/release-notes/releases-index.json
- task: DotNetCoreCLI@2
displayName: Build and Test
diff --git a/build/steps-release.yml b/build/steps-release.yml
index 60fd3c3a..ca070468 100644
--- a/build/steps-release.yml
+++ b/build/steps-release.yml
@@ -16,41 +16,32 @@ steps:
command: pack
projects: ${{ parameters.solution }}
${{ if eq(parameters.versionSuffix, '') }}:
- arguments: --configuration ${{ parameters.configuration }}
+ arguments: --no-build --configuration ${{ parameters.configuration }}
${{ else }}:
- arguments: --configuration ${{ parameters.configuration }} --version-suffix ${{ parameters.versionSuffix }}
+ arguments: --no-build --configuration ${{ parameters.configuration }} --version-suffix ${{ parameters.versionSuffix }}
outputDir: $(Build.ArtifactStagingDirectory)/packages
noBuild: true
- script: dotnet tool restore
displayName: Restore Tools
-- pwsh: >
- Compress-Archive
- -Path $(Build.ArtifactStagingDirectory)/packages/*
- -DestinationPath $(Build.ArtifactStagingDirectory)/packages.zip
- displayName: Package Artifacts for Signing
-
- script: >
- dotnet signclient sign
- --config build/signing.json
- --input $(Build.ArtifactStagingDirectory)/packages.zip
- --user "$(codesign_user)"
- --secret "$(codesign_secret)"
- --name "ASP.NET API Versioning"
+ dotnet sign code azure-key-vault "*.nupkg"
+ --base-directory "$(Build.ArtifactStagingDirectory)/packages"
+ --publisher-name "ASP.NET API Versioning"
--description "Adds versioning semantics to APIs built with ASP.NET"
- --descriptionUrl "https://github.com/dotnet/aspnet-api-versioning"
+ --description-url "https://github.com/dotnet/aspnet-api-versioning"
+ --azure-key-vault-tenant-id "$(SignTenantId)"
+ --azure-key-vault-client-id "$(SignClientId)"
+ --azure-key-vault-client-secret "$(SignClientSecret)"
+ --azure-key-vault-certificate "$(SignKeyVaultCertificate)"
+ --azure-key-vault-url "$(SignKeyVaultUrl)"
+ --timestamp-url http://timestamp.digicert.com
displayName: Sign Artifacts
-- pwsh: >
- Expand-Archive
- -Path $(Build.ArtifactStagingDirectory)/packages.zip
- -DestinationPath $(Build.ArtifactStagingDirectory)/signed-packages
- displayName: Extract Signed Artifacts
-
- task: PublishBuildArtifacts@1
- displayName: Publish package artifacts
+ displayName: Publish Artifacts
inputs:
- pathToPublish: $(Build.ArtifactStagingDirectory)/signed-packages
+ pathToPublish: $(Build.ArtifactStagingDirectory)/packages
publishLocation: Container
artifactName: NuGet Packages
\ No newline at end of file
diff --git a/build/test.targets b/build/test.targets
index 8616ce63..a3fc5fea 100644
--- a/build/test.targets
+++ b/build/test.targets
@@ -3,8 +3,13 @@
6.8.0
- 4.18.3
- 2.4.5
+
+
+ 4.20.69
+ 2.5.0
@@ -15,14 +20,14 @@
-
+
-
+
-
+
diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/PersonModelConfiguration.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/PersonModelConfiguration.cs
index 86759c34..aa3c7000 100644
--- a/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/PersonModelConfiguration.cs
+++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/PersonModelConfiguration.cs
@@ -18,6 +18,7 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout
person.HasKey( p => p.Id );
person.Select().OrderBy( "firstName", "lastName" );
+ person.Page( maxTopValue: 100, pageSizeValue: default );
if ( apiVersion < ApiVersions.V3 )
{
diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/ProductConfiguration.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/ProductConfiguration.cs
index b6c151f0..edc33de9 100644
--- a/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/ProductConfiguration.cs
+++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/ProductConfiguration.cs
@@ -18,8 +18,10 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout
return;
}
- var product = builder.EntitySet( "Products" ).EntityType.HasKey( p => p.Id );
+ var product = builder.EntitySet( "Products" ).EntityType;
+ product.HasKey( p => p.Id );
+ product.Page( maxTopValue: 100, pageSizeValue: default );
product.Action( "Rate" ).Parameter( "stars" );
product.Collection.Action( "Rate" ).Parameter( "stars" );
}
diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/SupplierConfiguration.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/SupplierConfiguration.cs
index f6e2f817..b1f91df2 100644
--- a/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/SupplierConfiguration.cs
+++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Configuration/SupplierConfiguration.cs
@@ -18,7 +18,11 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout
return;
}
- builder.EntitySet( "Suppliers" ).EntityType.HasKey( p => p.Id );
+ var supplier = builder.EntitySet( "Suppliers" ).EntityType;
+
+ supplier.HasKey( p => p.Id );
+ supplier.Page( maxTopValue: 100, pageSizeValue: default );
+
builder.Singleton( "Acme" );
}
}
\ No newline at end of file
diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs
index 6ed326dc..e3f15cc4 100644
--- a/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs
+++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Models/Order.cs
@@ -7,6 +7,7 @@
///
/// Represents an order.
///
+[Page( MaxTop = 100 )]
[Select]
[Select( "effectiveDate", SelectType = SelectExpandType.Disabled )]
public class Order
diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs
index e867f18d..2577d01c 100644
--- a/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs
+++ b/examples/AspNet/OData/OpenApiODataWebApiExample/Startup.cs
@@ -5,6 +5,7 @@
using Asp.Versioning.Conventions;
using Asp.Versioning.OData;
using Microsoft.AspNet.OData.Extensions;
+using Microsoft.Extensions.Primitives;
using Microsoft.OData;
using Newtonsoft.Json.Serialization;
using Owin;
@@ -118,9 +119,9 @@ public void Configuration( IAppBuilder builder )
description.Append( " This API version has been deprecated." );
}
- if ( group.SunsetPolicy is SunsetPolicy policy )
+ if ( group.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
description.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -131,25 +132,45 @@ public void Configuration( IAppBuilder builder )
{
description.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- description.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- description.Append( link.Title.Value ).Append( ": " );
+ description.AppendLine();
+ description.Append( "**Links**" );
+ description.AppendLine();
+ rendered = true;
}
- description.Append( link.LinkTarget.OriginalString );
+ if ( StringSegment.IsNullOrEmpty( link.Title ) )
+ {
+ if ( link.LinkTarget.IsAbsoluteUri )
+ {
+ description.AppendLine( $"- {link.LinkTarget.OriginalString}" );
+ }
+ else
+ {
+ description.AppendFormat( "- {0} ", link.LinkTarget.OriginalString );
+ description.AppendLine();
+ }
+ }
+ else
+ {
+ description.AppendLine( $"- [{link.Title}]({link.LinkTarget.OriginalString})" );
+ }
}
}
}
}
+ description.AppendLine();
+ description.AppendLine( "**Additional Information**" );
info.Version( group.Name, $"Sample API {group.ApiVersion}" )
.Contact( c => c.Name( "Bill Mei" ).Email( "bill.mei@somewhere.com" ) )
.Description( description.ToString() )
diff --git a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs
index 6e1412da..af837960 100644
--- a/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs
+++ b/examples/AspNet/OData/OpenApiODataWebApiExample/V3/SuppliersController.cs
@@ -118,7 +118,7 @@ public IHttpActionResult Put( [FromODataUri] int key, [FromBody] Supplier update
///
/// The supplier identifier.
/// The associated supplier products.
- [EnableQuery]
+ [EnableQuery( MaxTop = 100 )]
public IQueryable GetProducts( [FromODataUri] int key ) =>
suppliers.Where( s => s.Id == key ).SelectMany( s => s.Products );
diff --git a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/BooksController.cs b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/BooksController.cs
index 05dbf26b..6017627b 100644
--- a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/BooksController.cs
+++ b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/BooksController.cs
@@ -14,15 +14,15 @@
[RoutePrefix( "api/books" )]
public class BooksController : ApiController
{
- private static readonly Book[] books = new Book[]
- {
+ private static readonly Book[] books =
+ [
new() { Id = "9781847490599", Title = "Anna Karenina", Author = "Leo Tolstoy", Published = 1878 },
new() { Id = "9780198800545", Title = "War and Peace", Author = "Leo Tolstoy", Published = 1869 },
new() { Id = "9780684801520", Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", Published = 1925 },
new() { Id = "9780486280615", Title = "The Adventures of Huckleberry Finn", Author = "Mark Twain", Published = 1884 },
new() { Id = "9780140430820", Title = "Moby Dick", Author = "Herman Melville", Published = 1851 },
new() { Id = "9780060934347", Title = "Don Quixote", Author = "Miguel de Cervantes", Published = 1605 },
- };
+ ];
///
/// Gets all books.
diff --git a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs
index dca602ca..1ac7209a 100644
--- a/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs
+++ b/examples/AspNet/OData/SomeOpenApiODataWebApiExample/Startup.cs
@@ -1,8 +1,8 @@
namespace ApiVersioning.Examples;
-using Asp.Versioning;
using Asp.Versioning.Conventions;
using Microsoft.AspNet.OData.Extensions;
+using Microsoft.Extensions.Primitives;
using Microsoft.OData;
using Newtonsoft.Json.Serialization;
using Owin;
@@ -92,9 +92,9 @@ public void Configuration( IAppBuilder builder )
description.Append( " This API version has been deprecated." );
}
- if ( group.SunsetPolicy is SunsetPolicy policy )
+ if ( group.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
description.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -105,25 +105,45 @@ public void Configuration( IAppBuilder builder )
{
description.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- description.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- description.Append( link.Title.Value ).Append( ": " );
+ description.AppendLine();
+ description.Append( "**Links**" );
+ description.AppendLine();
+ rendered = true;
}
- description.Append( link.LinkTarget.OriginalString );
+ if ( StringSegment.IsNullOrEmpty( link.Title ) )
+ {
+ if ( link.LinkTarget.IsAbsoluteUri )
+ {
+ description.AppendLine( $"- {link.LinkTarget.OriginalString}" );
+ }
+ else
+ {
+ description.AppendFormat( "- {0} ", link.LinkTarget.OriginalString );
+ description.AppendLine();
+ }
+ }
+ else
+ {
+ description.AppendLine( $"- [{link.Title}]({link.LinkTarget.OriginalString})" );
+ }
}
}
}
}
+ description.AppendLine();
+ description.AppendLine( "**Additional Information**" );
info.Version( group.Name, $"Sample API {group.ApiVersion}" )
.Contact( c => c.Name( "Bill Mei" ).Email( "bill.mei@somewhere.com" ) )
.Description( description.ToString() )
diff --git a/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs b/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs
index 7dd4690b..ef7aa0ba 100644
--- a/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs
+++ b/examples/AspNet/WebApi/BasicWebApiExample/Controllers/Values2Controller.cs
@@ -1,6 +1,7 @@
namespace ApiVersioning.Examples.Controllers;
using Asp.Versioning;
+using System.Net.Http;
using System.Web.Http;
[ApiVersion( 2.0 )]
diff --git a/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs b/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs
index e8ceaf54..1d700c5c 100644
--- a/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs
+++ b/examples/AspNet/WebApi/ConventionsWebApiExample/Controllers/Values2Controller.cs
@@ -1,5 +1,6 @@
namespace ApiVersioning.Examples.Controllers;
+using System.Net.Http;
using System.Web.Http;
[RoutePrefix( "api/values" )]
diff --git a/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs b/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs
index b3651855..2a060f11 100644
--- a/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs
+++ b/examples/AspNet/WebApi/OpenApiWebApiExample/Startup.cs
@@ -2,6 +2,7 @@
using Asp.Versioning;
using Asp.Versioning.Routing;
+using Microsoft.Extensions.Primitives;
using Owin;
using Swashbuckle.Application;
using System.IO;
@@ -75,9 +76,9 @@ public void Configuration( IAppBuilder builder )
description.Append( " This API version has been deprecated." );
}
- if ( group.SunsetPolicy is SunsetPolicy policy )
+ if ( group.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
description.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -88,25 +89,45 @@ public void Configuration( IAppBuilder builder )
{
description.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- description.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- description.Append( link.Title.Value ).Append( ": " );
+ description.AppendLine();
+ description.Append( "**Links**" );
+ description.AppendLine();
+ rendered = true;
}
- description.Append( link.LinkTarget.OriginalString );
+ if ( StringSegment.IsNullOrEmpty( link.Title ) )
+ {
+ if ( link.LinkTarget.IsAbsoluteUri )
+ {
+ description.AppendLine( $"- {link.LinkTarget.OriginalString}" );
+ }
+ else
+ {
+ description.AppendFormat( "- {0} ", link.LinkTarget.OriginalString );
+ description.AppendLine();
+ }
+ }
+ else
+ {
+ description.AppendLine( $"- [{link.Title}]({link.LinkTarget.OriginalString})" );
+ }
}
}
}
}
+ description.AppendLine();
+ description.AppendLine( "**Additional Information**" );
info.Version( group.Name, $"Example API {group.ApiVersion}" )
.Contact( c => c.Name( "Bill Mei" ).Email( "bill.mei@somewhere.com" ) )
.Description( description.ToString() )
diff --git a/examples/AspNetCore/Directory.Build.props b/examples/AspNetCore/Directory.Build.props
new file mode 100644
index 00000000..28e9057c
--- /dev/null
+++ b/examples/AspNetCore/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+ net8.0
+
+
+
\ No newline at end of file
diff --git a/examples/AspNetCore/OData/Directory.Build.props b/examples/AspNetCore/OData/Directory.Build.props
index 0d88b71c..0c6cf639 100644
--- a/examples/AspNetCore/OData/Directory.Build.props
+++ b/examples/AspNetCore/OData/Directory.Build.props
@@ -1,11 +1,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj b/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj
index f0d91cbe..ecccb3a9 100644
--- a/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj
+++ b/examples/AspNetCore/OData/ODataAdvancedExample/ODataAdvancedExample.csproj
@@ -1,9 +1,5 @@
-
- net7.0
-
-
diff --git a/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj b/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj
index fc184237..6bdc8cef 100644
--- a/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj
+++ b/examples/AspNetCore/OData/ODataBasicExample/ODataBasicExample.csproj
@@ -1,9 +1,5 @@
-
- net7.0
-
-
diff --git a/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj b/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj
index f0d91cbe..ecccb3a9 100644
--- a/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj
+++ b/examples/AspNetCore/OData/ODataConventionsExample/ODataConventionsExample.csproj
@@ -1,9 +1,5 @@
-
- net7.0
-
-
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/PersonModelConfiguration.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/PersonModelConfiguration.cs
index 5bae1fe3..98d8b060 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/PersonModelConfiguration.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/PersonModelConfiguration.cs
@@ -18,6 +18,7 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout
person.HasKey( p => p.Id );
person.Select().OrderBy( "firstName", "lastName" );
+ person.Page( maxTopValue: 100, pageSizeValue: default );
if ( apiVersion < ApiVersions.V3 )
{
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs
index e8936359..88f7d83a 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/ProductConfiguration.cs
@@ -18,6 +18,9 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout
return;
}
- var product = builder.EntitySet( "Products" ).EntityType.HasKey( p => p.Id );
+ var product = builder.EntitySet( "Products" ).EntityType;
+
+ product.HasKey( p => p.Id );
+ product.Page( maxTopValue: 100, pageSizeValue: default );
}
}
\ No newline at end of file
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/SupplierConfiguration.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/SupplierConfiguration.cs
index 7a803487..e8f1f9e0 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/SupplierConfiguration.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/Configuration/SupplierConfiguration.cs
@@ -18,7 +18,12 @@ public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string rout
return;
}
- builder.EntitySet( "Suppliers" ).EntityType.HasKey( p => p.Id );
+ var supplier = builder.EntitySet( "Suppliers" ).EntityType;
+
+
+ supplier.HasKey( p => p.Id );
+ supplier.Page( maxTopValue: 100, pageSizeValue: default );
+
builder.Singleton( "Acme" );
}
}
\ No newline at end of file
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs
index d0575a5e..5384c160 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/ConfigureSwaggerOptions.cs
@@ -1,9 +1,9 @@
namespace ApiVersioning.Examples;
-using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Text;
@@ -50,9 +50,9 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is SunsetPolicy policy )
+ if ( description.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
text.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -63,25 +63,39 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
{
text.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- text.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- text.Append( link.Title.Value ).Append( ": " );
+ text.Append( "Links " );
+ }
}
}
+ text.Append( "Additional Information " );
info.Description = text.ToString();
return info;
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs
index 009e9862..1df9452e 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/Models/Order.cs
@@ -6,6 +6,7 @@
///
/// Represents an order.
///
+[Page( MaxTop = 100 )]
[Select]
[Select( "effectiveDate", SelectType = SelectExpandType.Disabled )]
public class Order
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj b/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj
index 79a6a4b8..3bffcf5e 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/ODataOpenApiExample.csproj
@@ -1,16 +1,15 @@
- net7.0
true
-
+
-
+
\ No newline at end of file
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs b/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs
index 1fd344e5..4dd5e683 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/Program.cs
@@ -80,15 +80,17 @@
var app = builder.Build();
-// Configure the HTTP request pipeline.
+// Configure HTTP request pipeline.
if ( app.Environment.IsDevelopment() )
{
- // navigate to ~/$odata to determine whether any endpoints did not match an odata route template
+ // Access ~/$odata to identify OData endpoints that failed to match a route template.
app.UseODataRouteDebug();
}
app.UseSwagger();
+if ( app.Environment.IsDevelopment() )
+{
app.UseSwaggerUI(
options =>
{
@@ -102,6 +104,7 @@
options.SwaggerEndpoint( url, name );
}
} );
+}
app.UseHttpsRedirection();
app.UseAuthorization();
diff --git a/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs b/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs
index 401e1a59..652b4d4e 100644
--- a/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs
+++ b/examples/AspNetCore/OData/ODataOpenApiExample/V3/SuppliersController.cs
@@ -17,9 +17,9 @@
public class SuppliersController : ODataController
{
private readonly IQueryable suppliers = new[]
- {
- NewSupplier( 1 ),
- NewSupplier( 2 ),
+ {
+ NewSupplier( 1 ),
+ NewSupplier( 2 ),
NewSupplier( 3 ),
}.AsQueryable();
@@ -46,7 +46,7 @@ public class SuppliersController : ODataController
[Produces( "application/json" )]
[ProducesResponseType( typeof( Supplier ), Status200OK )]
[ProducesResponseType( Status404NotFound )]
- public SingleResult Get( int key ) =>
+ public SingleResult Get( int key ) =>
SingleResult.Create( suppliers.Where( p => p.Id == key ) );
///
@@ -147,7 +147,7 @@ public IActionResult Put( int key, [FromBody] Supplier update )
/// The supplier identifier.
/// The associated supplier products.
[HttpGet]
- [EnableQuery]
+ [EnableQuery( MaxTop = 100 )]
public IQueryable GetProducts( int key ) =>
suppliers.Where( s => s.Id == key ).SelectMany( s => s.Products );
@@ -181,8 +181,8 @@ public IActionResult CreateRef(
[ProducesResponseType( Status204NoContent )]
[ProducesResponseType( Status404NotFound )]
public IActionResult DeleteRef(
- int key,
- int relatedKey,
+ int key,
+ int relatedKey,
string navigationProperty ) => NoContent();
private static Supplier NewSupplier( int id ) =>
diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs
index 6266bc41..68e97255 100644
--- a/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs
+++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/BooksController.cs
@@ -13,15 +13,15 @@
[Route( "api/[controller]" )]
public class BooksController : ControllerBase
{
- private static readonly Book[] books = new Book[]
- {
+ private static readonly Book[] books =
+ [
new() { Id = "9781847490599", Title = "Anna Karenina", Author = "Leo Tolstoy", Published = 1878 },
new() { Id = "9780198800545", Title = "War and Peace", Author = "Leo Tolstoy", Published = 1869 },
new() { Id = "9780684801520", Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", Published = 1925 },
new() { Id = "9780486280615", Title = "The Adventures of Huckleberry Finn", Author = "Mark Twain", Published = 1884 },
new() { Id = "9780140430820", Title = "Moby Dick", Author = "Herman Melville", Published = 1851 },
new() { Id = "9780060934347", Title = "Don Quixote", Author = "Miguel de Cervantes", Published = 1605 },
- };
+ ];
///
/// Gets all books.
diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs
index 987145a1..b725ab72 100644
--- a/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/ConfigureSwaggerOptions.cs
@@ -1,9 +1,9 @@
namespace ApiVersioning.Examples;
-using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Text;
@@ -50,9 +50,9 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is SunsetPolicy policy )
+ if ( description.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
text.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -63,25 +63,39 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
{
text.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- text.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- text.Append( link.Title.Value ).Append( ": " );
+ text.Append( "Links " );
+ }
}
}
+ text.Append( "Additional Information " );
info.Description = text.ToString();
return info;
diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs b/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs
index 7dc6c5c0..55e70b17 100644
--- a/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs
+++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/Program.cs
@@ -64,20 +64,22 @@
// Configure the HTTP request pipeline.
app.UseSwagger();
-app.UseSwaggerUI(
+if ( app.Environment.IsDevelopment() )
+{
+ app.UseSwaggerUI(
options =>
- {
- var descriptions = app.DescribeApiVersions();
-
- // build a swagger endpoint for each discovered API version
- foreach ( var description in descriptions )
- {
- var url = $"/swagger/{description.GroupName}/swagger.json";
- var name = description.GroupName.ToUpperInvariant();
- options.SwaggerEndpoint( url, name );
- }
- } );
-
+ {
+ var descriptions = app.DescribeApiVersions();
+
+ // build a swagger endpoint for each discovered API version
+ foreach ( var description in descriptions )
+ {
+ var url = $"/swagger/{description.GroupName}/swagger.json";
+ var name = description.GroupName.ToUpperInvariant();
+ options.SwaggerEndpoint( url, name );
+ }
+ } );
+}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
diff --git a/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj b/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj
index 79a6a4b8..3bffcf5e 100644
--- a/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj
+++ b/examples/AspNetCore/OData/SomeODataOpenApiExample/SomeODataOpenApiExample.csproj
@@ -1,16 +1,15 @@
- net7.0
true
-
+
-
+
\ No newline at end of file
diff --git a/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj b/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj
index 24772e6a..f283af35 100644
--- a/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj
+++ b/examples/AspNetCore/WebApi/BasicExample/BasicExample.csproj
@@ -1,9 +1,5 @@
-
- net7.0
-
-
diff --git a/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj b/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj
index 24772e6a..f283af35 100644
--- a/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj
+++ b/examples/AspNetCore/WebApi/ByNamespaceExample/ByNamespaceExample.csproj
@@ -1,9 +1,5 @@
-
- net7.0
-
-
diff --git a/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj b/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj
index 24772e6a..f283af35 100644
--- a/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj
+++ b/examples/AspNetCore/WebApi/ConventionsExample/ConventionsExample.csproj
@@ -1,9 +1,5 @@
-
- net7.0
-
-
diff --git a/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj b/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj
index ad99ee2f..3fc9fc03 100644
--- a/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj
+++ b/examples/AspNetCore/WebApi/MinimalApiExample/MinimalApiExample.csproj
@@ -1,7 +1,6 @@
- net7.0
enable
diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs
index bee2b1f6..d531cea4 100644
--- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/ConfigureSwaggerOptions.cs
@@ -1,9 +1,9 @@
namespace ApiVersioning.Examples;
-using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Text;
@@ -50,9 +50,9 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is SunsetPolicy policy )
+ if ( description.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
text.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -63,25 +63,39 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
{
text.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- text.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- text.Append( link.Title.Value ).Append( ": " );
+ text.Append( "Links " );
+ }
}
}
+ text.Append( "Additional Information " );
info.Description = text.ToString();
return info;
diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj b/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj
index 47611fa4..d2daf6ff 100644
--- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj
+++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/MinimalOpenApiExample.csproj
@@ -1,11 +1,7 @@
-
- net7.0
-
-
-
+
diff --git a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs
index acf998ed..909d8261 100644
--- a/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs
+++ b/examples/AspNetCore/WebApi/MinimalOpenApiExample/Program.cs
@@ -264,18 +264,20 @@
.Produces( 400 );
app.UseSwagger();
-app.UseSwaggerUI(
- options =>
- {
- var descriptions = app.DescribeApiVersions();
-
- // build a swagger endpoint for each discovered API version
- foreach ( var description in descriptions )
+if ( app.Environment.IsDevelopment() )
+{
+ app.UseSwaggerUI(
+ options =>
{
- var url = $"/swagger/{description.GroupName}/swagger.json";
- var name = description.GroupName.ToUpperInvariant();
- options.SwaggerEndpoint( url, name );
- }
- } );
+ var descriptions = app.DescribeApiVersions();
+ // build a swagger endpoint for each discovered API version
+ foreach ( var description in descriptions )
+ {
+ var url = $"/swagger/{description.GroupName}/swagger.json";
+ var name = description.GroupName.ToUpperInvariant();
+ options.SwaggerEndpoint( url, name );
+ }
+ } );
+}
app.Run();
\ No newline at end of file
diff --git a/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs b/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs
index bee2b1f6..d531cea4 100644
--- a/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs
+++ b/examples/AspNetCore/WebApi/OpenApiExample/ConfigureSwaggerOptions.cs
@@ -1,9 +1,9 @@
namespace ApiVersioning.Examples;
-using Asp.Versioning;
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Text;
@@ -50,9 +50,9 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
text.Append( " This API version has been deprecated." );
}
- if ( description.SunsetPolicy is SunsetPolicy policy )
+ if ( description.SunsetPolicy is { } policy )
{
- if ( policy.Date is DateTimeOffset when )
+ if ( policy.Date is { } when )
{
text.Append( " The API will be sunset on " )
.Append( when.Date.ToShortDateString() )
@@ -63,25 +63,39 @@ private static OpenApiInfo CreateInfoForApiVersion( ApiVersionDescription descri
{
text.AppendLine();
+ var rendered = false;
+
for ( var i = 0; i < policy.Links.Count; i++ )
{
var link = policy.Links[i];
if ( link.Type == "text/html" )
{
- text.AppendLine();
-
- if ( link.Title.HasValue )
+ if ( !rendered )
{
- text.Append( link.Title.Value ).Append( ": " );
+ text.Append( "Links " );
+ }
}
}
+ text.Append( "Additional Information " );
info.Description = text.ToString();
return info;
diff --git a/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj b/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj
index 5c6ed4ea..2be84679 100644
--- a/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj
+++ b/examples/AspNetCore/WebApi/OpenApiExample/OpenApiExample.csproj
@@ -1,12 +1,11 @@
- net7.0
true
-
+
diff --git a/examples/AspNetCore/WebApi/OpenApiExample/Program.cs b/examples/AspNetCore/WebApi/OpenApiExample/Program.cs
index 64ad0ad1..454c60d7 100644
--- a/examples/AspNetCore/WebApi/OpenApiExample/Program.cs
+++ b/examples/AspNetCore/WebApi/OpenApiExample/Program.cs
@@ -57,19 +57,22 @@
// Configure the HTTP request pipeline.
app.UseSwagger();
-app.UseSwaggerUI(
- options =>
- {
- var descriptions = app.DescribeApiVersions();
-
- // build a swagger endpoint for each discovered API version
- foreach ( var description in descriptions )
- {
- var url = $"/swagger/{description.GroupName}/swagger.json";
- var name = description.GroupName.ToUpperInvariant();
- options.SwaggerEndpoint( url, name );
- }
- } );
+if ( app.Environment.IsDevelopment() )
+{
+ app.UseSwaggerUI(
+ options =>
+ {
+ var descriptions = app.DescribeApiVersions();
+
+ // build a swagger endpoint for each discovered API version
+ foreach ( var description in descriptions )
+ {
+ var url = $"/swagger/{description.GroupName}/swagger.json";
+ var name = description.GroupName.ToUpperInvariant();
+ options.SwaggerEndpoint( url, name );
+ }
+ } );
+}
app.UseHttpsRedirection();
app.UseAuthorization();
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/AdvertiseApiVersionsAttribute.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/AdvertiseApiVersionsAttribute.cs
index 195fa7ea..ca183af6 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/AdvertiseApiVersionsAttribute.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/AdvertiseApiVersionsAttribute.cs
@@ -1,5 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
+#pragma warning disable CA1019
+#pragma warning disable CA1033
+#pragma warning disable CA1813
+
namespace Asp.Versioning;
using static System.AttributeTargets;
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs
index 24577d7a..4a780ad5 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/AmbiguousApiVersionException.cs
@@ -12,14 +12,14 @@ public partial class AmbiguousApiVersionException : Exception
///
/// Initializes a new instance of the class.
///
- public AmbiguousApiVersionException() => apiVersions = Array.Empty();
+ public AmbiguousApiVersionException() => apiVersions = [];
///
/// Initializes a new instance of the class.
///
/// The associated error message.
public AmbiguousApiVersionException( string message )
- : base( message ) => apiVersions = Array.Empty();
+ : base( message ) => apiVersions = [];
///
/// Initializes a new instance of the class.
@@ -27,7 +27,7 @@ public AmbiguousApiVersionException( string message )
/// The associated error message.
/// The inner exception that caused the current exception, if any.
public AmbiguousApiVersionException( string message, Exception innerException )
- : base( message, innerException ) => apiVersions = Array.Empty();
+ : base( message, innerException ) => apiVersions = [];
///
/// Initializes a new instance of the class.
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersion.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersion.cs
index e7fe068d..1bf4eeaa 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersion.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersion.cs
@@ -74,7 +74,7 @@ protected ApiVersion( double version, string? status, Func isVali
Status = ValidateStatus(
status,
- isValidStatus ?? throw new ArgumentNullException( nameof( isValidStatus ) ) );
+ isValidStatus ?? throw new System.ArgumentNullException( nameof( isValidStatus ) ) );
var number = new decimal( version );
var bits = decimal.GetBits( number );
@@ -124,10 +124,7 @@ protected internal ApiVersion(
/// The instance to derive from.
protected ApiVersion( ApiVersion other )
{
- if ( other == null )
- {
- throw new ArgumentNullException( nameof( other ) );
- }
+ ArgumentNullException.ThrowIfNull( other );
hashCode = other.hashCode;
GroupVersion = other.GroupVersion;
@@ -341,7 +338,11 @@ public virtual int CompareTo( ApiVersion? other )
public virtual string ToString( string? format, IFormatProvider? formatProvider )
{
var provider = ApiVersionFormatProvider.GetInstance( formatProvider );
+#pragma warning disable IDE0079
+#pragma warning disable CA1062 // Validate arguments of public methods
return provider.Format( format, this, formatProvider );
+#pragma warning restore CA1062 // Validate arguments of public methods
+#pragma warning restore IDE0079
}
private static string? ValidateStatus( string? status, Func isValid )
@@ -351,7 +352,7 @@ public virtual string ToString( string? format, IFormatProvider? formatProvider
return status;
}
- var message = string.Format( CultureInfo.CurrentCulture, SR.ApiVersionBadStatus, status );
+ var message = string.Format( CultureInfo.CurrentCulture, Format.ApiVersionBadStatus, status );
throw new ArgumentException( message, nameof( status ) );
}
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionAttribute.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionAttribute.cs
index bbdff81b..90657500 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionAttribute.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionAttribute.cs
@@ -1,5 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
+#pragma warning disable CA1019
+#pragma warning disable CA1033
+#pragma warning disable CA1813
+
namespace Asp.Versioning;
using static System.AttributeTargets;
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionFormatProvider.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionFormatProvider.cs
index 9879d007..6ade14b6 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionFormatProvider.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionFormatProvider.cs
@@ -200,7 +200,7 @@ public ApiVersionFormatProvider()
///
/// The used by the format provider.
public ApiVersionFormatProvider( DateTimeFormatInfo dateTimeFormat )
- : this( dateTimeFormat ?? throw new ArgumentNullException( nameof( dateTimeFormat ) ), dateTimeFormat.Calendar ) { }
+ : this( dateTimeFormat ?? throw new System.ArgumentNullException( nameof( dateTimeFormat ) ), dateTimeFormat.Calendar ) { }
///
/// Initializes a new instance of the class.
@@ -293,20 +293,11 @@ protected virtual void FormatVersionPart(
Text format,
IFormatProvider formatProvider )
{
- if ( text == null )
- {
- throw new ArgumentNullException( nameof( text ) );
- }
-
- if ( apiVersion == null )
- {
- throw new ArgumentNullException( nameof( apiVersion ) );
- }
-
- if ( Str.IsNullOrEmpty( format ) )
- {
- throw new ArgumentNullException( nameof( format ) );
- }
+ ArgumentNullException.ThrowIfNull( text );
+ ArgumentNullException.ThrowIfNull( apiVersion );
+#if NETSTANDARD1_0
+ ArgumentNullException.ThrowIfNull( format );
+#endif
switch ( format[0] )
{
@@ -334,16 +325,8 @@ protected virtual void FormatStatusPart(
Text format,
IFormatProvider formatProvider )
{
- if ( text == null )
- {
- throw new ArgumentNullException( nameof( text ) );
- }
-
- if ( apiVersion == null )
- {
- throw new ArgumentNullException( nameof( apiVersion ) );
- }
-
+ ArgumentNullException.ThrowIfNull( text );
+ ArgumentNullException.ThrowIfNull( apiVersion );
text.Append( apiVersion.Status );
}
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs
index 4211a665..d7e49d28 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionMetadata.cs
@@ -34,10 +34,7 @@ public class ApiVersionMetadata
/// The other instance to initialize from.
protected ApiVersionMetadata( ApiVersionMetadata other )
{
- if ( other == null )
- {
- throw new ArgumentNullException( nameof( other ) );
- }
+ ArgumentNullException.ThrowIfNull( other );
apiModel = other.apiModel;
endpointModel = other.endpointModel;
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelDebugView.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelDebugView.cs
index 632500d0..00cccc5b 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelDebugView.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelDebugView.cs
@@ -4,12 +4,9 @@ namespace Asp.Versioning;
using static System.String;
-internal sealed class ApiVersionModelDebugView
+internal sealed class ApiVersionModelDebugView( ApiVersionModel model )
{
private const string Comma = ", ";
- private readonly ApiVersionModel model;
-
- public ApiVersionModelDebugView( ApiVersionModel model ) => this.model = model;
public bool VersionNeutral => model.IsApiVersionNeutral;
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs
index e45e0f5c..efdc86f3 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionModelExtensions.cs
@@ -17,15 +17,8 @@ public static class ApiVersionModelExtensions
/// other version information and the current version information.
public static ApiVersionModel Aggregate( this ApiVersionModel version, ApiVersionModel otherVersion )
{
- if ( version == null )
- {
- throw new ArgumentNullException( nameof( version ) );
- }
-
- if ( otherVersion == null )
- {
- throw new ArgumentNullException( nameof( otherVersion ) );
- }
+ ArgumentNullException.ThrowIfNull( version );
+ ArgumentNullException.ThrowIfNull( otherVersion );
var implemented = new SortedSet( version.ImplementedApiVersions );
var supported = new SortedSet( version.SupportedApiVersions );
@@ -50,15 +43,8 @@ public static ApiVersionModel Aggregate( this ApiVersionModel version, ApiVersio
/// other version information and the current version information.
public static ApiVersionModel Aggregate( this ApiVersionModel version, IEnumerable otherVersions )
{
- if ( version == null )
- {
- throw new ArgumentNullException( nameof( version ) );
- }
-
- if ( otherVersions == null )
- {
- throw new ArgumentNullException( nameof( otherVersions ) );
- }
+ ArgumentNullException.ThrowIfNull( version );
+ ArgumentNullException.ThrowIfNull( otherVersions );
if ( ( otherVersions is ICollection collection && collection.Count == 0 ) ||
( otherVersions is IReadOnlyCollection readOnlyCollection && readOnlyCollection.Count == 0 ) )
@@ -99,10 +85,7 @@ public static ApiVersionModel Aggregate( this ApiVersionModel version, IEnumerab
/// A new that is the aggregated result of the provided version information .
public static ApiVersionModel Aggregate( this IEnumerable versions )
{
- if ( versions == null )
- {
- throw new ArgumentNullException( nameof( versions ) );
- }
+ ArgumentNullException.ThrowIfNull( versions );
if ( ( versions is ICollection collection && collection.Count == 0 ) ||
( versions is IReadOnlyCollection readOnlyCollection && readOnlyCollection.Count == 0 ) )
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionParser.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionParser.cs
index f35292f9..97ec10bb 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionParser.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionParser.cs
@@ -187,8 +187,6 @@ public virtual ApiVersion Parse( Text text )
///
#if NETSTANDARD1_0
public virtual bool TryParse( Text? text, out ApiVersion apiVersion )
-#elif NETSTANDARD2_0
- public virtual bool TryParse( Text text, out ApiVersion apiVersion )
#else
public virtual bool TryParse( Text text, [MaybeNullWhen( false )] out ApiVersion apiVersion )
#endif
@@ -338,11 +336,11 @@ public virtual bool TryParse( Text text, [MaybeNullWhen( false )] out ApiVersion
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static FormatException InvalidGroupVersion( string value ) =>
- new( string.Format( CultureInfo.CurrentCulture, SR.ApiVersionBadGroupVersion, value ) );
+ new( string.Format( CultureInfo.CurrentCulture, Format.ApiVersionBadGroupVersion, value ) );
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static FormatException InvalidStatus( string value ) =>
- new( string.Format( CultureInfo.CurrentCulture, SR.ApiVersionBadStatus, value ) );
+ new( string.Format( CultureInfo.CurrentCulture, Format.ApiVersionBadStatus, value ) );
private static bool IsDateLike( Text value )
{
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs
index 212e20ac..4c966ae5 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ApiVersionsBaseAttribute.cs
@@ -96,7 +96,7 @@ protected ApiVersionsBaseAttribute( string version, params string[] otherVersion
/// The parser used to parse the specified versions.
/// The API version string.
protected ApiVersionsBaseAttribute( IApiVersionParser parser, string version ) =>
- Versions = new[] { ( parser ?? throw new ArgumentNullException( nameof( parser ) ) ).Parse( version ) };
+ Versions = new[] { ( parser ?? throw new System.ArgumentNullException( nameof( parser ) ) ).Parse( version ) };
///
/// Initializes a new instance of the class.
@@ -106,10 +106,7 @@ protected ApiVersionsBaseAttribute( IApiVersionParser parser, string version ) =
/// An array of API other version strings.
protected ApiVersionsBaseAttribute( IApiVersionParser parser, string version, params string[] otherVersions )
{
- if ( parser == null )
- {
- throw new ArgumentNullException( nameof( parser ) );
- }
+ ArgumentNullException.ThrowIfNull( parser );
int count;
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Asp.Versioning.Abstractions.csproj b/src/Abstractions/src/Asp.Versioning.Abstractions/Asp.Versioning.Abstractions.csproj
index 04d6b034..68dd7991 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/Asp.Versioning.Abstractions.csproj
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Asp.Versioning.Abstractions.csproj
@@ -1,18 +1,22 @@
-
+
- 7.0.0
- 7.0.0.0
- net7.0;netstandard1.0;netstandard2.0
+ 8.1.0
+ 8.1.0.0
+ $(DefaultTargetFramework);netstandard1.0;netstandard2.0
API Versioning Abstractions
The abstractions library for API versioning.
Asp.Versioning
Asp;AspNet;AspNetCore;Versioning
+
+ true
+
+
-
-
+
+
@@ -22,21 +26,28 @@
-
-
+
+
-
+
+
+
+
+
+
+
+
-
+
-
+
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs
index 4a431637..4ebe62bd 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderBase.cs
@@ -39,25 +39,25 @@ protected ApiVersionConventionBuilderBase() { }
/// Gets the collection of API versions supported by the current controller.
///
/// A collection of supported API versions .
- protected ICollection SupportedVersions => supported ??= new();
+ protected ICollection SupportedVersions => supported ??= [];
///
/// Gets the collection of API versions deprecated by the current controller.
///
/// A collection of deprecated API versions .
- protected ICollection DeprecatedVersions => deprecated ??= new();
+ protected ICollection DeprecatedVersions => deprecated ??= [];
///
/// Gets the collection of API versions advertised by the current controller.
///
/// A collection of advertised API versions .
- protected ICollection AdvertisedVersions => advertised ??= new();
+ protected ICollection AdvertisedVersions => advertised ??= [];
///
/// Gets the collection of API versions advertised and deprecated by the current controller.
///
/// A collection of advertised and deprecated API versions .
- protected ICollection DeprecatedAdvertisedVersions => deprecatedAdvertised ??= new();
+ protected ICollection DeprecatedAdvertisedVersions => deprecatedAdvertised ??= [];
///
/// Merges API version information from the specified attributes with the current conventions.
@@ -72,10 +72,7 @@ protected virtual void MergeAttributesWithConventions( IEnumerable attri
/// The read-only list of attributes to merge.
protected virtual void MergeAttributesWithConventions( IReadOnlyList attributes )
{
- if ( attributes == null )
- {
- throw new ArgumentNullException( nameof( attributes ) );
- }
+ ArgumentNullException.ThrowIfNull( attributes );
if ( VersionNeutral )
{
@@ -102,19 +99,19 @@ protected virtual void MergeAttributesWithConventions( IReadOnlyList att
switch ( provider.Options )
{
case None:
- target = newSupported ??= new();
+ target = newSupported ??= [];
source = provider.Versions;
break;
case Deprecated:
- target = newDeprecated ??= new();
+ target = newDeprecated ??= [];
source = provider.Versions;
break;
case Advertised:
- target = newAdvertised ??= new();
+ target = newAdvertised ??= [];
source = provider.Versions;
break;
case DeprecatedAdvertised:
- target = newDeprecatedAdvertised ??= new();
+ target = newDeprecatedAdvertised ??= [];
source = provider.Versions;
break;
default:
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs
index 3813f478..9d528546 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Conventions/ApiVersionConventionBuilderExtensions.cs
@@ -84,10 +84,7 @@ public static class ApiVersionConventionBuilderExtensions
public static T HasApiVersions( this T builder, IEnumerable apiVersions )
where T : notnull, IDeclareApiVersionConventionBuilder
{
- if ( apiVersions == null )
- {
- throw new ArgumentNullException( nameof( apiVersions ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersions );
foreach ( var apiVersion in apiVersions )
{
@@ -170,10 +167,7 @@ public static T HasApiVersions( this T builder, IEnumerable apiVe
public static T HasDeprecatedApiVersions( this T builder, IEnumerable apiVersions )
where T : notnull, IDeclareApiVersionConventionBuilder
{
- if ( apiVersions == null )
- {
- throw new ArgumentNullException( nameof( apiVersions ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersions );
foreach ( var apiVersion in apiVersions )
{
@@ -256,10 +250,7 @@ public static T HasDeprecatedApiVersions( this T builder, IEnumerable( this T builder, IEnumerable apiVersions )
where T : notnull, IDeclareApiVersionConventionBuilder
{
- if ( apiVersions == null )
- {
- throw new ArgumentNullException( nameof( apiVersions ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersions );
foreach ( var apiVersion in apiVersions )
{
@@ -342,10 +333,7 @@ public static T AdvertisesApiVersions( this T builder, IEnumerable( this T builder, IEnumerable apiVersions )
where T : notnull, IDeclareApiVersionConventionBuilder
{
- if ( apiVersions == null )
- {
- throw new ArgumentNullException( nameof( apiVersions ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersions );
foreach ( var apiVersion in apiVersions )
{
@@ -428,10 +416,7 @@ public static T AdvertisesDeprecatedApiVersions( this T builder, IEnumerable<
public static T MapToApiVersions( this T builder, IEnumerable apiVersions )
where T : notnull, IMapToApiVersionConventionBuilder
{
- if ( apiVersions == null )
- {
- throw new ArgumentNullException( nameof( apiVersions ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersions );
foreach ( var apiVersion in apiVersions )
{
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs
new file mode 100644
index 00000000..dbea4469
--- /dev/null
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/Format.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+#if NET
+using System.Text;
+#endif
+
+internal static class Format
+{
+#if NETSTANDARD
+ internal static readonly string ApiVersionBadStatus = SR.ApiVersionBadStatus;
+ internal static readonly string ApiVersionBadGroupVersion = SR.ApiVersionBadGroupVersion;
+#else
+ internal static readonly CompositeFormat ApiVersionBadStatus = CompositeFormat.Parse( SR.ApiVersionBadStatus );
+ internal static readonly CompositeFormat ApiVersionBadGroupVersion = CompositeFormat.Parse( SR.ApiVersionBadGroupVersion );
+#endif
+}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionNeutral.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionNeutral.cs
index cc4c27a3..375541ef 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionNeutral.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionNeutral.cs
@@ -1,5 +1,8 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
+#pragma warning disable CA1040
+
namespace Asp.Versioning;
///
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs
index 4a3dd194..55022bce 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersionParameterSourceExtensions.cs
@@ -18,10 +18,7 @@ public static class IApiVersionParameterSourceExtensions
/// True if the parameter source versions by query string; otherwise, false.
public static bool VersionsByQueryString( this IApiVersionParameterSource source, bool allowMultipleLocations = true )
{
- if ( source == null )
- {
- throw new ArgumentNullException( nameof( source ) );
- }
+ ArgumentNullException.ThrowIfNull( source );
var context = new DescriptionContext( Query );
@@ -39,10 +36,7 @@ public static bool VersionsByQueryString( this IApiVersionParameterSource source
/// True if the parameter source versions by HTTP header; otherwise, false.
public static bool VersionsByHeader( this IApiVersionParameterSource source, bool allowMultipleLocations = true )
{
- if ( source == null )
- {
- throw new ArgumentNullException( nameof( source ) );
- }
+ ArgumentNullException.ThrowIfNull( source );
var context = new DescriptionContext( Header );
@@ -60,10 +54,7 @@ public static bool VersionsByHeader( this IApiVersionParameterSource source, boo
/// True if the parameter source versions by URL path segment; otherwise, false.
public static bool VersionsByUrl( this IApiVersionParameterSource source, bool allowMultipleLocations = true )
{
- if ( source == null )
- {
- throw new ArgumentNullException( nameof( source ) );
- }
+ ArgumentNullException.ThrowIfNull( source );
var context = new DescriptionContext( Path );
@@ -81,10 +72,7 @@ public static bool VersionsByUrl( this IApiVersionParameterSource source, bool a
/// True if the parameter source versions by media type; otherwise, false.
public static bool VersionsByMediaType( this IApiVersionParameterSource source, bool allowMultipleLocations = true )
{
- if ( source == null )
- {
- throw new ArgumentNullException( nameof( source ) );
- }
+ ArgumentNullException.ThrowIfNull( source );
var context = new DescriptionContext( MediaTypeParameter );
@@ -102,10 +90,7 @@ public static bool VersionsByMediaType( this IApiVersionParameterSource source,
/// or null .
public static string GetParameterName( this IApiVersionParameterSource source, ApiVersionParameterLocation location )
{
- if ( source == null )
- {
- throw new ArgumentNullException( nameof( source ) );
- }
+ ArgumentNullException.ThrowIfNull( source );
var context = new DescriptionContext( location );
@@ -122,10 +107,7 @@ public static string GetParameterName( this IApiVersionParameterSource source, A
/// The names of the parameters defined by the parameter source for the specified .
public static IReadOnlyList GetParameterNames( this IApiVersionParameterSource source, ApiVersionParameterLocation location )
{
- if ( source == null )
- {
- throw new ArgumentNullException( nameof( source ) );
- }
+ ArgumentNullException.ThrowIfNull( source );
var context = new DescriptionContext( location );
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs
index e2040ba2..048d6d49 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/IApiVersioningPolicyBuilderExtensions.cs
@@ -19,11 +19,7 @@ public static class IApiVersioningPolicyBuilderExtensions
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( name, default );
}
@@ -43,11 +39,7 @@ public static ISunsetPolicyBuilder Sunset(
int? minorVersion = default,
string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( name, new ApiVersion( majorVersion, minorVersion, status ) );
}
@@ -61,11 +53,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name, double version, string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( name, new ApiVersion( version, status ) );
}
@@ -81,11 +69,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name, int year, int month, int day, string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( name, new ApiVersion( new DateOnly( year, month, day ), status ) );
}
@@ -99,11 +83,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, string name, DateOnly groupVersion, string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( name, new ApiVersion( groupVersion, status ) );
}
@@ -115,11 +95,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, ApiVersion apiVersion )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( default, apiVersion );
}
@@ -137,11 +113,7 @@ public static ISunsetPolicyBuilder Sunset(
int? minorVersion = default,
string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( default, new ApiVersion( majorVersion, minorVersion, status ) );
}
@@ -154,11 +126,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, double version, string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( default, new ApiVersion( version, status ) );
}
@@ -173,11 +141,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, int year, int month, int day, string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( default, new ApiVersion( new DateOnly( year, month, day ), status ) );
}
@@ -190,11 +154,7 @@ public static ISunsetPolicyBuilder Sunset(
/// A new sunset policy builder .
public static ISunsetPolicyBuilder Sunset( this IApiVersioningPolicyBuilder builder, DateOnly groupVersion, string? status = default )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Sunset( default, new ApiVersion( groupVersion, status ) );
}
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ILinkBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ILinkBuilderExtensions.cs
index ccb9a64e..acf444f9 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ILinkBuilderExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ILinkBuilderExtensions.cs
@@ -15,11 +15,7 @@ public static class ILinkBuilderExtensions
/// A new link builder .
public static ILinkBuilder Link( this ILinkBuilder builder, string linkTarget )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) );
}
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs
index 778f78d7..4a81ebd4 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyBuilderExtensions.cs
@@ -15,11 +15,7 @@ public static class ISunsetPolicyBuilderExtensions
/// A new link builder .
public static ILinkBuilder Link( this ISunsetPolicyBuilder builder, string linkTarget )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
return builder.Link( new Uri( linkTarget, UriKind.RelativeOrAbsolute ) );
}
@@ -35,11 +31,7 @@ public static ILinkBuilder Link( this ISunsetPolicyBuilder builder, string linkT
public static TBuilder Effective( this TBuilder builder, int year, int month, int day )
where TBuilder : notnull, ISunsetPolicyBuilder
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
builder.Effective( new DateTimeOffset( new DateTime( year, month, day ) ) );
return builder;
}
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs
index 77a175a7..de2dd77e 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManager.cs
@@ -18,11 +18,5 @@ public interface ISunsetPolicyManager
/// policy for the specified API version . If
/// API version is null , it is assumed the caller intends to match
/// any sunset policy for the specified .
- bool TryGetPolicy(
- string? name,
- ApiVersion? apiVersion,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out SunsetPolicy sunsetPolicy );
+ bool TryGetPolicy( string? name, ApiVersion? apiVersion, [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy );
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs
index 11e21279..f28f9cca 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/ISunsetPolicyManagerExtensions.cs
@@ -17,16 +17,9 @@ public static class ISunsetPolicyManagerExtensions
public static bool TryGetPolicy(
this ISunsetPolicyManager policyManager,
ApiVersion apiVersion,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out SunsetPolicy sunsetPolicy )
+ [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
{
- if ( policyManager == null )
- {
- throw new ArgumentNullException( nameof( policyManager ) );
- }
-
+ ArgumentNullException.ThrowIfNull( policyManager );
return policyManager.TryGetPolicy( default, apiVersion, out sunsetPolicy );
}
@@ -40,16 +33,9 @@ public static bool TryGetPolicy(
public static bool TryGetPolicy(
this ISunsetPolicyManager policyManager,
string name,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out SunsetPolicy sunsetPolicy )
+ [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
{
- if ( policyManager == null )
- {
- throw new ArgumentNullException( nameof( policyManager ) );
- }
-
+ ArgumentNullException.ThrowIfNull( policyManager );
return policyManager.TryGetPolicy( name, default, out sunsetPolicy );
}
@@ -60,7 +46,7 @@ public static bool TryGetPolicy(
/// The name of the API.
/// The API version to get the policy for.
/// The applicable sunset policy , if any.
- /// The resolution or is as follows:
+ /// The resolution order is as follows:
///
/// and
/// only
@@ -72,10 +58,7 @@ public static bool TryGetPolicy(
string? name,
ApiVersion? apiVersion )
{
- if ( policyManager == null )
- {
- throw new ArgumentNullException( nameof( policyManager ) );
- }
+ ArgumentNullException.ThrowIfNull( policyManager );
if ( policyManager.TryResolvePolicy( name, apiVersion, out var policy ) )
{
@@ -93,7 +76,7 @@ public static bool TryGetPolicy(
/// The API version to get the policy for.
/// /// The applicable sunset policy , if any.
/// True if the sunset policy was retrieved; otherwise, false.
- /// The resolution or is as follows:
+ /// The resolution order is as follows:
///
/// and
/// only
@@ -104,15 +87,9 @@ public static bool TryResolvePolicy(
this ISunsetPolicyManager policyManager,
string? name,
ApiVersion? apiVersion,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out SunsetPolicy sunsetPolicy )
+ [MaybeNullWhen( false )] out SunsetPolicy sunsetPolicy )
{
- if ( policyManager == null )
- {
- throw new ArgumentNullException( nameof( policyManager ) );
- }
+ ArgumentNullException.ThrowIfNull( policyManager );
if ( !string.IsNullOrEmpty( name ) )
{
@@ -125,7 +102,8 @@ public static bool TryResolvePolicy(
return true;
}
}
- else if ( apiVersion != null && policyManager.TryGetPolicy( apiVersion, out sunsetPolicy ) )
+
+ if ( apiVersion != null && policyManager.TryGetPolicy( apiVersion, out sunsetPolicy ) )
{
return true;
}
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs
index eeec87a0..94088bc9 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/LinkHeaderValue.cs
@@ -85,7 +85,7 @@ public StringSegment Language
#endif
if ( languages is null )
{
- languages = new() { value };
+ languages = [value];
}
else if ( languages.Count == 0 )
{
@@ -105,7 +105,7 @@ public StringSegment Language
/// This is only a hint; for example, it does not override the Content-Language header field of
/// a HTTP response obtained by actually following the link. A single link may indicate that multiple
/// languages are available from the indicated resource.
- public IList Languages => languages ??= new();
+ public IList Languages => languages ??= [];
///
/// Gets or sets the link media.
@@ -161,10 +161,7 @@ public StringSegment Language
public static bool TryParse(
StringSegment input,
Func? resolveRelativeUrl,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out LinkHeaderValue parsedValue )
+ [MaybeNullWhen( false )] out LinkHeaderValue parsedValue )
{
#if NETSTANDARD1_0
if ( string.IsNullOrEmpty( input ) )
@@ -209,11 +206,11 @@ public static bool TryParse(
type = attribute.Value;
break;
case "hreflang":
- languages ??= new();
+ languages ??= [];
languages.Add( attribute.Value );
break;
default:
- extensions ??= new();
+ extensions ??= [];
extensions.Add( attribute );
break;
}
@@ -302,9 +299,7 @@ public static bool TryParse(
public static bool TryParseList(
IList? input,
Func? resolveRelativeUrl,
-#if !NETSTANDARD
[MaybeNullWhen( false )]
-#endif
out IList parsedValues )
{
if ( input == null )
@@ -404,10 +399,7 @@ private static void AppendTargetAttribute( StringBuilder builder, ReadOnlySpan? resolveRelativeUrl,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out Uri targetLink )
+ [MaybeNullWhen( false )] out Uri targetLink )
{
var start = segment.IndexOf( '<' );
@@ -495,7 +487,7 @@ public bool Remove( KeyValuePair item ) =>
public bool TryGetValue(
StringSegment key,
-#if !NETSTANDARD
+#if !NETSTANDARD1_0
[MaybeNullWhen( false )]
#endif
out StringSegment value ) => items.TryGetValue( key, out value );
@@ -540,16 +532,10 @@ private static ref StringSegment ValidateKey( ref StringSegment key )
}
}
- private struct TargetAttributesEnumerator : IEnumerable>
+ private struct TargetAttributesEnumerator( StringSegment remaining )
+ : IEnumerable>
{
- private readonly StringSegment remaining;
- private int start;
-
- public TargetAttributesEnumerator( StringSegment remaining )
- {
- this.remaining = remaining;
- start = 0;
- }
+ private int start = 0;
public IEnumerator> GetEnumerator()
{
@@ -572,11 +558,13 @@ public IEnumerator> GetEnumerator()
}
// REF: https://datatracker.ietf.org/doc/html/rfc8288#appendix-B.3 #9
+#pragma warning disable CA1308 // Normalize strings to uppercase (all ascii and should normalize to lowercase)
#if NETSTANDARD1_0
var key = remaining.Substring( start, end - start ).ToLowerInvariant();
#else
var key = new StringSegment( remaining.Substring( start, end - start ).ToLowerInvariant() );
#endif
+#pragma warning restore CA1308 // Normalize strings to uppercase
start = end;
ConsumeWhitespace();
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/MapToApiVersionAttribute.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/MapToApiVersionAttribute.cs
index 3d7050cc..e90f5c58 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/MapToApiVersionAttribute.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/MapToApiVersionAttribute.cs
@@ -1,5 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
+#pragma warning disable CA1019
+#pragma warning disable CA1033
+#pragma warning disable CA1813
+
namespace Asp.Versioning;
using static System.AttributeTargets;
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs
index d857297b..a4246749 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/NamespaceParser.cs
@@ -53,10 +53,7 @@ public class NamespaceParser
/// A read-only list of API verisons .
public IReadOnlyList Parse( Type type )
{
- if ( type == null )
- {
- throw new ArgumentNullException( nameof( type ) );
- }
+ ArgumentNullException.ThrowIfNull( type );
if ( string.IsNullOrEmpty( type.Namespace ) )
{
@@ -95,7 +92,7 @@ public IReadOnlyList Parse( Type type )
}
else if ( versions is null )
{
- versions = new() { version, result };
+ versions = [version, result];
}
else
{
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/net7.0/ApiVersion.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/net#.0/ApiVersion.cs
similarity index 73%
rename from src/Abstractions/src/Asp.Versioning.Abstractions/net7.0/ApiVersion.cs
rename to src/Abstractions/src/Asp.Versioning.Abstractions/net#.0/ApiVersion.cs
index bd7c0682..c9eb3b00 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/net7.0/ApiVersion.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/net#.0/ApiVersion.cs
@@ -11,6 +11,10 @@ public partial class ApiVersion : ISpanFormattable
public virtual bool TryFormat( Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider )
{
var instance = ApiVersionFormatProvider.GetInstance( provider );
+#pragma warning disable IDE0079
+#pragma warning disable CA1062 // Validate arguments of public methods
return instance.TryFormat( destination, out charsWritten, format, this, provider );
+#pragma warning restore CA1062 // Validate arguments of public methods
+#pragma warning restore IDE0079
}
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard1.0/ApiVersionFormatProvider.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard1.0/ApiVersionFormatProvider.cs
index faf0e6df..2cf84bb0 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard1.0/ApiVersionFormatProvider.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard1.0/ApiVersionFormatProvider.cs
@@ -22,15 +22,8 @@ protected virtual void FormatGroupVersionPart(
string? format,
IFormatProvider? formatProvider )
{
- if ( text == null )
- {
- throw new ArgumentNullException( nameof( text ) );
- }
-
- if ( apiVersion == null )
- {
- throw new ArgumentNullException( nameof( apiVersion ) );
- }
+ ArgumentNullException.ThrowIfNull( text );
+ ArgumentNullException.ThrowIfNull( apiVersion );
if ( !apiVersion.GroupVersion.HasValue || string.IsNullOrEmpty( format ) )
{
@@ -106,15 +99,8 @@ protected virtual void FormatAllParts(
string? format,
IFormatProvider? formatProvider )
{
- if ( text == null )
- {
- throw new ArgumentNullException( nameof( text ) );
- }
-
- if ( apiVersion == null )
- {
- throw new ArgumentNullException( nameof( apiVersion ) );
- }
+ ArgumentNullException.ThrowIfNull( text );
+ ArgumentNullException.ThrowIfNull( apiVersion );
if ( apiVersion.GroupVersion.HasValue )
{
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/AmbiguousApiVersionException.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/AmbiguousApiVersionException.cs
index 7215f3e9..baf164e4 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/AmbiguousApiVersionException.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/AmbiguousApiVersionException.cs
@@ -2,6 +2,7 @@
namespace Asp.Versioning;
+using System.ComponentModel;
using System.Runtime.Serialization;
///
@@ -10,11 +11,23 @@ namespace Asp.Versioning;
[Serializable]
public partial class AmbiguousApiVersionException : Exception
{
+ private const string LegacyFormatterImplMessage = "This API supports obsolete formatter-based serialization. It should not be called or extended by application code.";
+#if NET
+ private const string LegacyFormatterImplDiagId = "SYSLIB0051";
+ private const string SharedUrlFormat = "https://aka.ms/dotnet-warnings/{0}";
+#endif
+
///
/// Initializes a new instance of the class.
///
/// The serialization info the exception is being deserialized with.
/// The streaming context the exception is being deserialized from.
+#if NET
+ [Obsolete( LegacyFormatterImplMessage, DiagnosticId = LegacyFormatterImplDiagId, UrlFormat = SharedUrlFormat )]
+#else
+ [Obsolete( LegacyFormatterImplMessage )]
+#endif
+ [EditorBrowsable( EditorBrowsableState.Never )]
protected AmbiguousApiVersionException( SerializationInfo info, StreamingContext context )
: base( info, context ) => apiVersions = (string[]) info.GetValue( nameof( apiVersions ), typeof( string[] ) )!;
@@ -23,6 +36,10 @@ protected AmbiguousApiVersionException( SerializationInfo info, StreamingContext
///
/// The serialization info the exception is being serialized with.
/// The streaming context the exception is being serialized in.
+#if NET
+ [Obsolete( LegacyFormatterImplMessage, DiagnosticId = LegacyFormatterImplDiagId, UrlFormat = SharedUrlFormat )]
+#endif
+ [EditorBrowsable( EditorBrowsableState.Never )]
public override void GetObjectData( SerializationInfo info, StreamingContext context )
{
base.GetObjectData( info, context );
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/ApiVersionFormatProvider.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/ApiVersionFormatProvider.cs
index ddf26eca..76ad58af 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/ApiVersionFormatProvider.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/ApiVersionFormatProvider.cs
@@ -354,15 +354,8 @@ protected virtual void FormatGroupVersionPart(
in ReadOnlySpan format,
IFormatProvider? formatProvider )
{
- if ( text == null )
- {
- throw new ArgumentNullException( nameof( text ) );
- }
-
- if ( apiVersion == null )
- {
- throw new ArgumentNullException( nameof( apiVersion ) );
- }
+ ArgumentNullException.ThrowIfNull( text );
+ ArgumentNullException.ThrowIfNull( apiVersion );
if ( !apiVersion.GroupVersion.HasValue || format.IsEmpty )
{
@@ -448,15 +441,8 @@ protected virtual void FormatAllParts(
in ReadOnlySpan format,
IFormatProvider? formatProvider )
{
- if ( text == null )
- {
- throw new ArgumentNullException( nameof( text ) );
- }
-
- if ( apiVersion == null )
- {
- throw new ArgumentNullException( nameof( apiVersion ) );
- }
+ ArgumentNullException.ThrowIfNull( text );
+ ArgumentNullException.ThrowIfNull( apiVersion );
Span buffer = stackalloc char[10];
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/FormatWriter.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/FormatWriter.cs
index 3fa0645f..b0c8019a 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/FormatWriter.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/FormatWriter.cs
@@ -45,7 +45,7 @@ internal FormatWriter(
public bool Succeeded { get; private set; }
- public int Written => totalWritten;
+ public readonly int Written => totalWritten;
public void Write( in FormatToken token )
{
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParser.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParser.cs
index c806e8dd..cfbf616e 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParser.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParser.cs
@@ -20,10 +20,5 @@ public interface IApiVersionParser
/// The text to parse as an API version.
/// The parsed API version or null.
/// True if the parsing was successful; otherwise false.
- bool TryParse(
- ReadOnlySpan text,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out ApiVersion apiVersion );
+ bool TryParse( ReadOnlySpan text, [MaybeNullWhen( false )] out ApiVersion apiVersion );
}
\ No newline at end of file
diff --git a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParserExtensions.cs b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParserExtensions.cs
index 1eabc3b4..3d23a71e 100644
--- a/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParserExtensions.cs
+++ b/src/Abstractions/src/Asp.Versioning.Abstractions/netstandard2.0/IApiVersionParserExtensions.cs
@@ -15,11 +15,7 @@ public static class IApiVersionParserExtensions
/// The parsed API version.
public static ApiVersion Parse( this IApiVersionParser parser, string? text )
{
- if ( parser == null )
- {
- throw new ArgumentNullException( nameof( parser ) );
- }
-
+ ArgumentNullException.ThrowIfNull( parser );
return parser.Parse( text == null ? default : text.AsSpan() );
}
@@ -33,16 +29,9 @@ public static ApiVersion Parse( this IApiVersionParser parser, string? text )
public static bool TryParse(
this IApiVersionParser parser,
string? text,
-#if !NETSTANDARD
- [MaybeNullWhen( false )]
-#endif
- out ApiVersion apiVersion )
+ [MaybeNullWhen( false )] out ApiVersion apiVersion )
{
- if ( parser == null )
- {
- throw new ArgumentNullException( nameof( parser ) );
- }
-
+ ArgumentNullException.ThrowIfNull( parser );
return parser.TryParse( text == null ? default : text.AsSpan(), out apiVersion );
}
}
\ No newline at end of file
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs
index 07186a3e..11c896a5 100644
--- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs
+++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ApiVersionTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: DX
+
namespace Asp.Versioning;
public partial class ApiVersionTest
@@ -595,27 +597,27 @@ public void api_version_1_ge_api_version_2_should_return_expected_result( string
}
public static IEnumerable FormatData =>
- new[]
+ new object[][]
{
- new[] { null, "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha" },
- new[] { "", "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha" },
- new[] { "F", "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha" },
- new[] { "G", "2013-08-06", "2013-08-06" },
- new[] { "GG", "2013-08-06-Alpha", "2013-08-06-Alpha" },
- new[] { "G", "1.1", "" },
- new[] { "G", "1.1-Alpha", "" },
- new[] { "G", "2013-08-06.1.1", "2013-08-06" },
- new[] { "GG", "2013-08-06.1.1-Alpha", "2013-08-06-Alpha" },
- new[] { "V", "2013-08-06", "" },
- new[] { "VVVV", "2013-08-06-Alpha", "" },
- new[] { "VV", "1.1", "1.1" },
- new[] { "VVVV", "1.1-Alpha", "1.1-Alpha" },
- new[] { "VV", "2013-08-06.1.1", "1.1" },
- new[] { "VVVV", "2013-08-06.1.1-Alpha", "1.1-Alpha" },
- new[] { "S", "1.1-Alpha", "Alpha" },
- new[] { "'v'VVV", "1.1", "v1.1" },
- new[] { "'Major': %V, 'Minor': %v", "1.1", "Major: 1, Minor: 1" },
- new[] { "MMM yyyy '('S')'", "2013-08-06-preview.1", "Aug 2013 (preview.1)" },
+ [null, "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha"],
+ ["", "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha"],
+ ["F", "2013-08-06.1.1-Alpha", "2013-08-06.1.1-Alpha"],
+ ["G", "2013-08-06", "2013-08-06"],
+ ["GG", "2013-08-06-Alpha", "2013-08-06-Alpha"],
+ ["G", "1.1", ""],
+ ["G", "1.1-Alpha", ""],
+ ["G", "2013-08-06.1.1", "2013-08-06"],
+ ["GG", "2013-08-06.1.1-Alpha", "2013-08-06-Alpha"],
+ ["V", "2013-08-06", ""],
+ ["VVVV", "2013-08-06-Alpha", ""],
+ ["VV", "1.1", "1.1"],
+ ["VVVV", "1.1-Alpha", "1.1-Alpha"],
+ ["VV", "2013-08-06.1.1", "1.1"],
+ ["VVVV", "2013-08-06.1.1-Alpha", "1.1-Alpha"],
+ ["S", "1.1-Alpha", "Alpha"],
+ ["'v'VVV", "1.1", "v1.1"],
+ ["'Major': %V, 'Minor': %v", "1.1", "Major: 1, Minor: 1"],
+ ["MMM yyyy '('S')'", "2013-08-06-preview.1", "Aug 2013 (preview.1)"],
};
#if NETFRAMEWORK
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Asp.Versioning.Abstractions.Tests.csproj b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Asp.Versioning.Abstractions.Tests.csproj
index 668e879c..e2fcdd62 100644
--- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Asp.Versioning.Abstractions.Tests.csproj
+++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/Asp.Versioning.Abstractions.Tests.csproj
@@ -1,14 +1,14 @@
- net7.0;net452;net472
+ $(DefaultTargetFramework);net452;net472
Asp.Versioning
-
-
-
+
+
+
-
+
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IApiVersionParameterSourceExtensionsTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IApiVersionParameterSourceExtensionsTest.cs
index b1186e85..486977b0 100644
--- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IApiVersionParameterSourceExtensionsTest.cs
+++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/IApiVersionParameterSourceExtensionsTest.cs
@@ -146,6 +146,7 @@ public void get_parameter_names_should_return_matching_names()
{
// arrange
var source = new Mock();
+ var expected = new[] { "api-version", "ver" };
source.Setup( s => s.AddParameters( It.IsAny() ) )
.Callback( ( IApiVersionParameterDescriptionContext context ) =>
@@ -160,6 +161,6 @@ public void get_parameter_names_should_return_matching_names()
var names = source.Object.GetParameterNames( Query );
// assert
- names.Should().BeEquivalentTo( new[] { "api-version", "ver" } );
+ names.Should().BeEquivalentTo( expected );
}
}
\ No newline at end of file
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs
index ac1cb2c0..9cc9b693 100644
--- a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs
+++ b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/ISunsetPolicyManagerExtensionsTest.cs
@@ -65,11 +65,10 @@ public void resolve_policy_should_fall_back_to_global_result()
var expected = new SunsetPolicy();
var other = new SunsetPolicy();
- manager.Setup( m => m.TryGetPolicy( "Test", new ApiVersion( 1.0, null ), out other ) ).Returns( true );
- manager.Setup( m => m.TryGetPolicy( default, new ApiVersion( 1.0, null ), out expected ) ).Returns( true );
+ manager.Setup( m => m.TryGetPolicy( It.IsAny(), new ApiVersion( 1.0, null ), out expected ) ).Returns( true );
// act
- var policy = manager.Object.ResolvePolicyOrDefault( default, new ApiVersion( 1.0 ) );
+ var policy = manager.Object.ResolvePolicyOrDefault( "Test", new ApiVersion( 1.0 ) );
// assert
policy.Should().BeSameAs( expected );
diff --git a/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/net7.0/ApiVersionTest.cs b/src/Abstractions/test/Asp.Versioning.Abstractions.Tests/net#.0/ApiVersionTest.cs
similarity index 100%
rename from src/Abstractions/test/Asp.Versioning.Abstractions.Tests/net7.0/ApiVersionTest.cs
rename to src/Abstractions/test/Asp.Versioning.Abstractions.Tests/net#.0/ApiVersionTest.cs
diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/InteropFixture.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/InteropFixture.cs
new file mode 100644
index 00000000..b61d1d68
--- /dev/null
+++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/InteropFixture.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+// Ignore Spelling: Interop
+namespace Asp.Versioning.Http.Basic;
+
+using System.Web.Http;
+
+public class InteropFixture : BasicFixture
+{
+ protected override void OnConfigure( HttpConfiguration configuration )
+ {
+ configuration.ConvertProblemDetailsToErrorObject();
+ base.OnConfigure( configuration );
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when error objects are enabled.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when error objects are enabled.cs
new file mode 100644
index 00000000..b40bfa14
--- /dev/null
+++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/Basic/given a versioned ApiController/when error objects are enabled.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace given_a_versioned_ApiController;
+
+using Asp.Versioning;
+using Asp.Versioning.Http.Basic;
+
+public class when_error_objects_are_enabled : AcceptanceTest, IClassFixture
+{
+ [Fact]
+ public async Task then_the_response_should_not_be_problem_details()
+ {
+ // arrange
+ var example = new
+ {
+ error = new
+ {
+ code = default( string ),
+ message = default( string ),
+ target = default( string ),
+ innerError = new
+ {
+ message = default( string ),
+ },
+ },
+ };
+
+ // act
+ var response = await GetAsync( "api/values?api-version=3.0" );
+ var error = await response.Content.ReadAsExampleAsync( example );
+
+ // assert
+ response.Content.Headers.ContentType.MediaType.Should().Be( "application/json" );
+ error.Should().BeEquivalentTo(
+ new
+ {
+ error = new
+ {
+ code = "UnsupportedApiVersion",
+ message = "Unsupported API version",
+ innerError = new
+ {
+ message = "No route providing a controller name with API version '3.0' " +
+ "was found to match request URI 'http://localhost/api/values'.",
+ },
+ },
+ } );
+ }
+
+ public when_error_objects_are_enabled( InteropFixture fixture ) : base( fixture ) { }
+}
\ No newline at end of file
diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and attribute-based routing.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and attribute-based routing.cs
index 0beae68c..8cd50259 100644
--- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and attribute-based routing.cs
+++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and attribute-based routing.cs
@@ -2,6 +2,8 @@
#pragma warning disable IDE1006 // Naming Styles
+//// Ignore Spelling: Dbased
+
namespace given_a_versioned_ApiController_per_namespace;
using Asp.Versioning;
diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and convention-based routing.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and convention-based routing.cs
index 63801fb2..88f11bf8 100644
--- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and convention-based routing.cs
+++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/Http/UsingNamespace/given a versioned ApiController per namespace/when using a url segment and convention-based routing.cs
@@ -2,6 +2,8 @@
#pragma warning disable IDE1006 // Naming Styles
+//// Ignore Spelling: Dbased
+
namespace given_a_versioned_ApiController_per_namespace;
using Asp.Versioning;
diff --git a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs
index d69604f8..9c420a43 100644
--- a/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs
+++ b/src/AspNet/Acceptance/Asp.Versioning.WebApi.Acceptance.Tests/OData/ODataAcceptanceTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dspecific
+
namespace Asp.Versioning.OData;
using static System.Net.HttpStatusCode;
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/AdHocEdmScope.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/AdHocEdmScope.cs
index 2bcf1549..f6744216 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/AdHocEdmScope.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/AdHocEdmScope.cs
@@ -64,7 +64,7 @@ private static IReadOnlyList FilterResults(
continue;
}
- results ??= new();
+ results ??= [];
results.Add( apiDescription );
for ( var j = 0; j < conventions.Count; j++ )
@@ -73,7 +73,7 @@ private static IReadOnlyList FilterResults(
}
}
- return results?.ToArray() ?? Array.Empty();
+ return results?.ToArray() ?? [];
}
private static void ApplyAdHocEdm(
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
index 4c45eb70..bb3eed90 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/ApiExplorer/ODataApiExplorer.cs
@@ -78,10 +78,7 @@ protected override bool ShouldExploreAction(
IHttpRoute route,
ApiVersion apiVersion )
{
- if ( actionDescriptor == null )
- {
- throw new ArgumentNullException( nameof( actionDescriptor ) );
- }
+ ArgumentNullException.ThrowIfNull( actionDescriptor );
if ( route is not ODataRoute )
{
@@ -126,15 +123,8 @@ protected override bool ShouldExploreController(
IHttpRoute route,
ApiVersion apiVersion )
{
- if ( controllerDescriptor == null )
- {
- throw new ArgumentNullException( nameof( controllerDescriptor ) );
- }
-
- if ( route == null )
- {
- throw new ArgumentNullException( nameof( route ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerDescriptor );
+ ArgumentNullException.ThrowIfNull( route );
if ( controllerDescriptor.ControllerType.IsMetadataController() )
{
@@ -168,10 +158,7 @@ protected override Collection ExploreRouteControllers(
IHttpRoute route,
ApiVersion apiVersion )
{
- if ( controllerMappings == null )
- {
- throw new ArgumentNullException( nameof( controllerMappings ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerMappings );
Collection apiDescriptions;
@@ -194,7 +181,7 @@ protected override Collection ExploreRouteControllers(
return apiDescriptions;
}
- apiDescriptions = new();
+ apiDescriptions = [];
var modelSelector = Configuration.GetODataRootContainer( route ).GetRequiredService();
var edmModel = modelSelector.SelectModel( apiVersion );
@@ -260,10 +247,7 @@ protected virtual void ExploreQueryOptions(
IEnumerable apiDescriptions,
ODataUriResolver uriResolver )
{
- if ( uriResolver == null )
- {
- throw new ArgumentNullException( nameof( uriResolver ) );
- }
+ ArgumentNullException.ThrowIfNull( uriResolver );
var queryOptions = Options.QueryOptions;
var settings = new ODataQueryOptionSettings()
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Asp.Versioning.WebApi.OData.ApiExplorer.csproj b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Asp.Versioning.WebApi.OData.ApiExplorer.csproj
index 0cd36a0c..b8bedf18 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Asp.Versioning.WebApi.OData.ApiExplorer.csproj
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Asp.Versioning.WebApi.OData.ApiExplorer.csproj
@@ -1,8 +1,8 @@
- 7.0.0
- 7.0.0.0
+ 7.1.0
+ 7.1.0.0
net45;net472
Asp.Versioning
ASP.NET Web API Versioning API Explorer for OData v4.0
@@ -15,8 +15,12 @@
+
+
+
+
@@ -27,6 +31,13 @@
+
+
+
+
+
+
+
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs
index 1c1a2f49..410f48ff 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataAttributeVisitor.cs
@@ -13,6 +13,6 @@ private void VisitAction( HttpActionDescriptor action )
var attributes = new List( controller.GetCustomAttributes( inherit: true ) );
attributes.AddRange( action.GetCustomAttributes( inherit: true ) );
- VisitEnableQuery( attributes );
+ VisitEnableQuery( attributes.ToArray() );
}
}
\ No newline at end of file
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs
index 7b008e3a..087a1b21 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs
@@ -19,10 +19,7 @@ public partial class ODataValidationSettingsConvention
///
public virtual void ApplyTo( ApiDescription apiDescription )
{
- if ( apiDescription == null )
- {
- throw new ArgumentNullException( nameof( apiDescription ) );
- }
+ ArgumentNullException.ThrowIfNull( apiDescription );
if ( !IsSupported( apiDescription.HttpMethod.Method ) )
{
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilderContext.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilderContext.cs
index 23fe0a48..3ff17b36 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilderContext.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/Routing/ODataRouteBuilderContext.cs
@@ -355,7 +355,7 @@ private static bool IsNavigationPropertyLink( IEdmEntitySet? entitySet, IEdmSing
{
if ( propertyNames is null )
{
- propertyNames = new();
+ propertyNames = [];
}
else
{
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/SR.Designer.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/SR.Designer.cs
new file mode 100644
index 00000000..978b3fa3
--- /dev/null
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/SR.Designer.cs
@@ -0,0 +1,72 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Asp.Versioning {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class SR {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal SR() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Asp.Versioning.SR", typeof(SR).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The value cannot be an empty string..
+ ///
+ internal static string Argument_EmptyString {
+ get {
+ return ResourceManager.GetString("Argument_EmptyString", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/SR.resx b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/SR.resx
new file mode 100644
index 00000000..9222b107
--- /dev/null
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData.ApiExplorer/SR.resx
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The value cannot be an empty string.
+
+
\ No newline at end of file
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Asp.Versioning.WebApi.OData.csproj b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Asp.Versioning.WebApi.OData.csproj
index 87b75cd3..7229ae69 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Asp.Versioning.WebApi.OData.csproj
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/Asp.Versioning.WebApi.OData.csproj
@@ -1,8 +1,8 @@
- 7.0.0
- 7.0.0.0
+ 7.1.0
+ 7.1.0.0
net45;net472
Asp.Versioning
API Versioning for ASP.NET Web API with OData v4.0
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/EdmModelSelector.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/EdmModelSelector.cs
index f8f7e17b..ed7497b9 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/EdmModelSelector.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/EdmModelSelector.cs
@@ -53,8 +53,8 @@ public EdmModelSelector( IEnumerable models, IApiVersionSelector apiV
break;
default:
- versions = new();
- collection = new();
+ versions = [];
+ collection = [];
foreach ( var model in models )
{
diff --git a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/VersionedODataModelBuilder.cs b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/VersionedODataModelBuilder.cs
index 7f493939..f08a3c18 100644
--- a/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/VersionedODataModelBuilder.cs
+++ b/src/AspNet/OData/src/Asp.Versioning.WebApi.OData/OData/VersionedODataModelBuilder.cs
@@ -78,7 +78,7 @@ protected virtual IReadOnlyList GetApiVersions()
if ( versions.Count > 0 && supported == null )
{
- supported = new();
+ supported = [];
}
for ( var j = 0; j < versions.Count; j++ )
@@ -90,7 +90,7 @@ protected virtual IReadOnlyList GetApiVersions()
if ( versions.Count > 0 && deprecated == null )
{
- deprecated = new();
+ deprecated = [];
}
for ( var j = 0; j < versions.Count; j++ )
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs
index b31d7496..72c1025f 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs
@@ -1,5 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dlike
+//// Ignore Spelling: Multipart
+//// Ignore Spelling: nonaction
+//// Ignore Spelling: nonquery
+
namespace Asp.Versioning.Conventions;
using Asp.Versioning.Description;
@@ -10,7 +15,6 @@ namespace Asp.Versioning.Conventions;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Query;
using Microsoft.OData.Edm;
-using System.Collections.ObjectModel;
using System.Net.Http;
using System.Reflection;
using System.Web.Http;
@@ -565,8 +569,8 @@ public static IEnumerable EnableQueryAttributeData
var controller = new Mock() { CallBase = true };
var action = new Mock() { CallBase = true };
- controller.Setup( m => m.GetCustomAttributes( It.IsAny() ) ).Returns( new Collection() );
- action.Setup( m => m.GetCustomAttributes( It.IsAny() ) ).Returns( new Collection() );
+ controller.Setup( m => m.GetCustomAttributes( It.IsAny() ) ).Returns( [] );
+ action.Setup( m => m.GetCustomAttributes( It.IsAny() ) ).Returns( [] );
var actionDescriptor = action.Object;
var responseType = singleResult ? typeof( object ) : typeof( IEnumerable );
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs
index 24d7a24e..bab6814e 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs
@@ -17,15 +17,15 @@ namespace Asp.Versioning.Simulators.V1;
[RoutePrefix( "api/books" )]
public class BooksController : ApiController
{
- private static readonly Book[] books = new Book[]
- {
+ private static readonly Book[] books =
+ [
new() { Id = "9781847490599", Title = "Anna Karenina", Author = "Leo Tolstoy", Published = 1878 },
new() { Id = "9780198800545", Title = "War and Peace", Author = "Leo Tolstoy", Published = 1869 },
new() { Id = "9780684801520", Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", Published = 1925 },
new() { Id = "9780486280615", Title = "The Adventures of Huckleberry Finn", Author = "Mark Twain", Published = 1884 },
new() { Id = "9780140430820", Title = "Moby Dick", Author = "Herman Melville", Published = 1851 },
new() { Id = "9780060934347", Title = "Don Quixote", Author = "Miguel de Cervantes", Published = 1605 },
- };
+ ];
///
/// Gets all books.
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V3/SuppliersController.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V3/SuppliersController.cs
index f3264880..a27e95ff 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V3/SuppliersController.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/Simulators/V3/SuppliersController.cs
@@ -1,9 +1,11 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
#pragma warning disable IDE0060 // Remove unused parameter
+#pragma warning disable IDE0079 // Remove unnecessary suppression
#pragma warning disable SA1625 // Element documentation should not be copied and pasted
namespace Asp.Versioning.Simulators.V3;
+#pragma warning restore IDE0079 // Remove unnecessary suppression
using Asp.Versioning.OData;
using Asp.Versioning.Simulators.Models;
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/Description/ApiDescriptionExtensionsTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/Description/ApiDescriptionExtensionsTest.cs
index 88a780dc..5d56a57a 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/Description/ApiDescriptionExtensionsTest.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.ApiExplorer.Tests/System.Web.Http/Description/ApiDescriptionExtensionsTest.cs
@@ -68,7 +68,7 @@ private static VersionedApiDescription CreateApiDescription( IEdmModel model )
{
var configuration = new HttpConfiguration();
var controllerType = typeof( Asp.Versioning.Simulators.V1.OrdersController );
- var actionMethod = controllerType.GetRuntimeMethod( "Get", new[] { typeof( int ) } );
+ var actionMethod = controllerType.GetRuntimeMethod( "Get", [typeof( int )] );
var controllerDescriptor = new HttpControllerDescriptor( configuration, "Orders", controllerType );
var actionDescriptor = new ReflectedHttpActionDescriptor( controllerDescriptor, actionMethod );
var apiDescription = new VersionedApiDescription()
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Asp.Versioning.WebApi.OData.Tests.csproj b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Asp.Versioning.WebApi.OData.Tests.csproj
index 592ea6dc..30285b43 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Asp.Versioning.WebApi.OData.Tests.csproj
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Asp.Versioning.WebApi.OData.Tests.csproj
@@ -1,9 +1,9 @@
-
+
-
- net452;net472
- Asp.Versioning
-
+
+ net452;net472
+ Asp.Versioning
+
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
index 60292852..c39e10b4 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
@@ -37,6 +37,7 @@ public async Task options_should_return_expected_headers()
configuration.AddApiVersioning(
options =>
{
+ options.ReportApiVersions = true;
options.Policies.Sunset( "VersionedMetadata" )
.Link( "policies" )
.Title( "Versioning Policy" )
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs
index a42188cc..f06fc620 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dneutral
+
namespace Asp.Versioning.Routing;
using Microsoft.AspNet.OData;
diff --git a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
index 4544139c..c2e86cbc 100644
--- a/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
+++ b/src/AspNet/OData/test/Asp.Versioning.WebApi.OData.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
@@ -46,7 +46,7 @@ public void map_versioned_odata_route_should_return_expected_result( string rout
var selector = GetODataRootContainer( configuration, routeName ).GetRequiredService();
var routingConventions = GetRoutingConventions( configuration, route );
- selector.ApiVersions.Should().Equal( new ApiVersion[] { new( 1, 0 ), new( 2, 0 ) } );
+ selector.ApiVersions.Should().Equal( [new( 1, 0 ), new( 2, 0 )] );
routingConventions[0].Should().BeOfType();
routingConventions[1].Should().BeOfType();
routingConventions.OfType().Should().BeEmpty();
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/ApiExplorerOptions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/ApiExplorerOptions.cs
index 09df17e5..63aaa0ec 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/ApiExplorerOptions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/ApiExplorerOptions.cs
@@ -44,4 +44,14 @@ public partial class ApiExplorerOptions
///
/// The name associated with the API version route constraint .
public string RouteConstraintName => options.Value.RouteConstraintName;
+
+ ///
+ /// Gets or sets the API version selector.
+ ///
+ /// An API version selector object.
+ public IApiVersionSelector ApiVersionSelector
+ {
+ get => apiVersionSelector ?? options.Value.ApiVersionSelector;
+ set => apiVersionSelector = value;
+ }
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs
index 83d99d0e..c7585844 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/ApiExplorer/VersionedApiExplorer.cs
@@ -101,7 +101,7 @@ public IDocumentationProvider DocumentationProvider
/// The configured sunset policy manager .
protected ISunsetPolicyManager SunsetPolicyManager
{
- get => sunsetPolicyManager ??= Configuration.DependencyResolver.GetSunsetPolicyManager();
+ get => sunsetPolicyManager ??= Configuration.GetSunsetPolicyManager();
set => sunsetPolicyManager = value;
}
@@ -227,7 +227,7 @@ protected virtual ApiDescriptionGroupCollection InitializeApiDescriptions()
}
var routes = FlattenRoutes( Configuration.Routes ).ToArray();
- var policyManager = Configuration.DependencyResolver.GetSunsetPolicyManager();
+ var policyManager = Configuration.GetSunsetPolicyManager();
foreach ( var apiVersion in FlattenApiVersions( controllerMappings ) )
{
@@ -713,7 +713,7 @@ protected virtual Collection ExploreRouteControllers(
var apiDescriptions = new Collection();
var routeTemplate = route.RouteTemplate;
- string controllerVariableValue;
+ string? controllerVariableValue;
if ( controllerVariableRegex.IsMatch( routeTemplate ) )
{
@@ -734,12 +734,12 @@ protected virtual Collection ExploreRouteControllers(
}
}
else if ( route.Defaults.TryGetValue( RouteValueKeys.Controller, out controllerVariableValue ) &&
- controllerMappings.TryGetValue( controllerVariableValue, out var controllerDescriptor ) )
+ controllerMappings.TryGetValue( controllerVariableValue!, out var controllerDescriptor ) )
{
// bound controller variable {controller = "controllerName"}
foreach ( var nestedControllerDescriptor in controllerDescriptor.AsEnumerable() )
{
- if ( ShouldExploreController( controllerVariableValue, nestedControllerDescriptor, route, apiVersion ) )
+ if ( ShouldExploreController( controllerVariableValue!, nestedControllerDescriptor, route, apiVersion ) )
{
ExploreRouteActions( route, routeTemplate, nestedControllerDescriptor, apiDescriptions, apiVersion );
}
@@ -782,7 +782,7 @@ private void ExploreRouteActions(
return;
}
- string actionVariableValue;
+ string? actionVariableValue;
if ( actionVariableRegex.IsMatch( localPath ) )
{
@@ -935,17 +935,8 @@ private static bool ShouldEmitPrefixes( ICollection par
parameter.CanConvertPropertiesFromString() ) > 1;
}
- private static Type GetCollectionElementType( Type collectionType )
- {
- var elementType = collectionType.GetElementType();
-
- if ( elementType == null )
- {
- elementType = typeof( ICollection<> ).GetGenericBinderTypeArgs( collectionType ).First();
- }
-
- return elementType;
- }
+ private static Type GetCollectionElementType( Type collectionType ) =>
+ collectionType.GetElementType() ?? typeof( ICollection<> ).GetGenericBinderTypeArgs( collectionType ).First();
private static void AddPlaceholderForProperties(
Dictionary parameterValuesForRoute,
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Asp.Versioning.WebApi.ApiExplorer.csproj b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Asp.Versioning.WebApi.ApiExplorer.csproj
index 5ded1914..9622bbfd 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Asp.Versioning.WebApi.ApiExplorer.csproj
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Asp.Versioning.WebApi.ApiExplorer.csproj
@@ -1,8 +1,8 @@
- 7.0.0
- 7.0.0.0
+ 7.1.0
+ 7.1.0.0
net45;net472
ASP.NET Web API Versioning API Explorer
The API Explorer extensions for ASP.NET Web API Versioning.
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs
index 596bd634..469c44f5 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiDescriptionGroup.cs
@@ -56,5 +56,5 @@ public ApiDescriptionGroup( ApiVersion apiVersion, string name )
///
/// A collection of
/// versioned API descriptions .
- public virtual Collection ApiDescriptions { get; } = new();
+ public virtual Collection ApiDescriptions { get; } = [];
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiVersionParameterDescriptionContext.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiVersionParameterDescriptionContext.cs
index c1e5fdf6..f92c6857 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiVersionParameterDescriptionContext.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/ApiVersionParameterDescriptionContext.cs
@@ -30,9 +30,9 @@ public class ApiVersionParameterDescriptionContext : IApiVersionParameterDescrip
public ApiVersionParameterDescriptionContext( ApiDescription apiDescription, ApiVersion apiVersion, ApiExplorerOptions options )
{
Options = options ?? throw new ArgumentNullException( nameof( options ) );
- ApiDescription = apiDescription;
- ApiVersion = apiVersion;
- optional = options.AssumeDefaultVersionWhenUnspecified && apiVersion == options.DefaultApiVersion;
+ ApiDescription = apiDescription ?? throw new ArgumentNullException( nameof( apiDescription ) );
+ ApiVersion = apiVersion ?? throw new ArgumentNullException( nameof( apiVersion ) );
+ optional = FirstParameterIsOptional( apiDescription, apiVersion, options );
}
///
@@ -242,4 +242,21 @@ private static void CloneFormattersAndAddMediaTypeParameter( NameValueHeaderValu
formatters.Add( formatter );
}
}
+
+ private static bool FirstParameterIsOptional(
+ ApiDescription apiDescription,
+ ApiVersion apiVersion,
+ ApiExplorerOptions options )
+ {
+ if ( !options.AssumeDefaultVersionWhenUnspecified )
+ {
+ return false;
+ }
+
+ var mapping = ApiVersionMapping.Explicit | ApiVersionMapping.Implicit;
+ var model = apiDescription.ActionDescriptor.GetApiVersionMetadata().Map( mapping );
+ var defaultApiVersion = options.ApiVersionSelector.SelectVersion( model );
+
+ return apiVersion == defaultApiVersion;
+ }
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs
index e5ab4a68..da07e7d1 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi.ApiExplorer/Description/VersionedApiDescription.cs
@@ -69,7 +69,7 @@ public ApiVersion ApiVersion
///
/// A collection of arbitrary metadata properties
/// associated with the API description .
- public IDictionary Properties => properties ??= new();
+ public IDictionary Properties => properties ??= [];
private static Action CreateSetResponseDescriptionMutator()
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs
index 284c66ba..0383fb9c 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ApiVersionRequestProperties.cs
@@ -88,7 +88,7 @@ public ApiVersion? RequestedApiVersion
return apiVersion;
}
- var parser = request.GetConfiguration().DependencyResolver.GetApiVersionParser();
+ var parser = request.GetConfiguration().GetApiVersionParser();
try
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj
index 5da0add5..761b386b 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Asp.Versioning.WebApi.csproj
@@ -1,8 +1,8 @@
- 7.0.0
- 7.0.0.0
+ 7.1.0
+ 7.1.0.0
net45;net472
ASP.NET Web API Versioning
A service API versioning library for Microsoft ASP.NET Web API.
@@ -15,8 +15,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ActionSelectorCacheItem.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ActionSelectorCacheItem.cs
index 0acd241f..4ae17299 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ActionSelectorCacheItem.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ActionSelectorCacheItem.cs
@@ -23,10 +23,10 @@ namespace Asp.Versioning.Controllers;
///
internal sealed class ActionSelectorCacheItem
{
- private static readonly HttpMethod[] cacheListMethodKinds = new[] { HttpMethod.Get, HttpMethod.Put, HttpMethod.Post };
+ private static readonly HttpMethod[] cacheListMethodKinds = [HttpMethod.Get, HttpMethod.Put, HttpMethod.Post];
private readonly HttpControllerDescriptor controllerDescriptor;
private readonly CandidateAction[] combinedCandidateActions;
- private readonly Dictionary actionParameterNames = new();
+ private readonly Dictionary actionParameterNames = [];
private readonly ILookup combinedActionNameMapping;
private StandardActionSelectionCache? standardActions;
@@ -76,7 +76,7 @@ private void InitializeStandardActions()
if ( controllerDescriptor.IsAttributeRouted() )
{
- selectionCache.StandardCandidateActions = Array.Empty();
+ selectionCache.StandardCandidateActions = [];
}
else
{
@@ -94,7 +94,7 @@ private void InitializeStandardActions()
}
}
- selectionCache.StandardCandidateActions = standardCandidateActions.ToArray();
+ selectionCache.StandardCandidateActions = [.. standardCandidateActions];
}
selectionCache.StandardActionNameMapping =
@@ -118,11 +118,7 @@ private void InitializeStandardActions()
HttpControllerContext controllerContext,
Func, HttpActionDescriptor?> selector )
{
- if ( selector == null )
- {
- throw new ArgumentNullException( nameof( selector ) );
- }
-
+ ArgumentNullException.ThrowIfNull( selector );
InitializeStandardActions();
var firstAttempt = FindAction( controllerContext, selector, ignoreSubRoutes: false );
@@ -304,7 +300,7 @@ private static List GetInitialCandidateWithParameterL
continue;
}
- subRouteData.Values.TryGetValue( RouteValueKeys.Action, out string actionName );
+ subRouteData.Values.TryGetValue( RouteValueKeys.Action, out string? actionName );
for ( var i = 0; i < candidates.Length; i++ )
{
@@ -335,9 +331,9 @@ private CandidateAction[] GetInitialCandidateList( HttpControllerContext control
var routeData = controllerContext.RouteData;
CandidateAction[] candidates;
- if ( routeData.Values.TryGetValue( RouteValueKeys.Action, out string actionName ) )
+ if ( routeData.Values.TryGetValue( RouteValueKeys.Action, out string? actionName ) )
{
- var actionsFoundByName = standardActions!.StandardActionNameMapping![actionName].ToArray();
+ var actionsFoundByName = standardActions!.StandardActionNameMapping![actionName!].ToArray();
if ( actionsFoundByName.Length == 0 )
{
@@ -412,7 +408,7 @@ private List FindActionMatchRequiredRouteAndQueryPara
}
private List FindActionMatchMostRouteAndQueryParameters( List candidatesFound ) =>
- candidatesFound.Count < 2 ? candidatesFound : candidatesFound.GroupBy( c => actionParameterNames[c.ActionDescriptor].Length ).OrderByDescending( g => g.Key ).First().ToList();
+ candidatesFound.Count < 2 ? candidatesFound : [.. candidatesFound.GroupBy( c => actionParameterNames[c.ActionDescriptor].Length ).OrderByDescending( g => g.Key ).First()];
private static CandidateActionWithParams[] GetCandidateActionsWithBindings( HttpControllerContext controllerContext, CandidateAction[] candidatesFound )
{
@@ -485,7 +481,7 @@ private static CandidateAction[] FindActionsForMethod( HttpMethod method, Candid
{
var listCandidates = new List();
FindActionsForMethod( method, candidates, listCandidates );
- return listCandidates.ToArray();
+ return [.. listCandidates];
}
private static void FindActionsForMethod( HttpMethod method, CandidateAction[] candidates, List listCandidates )
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionActionSelector.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionActionSelector.cs
index a8b5a729..e23f72a8 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionActionSelector.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionActionSelector.cs
@@ -24,11 +24,7 @@ public class ApiVersionActionSelector : IHttpActionSelector
/// controller context .
public virtual HttpActionDescriptor? SelectAction( HttpControllerContext controllerContext )
{
- if ( controllerContext == null )
- {
- throw new ArgumentNullException( nameof( controllerContext ) );
- }
-
+ ArgumentNullException.ThrowIfNull( controllerContext );
var internalSelector = GetInternalSelector( controllerContext.ControllerDescriptor );
return internalSelector.SelectAction( controllerContext, SelectActionVersion );
}
@@ -41,10 +37,7 @@ public class ApiVersionActionSelector : IHttpActionSelector
/// specified controller descriptor .
public virtual ILookup GetActionMapping( HttpControllerDescriptor controllerDescriptor )
{
- if ( controllerDescriptor == null )
- {
- throw new ArgumentNullException( nameof( controllerDescriptor ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerDescriptor );
var actionMappings = ( from descriptor in controllerDescriptor.AsEnumerable( includeCandidates: true )
let selector = GetInternalSelector( descriptor )
@@ -65,15 +58,8 @@ public virtual ILookup GetActionMapping( HttpContr
/// ambiguous among the provided list of candidate actions .
protected virtual HttpActionDescriptor? SelectActionVersion( HttpControllerContext controllerContext, IReadOnlyList candidateActions )
{
- if ( controllerContext == null )
- {
- throw new ArgumentNullException( nameof( controllerContext ) );
- }
-
- if ( candidateActions == null )
- {
- throw new ArgumentNullException( nameof( candidateActions ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerContext );
+ ArgumentNullException.ThrowIfNull( candidateActions );
if ( candidateActions.Count == 0 )
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionParameterBinding.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionParameterBinding.cs
index cc34b6a8..a9f4d9af 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionParameterBinding.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/ApiVersionParameterBinding.cs
@@ -25,11 +25,7 @@ public override Task ExecuteBindingAsync(
HttpActionContext actionContext,
CancellationToken cancellationToken )
{
- if ( actionContext == null )
- {
- throw new ArgumentNullException( nameof( actionContext ) );
- }
-
+ ArgumentNullException.ThrowIfNull( actionContext );
var value = actionContext.Request.ApiVersionProperties().RequestedApiVersion;
SetValue( actionContext, value );
return CompletedTask;
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/HttpControllerDescriptorGroup.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/HttpControllerDescriptorGroup.cs
index 4e3aab24..884a1747 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/HttpControllerDescriptorGroup.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Controllers/HttpControllerDescriptorGroup.cs
@@ -32,7 +32,7 @@ public class HttpControllerDescriptorGroup : HttpControllerDescriptor, IReadOnly
/// HTTP controller descriptors .
public HttpControllerDescriptorGroup( HttpConfiguration configuration, string controllerName, params HttpControllerDescriptor[] controllerDescriptors )
: base( configuration, controllerName, controllerDescriptors?[0].ControllerType ) =>
- descriptors = controllerDescriptors ?? throw new ArgumentNullException( nameof( controllerDescriptors ) );
+ descriptors = controllerDescriptors ?? throw new System.ArgumentNullException( nameof( controllerDescriptors ) );
///
/// Initializes a new instance of the class.
@@ -50,7 +50,7 @@ public HttpControllerDescriptorGroup( HttpConfiguration configuration, string co
/// HTTP controller descriptors .
public HttpControllerDescriptorGroup( HttpConfiguration configuration, string controllerName, IReadOnlyList controllerDescriptors )
: base( configuration, controllerName, controllerDescriptors?[0].ControllerType ) =>
- descriptors = controllerDescriptors ?? throw new ArgumentNullException( nameof( controllerDescriptors ) );
+ descriptors = controllerDescriptors ?? throw new System.ArgumentNullException( nameof( controllerDescriptors ) );
///
/// Creates and returns a controller for the specified request.
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ActionApiVersionConventionBuilderBase.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ActionApiVersionConventionBuilderBase.cs
index aea7e59b..9dd43313 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ActionApiVersionConventionBuilderBase.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ActionApiVersionConventionBuilderBase.cs
@@ -13,10 +13,7 @@ public partial class ActionApiVersionConventionBuilderBase : IApiVersionConventi
///
public virtual void ApplyTo( HttpActionDescriptor item )
{
- if ( item == null )
- {
- throw new ArgumentNullException( nameof( item ) );
- }
+ ArgumentNullException.ThrowIfNull( item );
var attributes = new List();
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ControllerApiVersionConventionBuilderBase.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ControllerApiVersionConventionBuilderBase.cs
index 0c194afe..1480d3ba 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ControllerApiVersionConventionBuilderBase.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Conventions/ControllerApiVersionConventionBuilderBase.cs
@@ -35,10 +35,7 @@ public abstract class ControllerApiVersionConventionBuilderBase : ApiVersionConv
/// to apply the conventions to.
public virtual void ApplyTo( HttpControllerDescriptor item )
{
- if ( item == null )
- {
- throw new ArgumentNullException( nameof( item ) );
- }
+ ArgumentNullException.ThrowIfNull( item );
var attributes = new List();
@@ -54,7 +51,7 @@ public virtual void ApplyTo( HttpControllerDescriptor item )
/// The method representing the action to retrieve the convention for.
/// The retrieved convention or null .
/// True if the convention was successfully retrieved; otherwise, false.
- protected abstract bool TryGetConvention( MethodInfo method, out IApiVersionConvention convention );
+ protected abstract bool TryGetConvention( MethodInfo method, [MaybeNullWhen( false )] out IApiVersionConvention convention );
private void ApplyActionConventions( HttpControllerDescriptor controller )
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DefaultApiVersionReporter.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DefaultApiVersionReporter.cs
index dc05e257..caa7f0be 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DefaultApiVersionReporter.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DefaultApiVersionReporter.cs
@@ -10,11 +10,6 @@ namespace Asp.Versioning;
///
public partial class DefaultApiVersionReporter
{
- private static DefaultApiVersionReporter? instance;
-
- internal static IReportApiVersions GetOrCreate( ISunsetPolicyManager sunsetPolicyManager ) =>
- instance ??= new( sunsetPolicyManager );
-
private static void AddApiVersionHeader( HttpResponseHeaders headers, string headerName, IReadOnlyList versions )
{
if ( versions.Count == 0 || headers.Contains( headerName ) )
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs
new file mode 100644
index 00000000..7725393c
--- /dev/null
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dependencies/DefaultContainer.cs
@@ -0,0 +1,84 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning.Dependencies;
+
+using Asp.Versioning;
+using Asp.Versioning.Conventions;
+using System.ComponentModel.Design;
+using System.Web.Http.Dependencies;
+
+internal sealed class DefaultContainer : IDependencyResolver, IDependencyScope
+{
+ private readonly ServiceContainer container = new();
+ private bool disposed;
+
+ internal DefaultContainer()
+ {
+ container.AddService( typeof( ApiVersioningOptions ), static ( sc, t ) => new ApiVersioningOptions() );
+ container.AddService( typeof( IApiVersionParser ), static ( sc, t ) => ApiVersionParser.Default );
+ container.AddService( typeof( IControllerNameConvention ), static ( sc, t ) => ControllerNameConvention.Default );
+ container.AddService( typeof( IProblemDetailsFactory ), static ( sc, t ) => new ProblemDetailsFactory() );
+ container.AddService( typeof( ISunsetPolicyManager ), NewSunsetPolicyManager );
+ container.AddService( typeof( IReportApiVersions ), NewApiVersionReporter );
+ }
+
+ public ApiVersioningOptions ApiVersioningOptions
+ {
+ get => GetApiVersioningOptions( container );
+ set
+ {
+ container.RemoveService( typeof( ApiVersioningOptions ) );
+ container.AddService( typeof( ApiVersioningOptions ), value );
+ }
+ }
+
+ public void Replace( Type serviceType, ServiceCreatorCallback activator )
+ {
+ container.RemoveService( serviceType );
+ container.AddService( serviceType, activator );
+ }
+
+ public IDependencyScope BeginScope() => this;
+
+ public void Dispose()
+ {
+ if ( disposed )
+ {
+ return;
+ }
+
+ disposed = true;
+ container.Dispose();
+ }
+
+ public object GetService( Type serviceType ) => container.GetService( serviceType );
+
+ public IEnumerable GetServices( Type serviceType )
+ {
+ var service = container.GetService( serviceType );
+
+ if ( service is not null )
+ {
+ yield return service;
+ }
+ }
+
+ private static ApiVersioningOptions GetApiVersioningOptions( IServiceProvider serviceProvider ) =>
+ (ApiVersioningOptions) serviceProvider.GetService( typeof( ApiVersioningOptions ) );
+
+ private static ISunsetPolicyManager NewSunsetPolicyManager( IServiceProvider serviceProvider, Type type ) =>
+ new SunsetPolicyManager( GetApiVersioningOptions( serviceProvider ) );
+
+ private static IReportApiVersions NewApiVersionReporter( IServiceProvider serviceProvider, Type type )
+ {
+ var options = GetApiVersioningOptions( serviceProvider );
+
+ if ( options.ReportApiVersions )
+ {
+ var sunsetPolicyManager = (ISunsetPolicyManager) serviceProvider.GetService( typeof( ISunsetPolicyManager ) );
+ return new DefaultApiVersionReporter( sunsetPolicyManager );
+ }
+
+ return new DoNotReportApiVersions();
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs
index f8b86bcc..6cad7f56 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DependencyResolverExtensions.cs
@@ -3,24 +3,39 @@
namespace Asp.Versioning;
using Asp.Versioning.Conventions;
+using System.Globalization;
+using System.Web.Http;
using System.Web.Http.Dependencies;
internal static class DependencyResolverExtensions
{
- internal static TService? GetService( this IDependencyResolver resolver ) => (TService) resolver.GetService( typeof( TService ) );
-
- internal static IApiVersionParser GetApiVersionParser( this IDependencyResolver resolver ) =>
- resolver.GetService() ?? ApiVersionParser.Default;
-
- internal static IReportApiVersions GetApiVersionReporter( this IDependencyResolver resolver ) =>
- resolver.GetService() ?? DefaultApiVersionReporter.GetOrCreate( resolver.GetSunsetPolicyManager() );
-
- internal static IControllerNameConvention GetControllerNameConvention( this IDependencyResolver resolver ) =>
- resolver.GetService() ?? ControllerNameConvention.Default;
-
- internal static IProblemDetailsFactory GetProblemDetailsFactory( this IDependencyResolver resolver ) =>
- resolver.GetService() ?? ProblemDetailsFactory.Default;
-
- internal static ISunsetPolicyManager GetSunsetPolicyManager( this IDependencyResolver resolver ) =>
- resolver.GetService() ?? SunsetPolicyManager.Default;
+ internal static TService? GetService( this IDependencyResolver resolver ) =>
+ (TService) resolver.GetService( typeof( TService ) );
+
+ internal static TService GetRequiredService( this IDependencyResolver resolver )
+ {
+ var service = resolver.GetService();
+ var message = string.Format( CultureInfo.CurrentCulture, SR.NoServiceRegistered, typeof( TService ) );
+ return service ?? throw new InvalidOperationException( message );
+ }
+
+ internal static IApiVersionParser GetApiVersionParser( this HttpConfiguration configuration ) =>
+ configuration.DependencyResolver.GetService() ??
+ configuration.ApiVersioningServices().GetRequiredService();
+
+ internal static IReportApiVersions GetApiVersionReporter( this HttpConfiguration configuration ) =>
+ configuration.DependencyResolver.GetService() ??
+ configuration.ApiVersioningServices().GetRequiredService();
+
+ internal static IControllerNameConvention GetControllerNameConvention( this HttpConfiguration configuration ) =>
+ configuration.DependencyResolver.GetService() ??
+ configuration.ApiVersioningServices().GetRequiredService();
+
+ internal static IProblemDetailsFactory GetProblemDetailsFactory( this HttpConfiguration configuration ) =>
+ configuration.DependencyResolver.GetService() ??
+ configuration.ApiVersioningServices().GetRequiredService();
+
+ internal static ISunsetPolicyManager GetSunsetPolicyManager( this HttpConfiguration configuration ) =>
+ configuration.DependencyResolver.GetService() ??
+ configuration.ApiVersioningServices().GetRequiredService();
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ApiVersionControllerSelector.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ApiVersionControllerSelector.cs
index a9473c8a..c889b994 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ApiVersionControllerSelector.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ApiVersionControllerSelector.cs
@@ -123,7 +123,7 @@ public virtual IDictionary GetControllerMappin
return null;
}
- if ( routeData.Values.TryGetValue( RouteDataTokenKeys.Controller, out string controller ) )
+ if ( routeData.Values.TryGetValue( RouteDataTokenKeys.Controller, out string? controller ) )
{
return controller;
}
@@ -221,7 +221,7 @@ private static void ApplyImplicitConventions( HttpControllerDescriptor controlle
}
var actions = mapping.SelectMany( g => g );
- var namingConvention = controller.Configuration.DependencyResolver.GetControllerNameConvention();
+ var namingConvention = controller.Configuration.GetControllerNameConvention();
var name = namingConvention.GroupName( controller.ControllerName );
var metadata = new ApiVersionMetadata( implicitVersionModel, implicitVersionModel, name );
@@ -258,7 +258,7 @@ private static HttpControllerDescriptor[] ApplyCollatedModels(
CollateControllerModels( controllerModels, visitedControllers, CollateActionModels( actionModels, visitedActions ) );
ApplyCollatedModelsToActions( configuration, visitedActions );
- return controllers.ToArray();
+ return [.. controllers];
}
private static void CollateControllerVersions(
@@ -344,7 +344,7 @@ private static void ApplyCollatedModelsToActions(
HttpConfiguration configuration,
List> visitedActions )
{
- var namingConvention = configuration.DependencyResolver.GetControllerNameConvention();
+ var namingConvention = configuration.GetControllerNameConvention();
for ( var i = 0; i < visitedActions.Count; i++ )
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelectionContext.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelectionContext.cs
index 5c8003c1..729390ce 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelectionContext.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/ControllerSelectionContext.cs
@@ -70,7 +70,7 @@ internal ApiVersion? RequestedVersion
foreach ( var action in actions )
{
- candidates ??= new();
+ candidates ??= [];
candidates.Add( new( action ) );
}
}
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpControllerTypeCache.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpControllerTypeCache.cs
index 473f9811..5c5d3abb 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpControllerTypeCache.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpControllerTypeCache.cs
@@ -33,7 +33,7 @@ private Dictionary> InitializeCache()
var services = configuration.Services;
var assembliesResolver = services.GetAssembliesResolver();
var typeResolver = services.GetHttpControllerTypeResolver();
- var convention = configuration.DependencyResolver.GetControllerNameConvention();
+ var convention = configuration.GetControllerNameConvention();
var comparer = StringComparer.OrdinalIgnoreCase;
return typeResolver.GetControllerTypes( assembliesResolver )
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs
index 4afb0774..22fac02a 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Dispatcher/HttpResponseExceptionFactory.cs
@@ -36,7 +36,7 @@ internal HttpResponseExceptionFactory( HttpRequestMessage request, ApiVersionMod
private ApiVersioningOptions Options => configuration.GetApiVersioningOptions();
- private IProblemDetailsFactory ProblemDetails => configuration.DependencyResolver.GetProblemDetailsFactory();
+ private IProblemDetailsFactory ProblemDetails => configuration.GetProblemDetailsFactory();
private ITraceWriter TraceWriter => configuration.Services.GetTraceWriter() ?? NullTraceWriter.Instance;
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DoNotReportApiVersions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DoNotReportApiVersions.cs
index 722073e5..db2b979d 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DoNotReportApiVersions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/DoNotReportApiVersions.cs
@@ -6,12 +6,6 @@ namespace Asp.Versioning;
internal sealed class DoNotReportApiVersions : IReportApiVersions
{
- private static DoNotReportApiVersions? instance;
-
- private DoNotReportApiVersions() { }
-
- internal static IReportApiVersions Instance => instance ??= new();
-
public ApiVersionMapping Mapping => Explicit | Implicit;
public void Report( HttpResponseMessage response, ApiVersionModel apiVersionModel ) { }
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ErrorObjectFactory.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ErrorObjectFactory.cs
new file mode 100644
index 00000000..79bbe3cc
--- /dev/null
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ErrorObjectFactory.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+// REF: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs
+namespace Asp.Versioning;
+
+using Newtonsoft.Json;
+using static Asp.Versioning.ProblemDetailsDefaults;
+using static Newtonsoft.Json.NullValueHandling;
+
+internal sealed class ErrorObjectFactory : IProblemDetailsFactory
+{
+ public ProblemDetails CreateProblemDetails(
+ HttpRequestMessage request,
+ int? statusCode = null,
+ string? title = null,
+ string? type = null,
+ string? detail = null,
+ string? instance = null )
+ {
+ var status = statusCode ?? 500;
+ ErrorObject? problem;
+
+ if ( type == Ambiguous.Type )
+ {
+ problem = NewError( title, instance );
+ problem.Error.Code = Ambiguous.Code;
+ }
+ else if ( type == Invalid.Type )
+ {
+ problem = NewError( title, instance );
+ problem.Error.Code = Invalid.Code;
+ return ProblemDetailsFactory.AddInvalidExtensions( request, status, problem, ApplyMessage );
+ }
+ else if ( type == Unspecified.Type )
+ {
+ problem = NewError( title, instance );
+ problem.Error.Code = Unspecified.Code;
+ }
+ else if ( type == Unsupported.Type )
+ {
+ problem = NewError( title, instance );
+ problem.Error.Code = Unsupported.Code;
+ return ProblemDetailsFactory.AddUnsupportedExtensions( request, status, problem, ApplyMessage );
+ }
+
+ return ProblemDetailsFactory.NewProblemDetails(
+ request,
+ statusCode,
+ title,
+ type,
+ detail,
+ instance );
+ }
+
+ private static ErrorObject NewError( string? message, string? target ) =>
+ new()
+ {
+ Error =
+ {
+ Message = message,
+ Target = target,
+ },
+ };
+
+ private static void ApplyMessage( ErrorObject obj, string message ) =>
+ obj.Error.InnerError = new() { Message = message };
+
+ private sealed class ErrorObject : ProblemDetails
+ {
+ [JsonProperty( "error" )]
+ public ErrorDetail Error { get; } = new();
+ }
+
+ private sealed class ErrorDetail
+ {
+ [JsonProperty( "code", NullValueHandling = Ignore )]
+ public string? Code { get; set; }
+
+ [JsonProperty( "message", NullValueHandling = Ignore )]
+ public string? Message { get; set; }
+
+ [JsonProperty( "target", NullValueHandling = Ignore )]
+ public string? Target { get; set; }
+
+ [JsonProperty( "innerError", NullValueHandling = Ignore )]
+ public InnerError? InnerError { get; set; }
+ }
+
+ private sealed class InnerError
+ {
+ [JsonProperty( "message", NullValueHandling = Ignore )]
+ public string? Message { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Formatting/ProblemDetailsMediaTypeFormatter.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Formatting/ProblemDetailsMediaTypeFormatter.cs
new file mode 100644
index 00000000..cf44d707
--- /dev/null
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Formatting/ProblemDetailsMediaTypeFormatter.cs
@@ -0,0 +1,69 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+// REF: https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Infrastructure/DefaultProblemDetailsFactory.cs
+namespace Asp.Versioning.Formatting;
+
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Formatting;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using static Asp.Versioning.ProblemDetailsDefaults;
+
+///
+/// Represents a media type formatter for problem details based on https://tools.ietf.org/html/rfc7807.
+///
+public class ProblemDetailsMediaTypeFormatter : MediaTypeFormatter
+{
+ private readonly JsonMediaTypeFormatter json;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProblemDetailsMediaTypeFormatter() : this( new() ) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The existing instance to derive from.
+ public ProblemDetailsMediaTypeFormatter( JsonMediaTypeFormatter formatter )
+ : base( formatter )
+ {
+ json = formatter;
+ SupportedEncodings.Add( new UTF8Encoding( encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true ) );
+ SupportedEncodings.Add( new UnicodeEncoding( bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true ) );
+ SupportedMediaTypes.Add( DefaultMediaType );
+ }
+
+ ///
+ /// Gets the default media type.
+ ///
+ /// Returns the media type for application/problem+json.
+ public static MediaTypeHeaderValue DefaultMediaType { get; } = MediaTypeHeaderValue.Parse( MediaType.Json );
+
+ ///
+ public override bool CanReadType( Type type ) => false;
+
+ ///
+ public override bool CanWriteType( Type type ) => typeof( ProblemDetails ).IsAssignableFrom( type );
+
+ ///
+ public override Task WriteToStreamAsync(
+ Type type,
+ object value,
+ Stream writeStream,
+ HttpContent content,
+ TransportContext transportContext,
+ CancellationToken cancellationToken ) =>
+ json.WriteToStreamAsync( type, value, writeStream, content, transportContext, cancellationToken );
+
+ ///
+ public override void SetDefaultContentHeaders( Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType )
+ {
+ mediaType.MediaType = DefaultMediaType.MediaType;
+ base.SetDefaultContentHeaders( type, headers, mediaType );
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/HeaderApiVersionReader.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/HeaderApiVersionReader.cs
index b18405aa..58b12d19 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/HeaderApiVersionReader.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/HeaderApiVersionReader.cs
@@ -10,10 +10,7 @@ public partial class HeaderApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequestMessage request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
var count = HeaderNames.Count;
@@ -68,7 +65,7 @@ public virtual IReadOnlyList Read( HttpRequestMessage request )
if ( versions == null )
{
- return version == null ? Array.Empty() : new[] { version };
+ return version == null ? [] : [version];
}
return versions.ToArray();
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs
new file mode 100644
index 00000000..37d63336
--- /dev/null
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/IApiVersionSelectorExtensions.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+///
+/// Provides extension methods for .
+///
+public static class IApiVersionSelectorExtensions
+{
+ ///
+ /// Selects an API version given the specified API version information.
+ ///
+ /// The extended .
+ /// The model to select the version from.
+ /// The selected API version .
+ public static ApiVersion SelectVersion( this IApiVersionSelector selector, ApiVersionModel model )
+ {
+ ArgumentNullException.ThrowIfNull( selector );
+ using var request = new HttpRequestMessage();
+ return selector.SelectVersion( request, model );
+ }
+}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReader.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReader.cs
index d748aa0b..d3a96174 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReader.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReader.cs
@@ -10,10 +10,7 @@ public partial class MediaTypeApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequestMessage request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
var contentType = request.Content?.Headers.ContentType;
var version = contentType is null ? default : ReadContentTypeHeader( contentType );
@@ -21,18 +18,18 @@ public virtual IReadOnlyList Read( HttpRequestMessage request )
if ( accept is null || ReadAcceptHeader( accept ) is not string otherVersion )
{
- return version is null ? Array.Empty() : new[] { version };
+ return version is null ? [] : [version];
}
var comparer = StringComparer.OrdinalIgnoreCase;
if ( version is null || comparer.Equals( version, otherVersion ) )
{
- return new[] { otherVersion };
+ return [otherVersion];
}
- return comparer.Compare( version, otherVersion ) <= 0 ?
- new[] { version, otherVersion } :
- new[] { otherVersion, version };
+ return comparer.Compare( version, otherVersion ) <= 0
+ ? [version, otherVersion]
+ : [otherVersion, version];
}
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs
index 78f24891..413f20e0 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/MediaTypeApiVersionReaderBuilder.cs
@@ -22,23 +22,20 @@ public partial class MediaTypeApiVersionReaderBuilder
/// The template syntax is the same used by route templates; however, constraints are not supported.
public virtual MediaTypeApiVersionReaderBuilder Template( string template, string? parameterName = default )
{
- if ( string.IsNullOrEmpty( template ) )
- {
- throw new ArgumentNullException( nameof( template ) );
- }
+ ArgumentException.ThrowIfNullOrEmpty( template );
if ( string.IsNullOrEmpty( parameterName ) )
{
var parser = new RouteParser();
var parsedRoute = parser.Parse( template );
var segments = from content in parsedRoute.PathSegments.OfType()
- from segment in content.Subsegments.OfType()
- select segment;
+ from segment in content.Subsegments.OfType()
+ select segment;
if ( segments.Count() > 1 )
{
var message = string.Format( CultureInfo.CurrentCulture, CommonSR.InvalidMediaTypeTemplate, template );
- throw new ArgumentException( message, nameof( template ) );
+ throw new System.ArgumentException( message, nameof( template ) );
}
}
@@ -111,11 +108,6 @@ private static IReadOnlyList ReadMediaTypePattern(
}
}
- if ( version is null )
- {
- return Array.Empty();
- }
-
- return versions is null ? new[] { version } : versions.ToArray();
+ return ToArray( ref version, versions );
}
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs
index b6a27396..e3640c0e 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetails.cs
@@ -32,7 +32,7 @@ public class ProblemDetails
/// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
/// "about:blank".
///
- [JsonProperty( "type" )]
+ [JsonProperty( "type", NullValueHandling = Ignore )]
public string? Type { get; set; }
///
@@ -72,5 +72,6 @@ public class ProblemDetails
/// The round-tripping behavior for 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.
///
+ [JsonExtensionData]
public IDictionary Extensions => extensions;
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs
index 3bb429dd..baaad563 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ProblemDetailsFactory.cs
@@ -10,11 +10,16 @@ namespace Asp.Versioning;
internal sealed class ProblemDetailsFactory : IProblemDetailsFactory
{
- private static ProblemDetailsFactory? @default;
-
- public static IProblemDetailsFactory Default => @default ??= new();
-
public ProblemDetails CreateProblemDetails(
+ HttpRequestMessage request,
+ int? statusCode = null,
+ string? title = null,
+ string? type = null,
+ string? detail = null,
+ string? instance = null ) =>
+ NewProblemDetails( request, statusCode, title, type, detail, instance );
+
+ internal static ProblemDetails NewProblemDetails(
HttpRequestMessage request,
int? statusCode = null,
string? title = null,
@@ -40,7 +45,7 @@ public ProblemDetails CreateProblemDetails(
else if ( type == Invalid.Type )
{
problemDetails.Code = Invalid.Code;
- return AddInvalidExtensions( request, status, problemDetails );
+ return AddInvalidExtensions( request, status, problemDetails, ApplyMessage );
}
else if ( type == Unspecified.Type )
{
@@ -49,13 +54,17 @@ public ProblemDetails CreateProblemDetails(
else if ( type == Unsupported.Type )
{
problemDetails.Code = Unsupported.Code;
- return AddUnsupportedExtensions( request, status, problemDetails );
+ return AddUnsupportedExtensions( request, status, problemDetails, ApplyMessage );
}
return problemDetails;
}
- private static ProblemDetailsEx AddInvalidExtensions( HttpRequestMessage request, int status, ProblemDetailsEx problemDetails )
+ internal static T AddInvalidExtensions(
+ HttpRequestMessage request,
+ int status,
+ T problemDetails,
+ Action applyMessage ) where T : ProblemDetails
{
if ( status != 400 || !request.ShouldIncludeErrorDetail() )
{
@@ -64,14 +73,18 @@ private static ProblemDetailsEx AddInvalidExtensions( HttpRequestMessage request
var safeUrl = request.RequestUri.SafeFullPath();
var requestedVersion = request.ApiVersionProperties().RawRequestedApiVersion;
- var error = string.Format( CurrentCulture, SR.VersionedControllerNameNotFound, safeUrl, requestedVersion );
+ var message = string.Format( CurrentCulture, SR.VersionedControllerNameNotFound, safeUrl, requestedVersion );
- problemDetails.Error = error;
+ applyMessage( problemDetails, message );
return problemDetails;
}
- private static ProblemDetailsEx AddUnsupportedExtensions( HttpRequestMessage request, int status, ProblemDetailsEx problemDetails )
+ internal static T AddUnsupportedExtensions(
+ HttpRequestMessage request,
+ int status,
+ T problemDetails,
+ Action applyMessage ) where T : ProblemDetails
{
if ( !request.ShouldIncludeErrorDetail() )
{
@@ -95,14 +108,17 @@ private static ProblemDetailsEx AddUnsupportedExtensions( HttpRequestMessage req
var safeUrl = request.RequestUri.SafeFullPath();
var requestedMethod = request.Method;
- var version = request.GetRequestedApiVersion()?.ToString() ?? "(null)";
- var error = string.Format( CurrentCulture, messageFormat, safeUrl, version, requestedMethod );
+ var version = request.ApiVersionProperties().RawRequestedApiVersion ?? "(null)";
+ var message = string.Format( CurrentCulture, messageFormat, safeUrl, version, requestedMethod );
- problemDetails.Error = error;
+ applyMessage( problemDetails, message );
return problemDetails;
}
+ private static void ApplyMessage( ProblemDetailsEx problemDetails, string message ) =>
+ problemDetails.Error = message;
+
private sealed class ProblemDetailsEx : ProblemDetails
{
[JsonProperty( "code", NullValueHandling = Ignore )]
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs
index 3f119177..8b88062b 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/QueryStringApiVersionReader.cs
@@ -10,10 +10,7 @@ public partial class QueryStringApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequestMessage request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull(request);
var count = ParameterNames.Count;
@@ -62,7 +59,7 @@ public virtual IReadOnlyList Read( HttpRequestMessage request )
if ( versions == null )
{
- return version == null ? Array.Empty() : new[] { version };
+ return version == null ? [] : [version];
}
return versions.ToArray();
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs
index 8208fd7e..782118fa 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/ReportApiVersionsAttribute.cs
@@ -18,10 +18,7 @@ public sealed partial class ReportApiVersionsAttribute
/// response provided that there is a response and the executed action was not version-neutral.
public override void OnActionExecuted( HttpActionExecutedContext actionExecutedContext )
{
- if ( actionExecutedContext == null )
- {
- throw new ArgumentNullException( nameof( actionExecutedContext ) );
- }
+ ArgumentNullException.ThrowIfNull( actionExecutedContext );
var response = actionExecutedContext.Response;
@@ -32,14 +29,7 @@ public override void OnActionExecuted( HttpActionExecutedContext actionExecutedC
var context = actionExecutedContext.ActionContext;
var action = context.ActionDescriptor;
- var reporter = reportApiVersions;
-
- if ( reporter is null )
- {
- var dependencyResolver = context.ControllerContext.Configuration.DependencyResolver;
- reporter = dependencyResolver.GetApiVersionReporter();
- }
-
+ var reporter = reportApiVersions ?? context.ControllerContext.Configuration.GetApiVersionReporter();
var model = action.GetApiVersionMetadata().Map( reporter.Mapping );
response.RequestMessage ??= actionExecutedContext.Request;
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionRouteConstraint.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionRouteConstraint.cs
index 3af05991..ad930d65 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionRouteConstraint.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionRouteConstraint.cs
@@ -2,7 +2,6 @@
namespace Asp.Versioning.Routing;
-using System.Web.Http;
using System.Web.Http.Routing;
///
@@ -21,17 +20,14 @@ public sealed class ApiVersionRouteConstraint : IHttpRouteConstraint
/// True if the route constraint is matched; otherwise, false.
public bool Match( HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection )
{
- if ( values == null )
- {
- throw new ArgumentNullException( nameof( values ) );
- }
+ ArgumentNullException.ThrowIfNull( values );
if ( string.IsNullOrEmpty( parameterName ) )
{
return false;
}
- if ( !values.TryGetValue( parameterName, out string value ) )
+ if ( !values.TryGetValue( parameterName, out string? value ) )
{
return false;
}
@@ -41,7 +37,7 @@ public bool Match( HttpRequestMessage request, IHttpRoute route, string paramete
return !string.IsNullOrEmpty( value );
}
- var parser = request.GetConfiguration().DependencyResolver.GetApiVersionParser();
+ var parser = request.GetConfiguration().GetApiVersionParser();
var properties = request.ApiVersionProperties();
properties.RouteParameter = parameterName;
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionUrlHelper.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionUrlHelper.cs
index cb414b6e..8a1beea4 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionUrlHelper.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/ApiVersionUrlHelper.cs
@@ -15,7 +15,7 @@ public class ApiVersionUrlHelper : UrlHelper
/// The inner URL helper .
public ApiVersionUrlHelper( UrlHelper url )
{
- Url = url ?? throw new ArgumentNullException( nameof( url ) );
+ Url = url ?? throw new System.ArgumentNullException( nameof( url ) );
if ( url.Request != null )
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/UrlHelperExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/UrlHelperExtensions.cs
index a53f0f3e..121d6e75 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/UrlHelperExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/Routing/UrlHelperExtensions.cs
@@ -3,6 +3,7 @@
namespace System.Web.Http.Routing;
using Asp.Versioning.Routing;
+using Backport;
///
/// Provides extension methods for .
@@ -22,10 +23,7 @@ public static class UrlHelperExtensions
/// it would be erroneously added as a query string parameter.
public static UrlHelper WithoutApiVersion( this UrlHelper urlHelper )
{
- if ( urlHelper == null )
- {
- throw new ArgumentNullException( nameof( urlHelper ) );
- }
+ ArgumentNullException.ThrowIfNull( urlHelper );
if ( urlHelper is WithoutApiVersionUrlHelper )
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.Designer.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.Designer.cs
index 56042d8b..c272abc8 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.Designer.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.Designer.cs
@@ -114,6 +114,15 @@ internal static string ApiVersionUnspecified {
}
}
+ ///
+ /// Looks up a localized string similar to The value cannot be an empty string..
+ ///
+ internal static string Argument_EmptyString {
+ get {
+ return ResourceManager.GetString("Argument_EmptyString", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to No route providing a controller name was found to match request URI '{0}'..
///
@@ -159,6 +168,15 @@ internal static string DirectRoute_AmbiguousController {
}
}
+ ///
+ /// Looks up a localized string similar to {0}.{1} is an invalid value for {2}.{3}. Did you mean to apply {4} via attribute or convention instead?.
+ ///
+ internal static string InvalidDefaultApiVersion {
+ get {
+ return ResourceManager.GetString("InvalidDefaultApiVersion", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to A controller was not selected for request URI '{0}' and API version '{1}'..
///
@@ -168,6 +186,15 @@ internal static string NoControllerSelected {
}
}
+ ///
+ /// Looks up a localized string similar to No service for type '{0}' has been registered..
+ ///
+ internal static string NoServiceRegistered {
+ get {
+ return ResourceManager.GetString("NoServiceRegistered", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to No HTTP resource was found that matches the request URI '{0}'..
///
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.resx b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.resx
index ed143dc1..dced0e03 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.resx
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SR.resx
@@ -135,6 +135,9 @@
An API version is required, but was not specified.
+
+ The value cannot be an empty string.
+
No route providing a controller name was found to match request URI '{0}'.
@@ -150,9 +153,20 @@
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.{1}{1}The request has found the following matching controller types: {0}
+
+ {0}.{1} is an invalid value for {2}.{3}. Did you mean to apply {4} via attribute or convention instead?
+ 0 = ApiVersion
+1 = Neutral
+2 = ApiVersioningOptions
+3 = DefaultApiVersion
+4 = IApiVersionNeutral
+
A controller was not selected for request URI '{0}' and API version '{1}'.
+
+ No service for type '{0}' has been registered.
+
No HTTP resource was found that matches the request URI '{0}'.
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SunsetPolicyManager.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SunsetPolicyManager.cs
index b15d2160..78af0cdf 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SunsetPolicyManager.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/SunsetPolicyManager.cs
@@ -8,17 +8,10 @@ namespace Asp.Versioning;
public partial class SunsetPolicyManager
{
private readonly ApiVersioningOptions options;
- private static ISunsetPolicyManager? @default;
///
/// Initializes a new instance of the class.
///
/// The associated API versioning options .
public SunsetPolicyManager( ApiVersioningOptions options ) => this.options = options;
-
- internal static ISunsetPolicyManager Default
- {
- get => @default ?? new SunsetPolicyManager( new ApiVersioningOptions() );
- set => @default = value;
- }
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpRequestMessageExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpRequestMessageExtensions.cs
index cd9ac2a7..e6163773 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpRequestMessageExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpRequestMessageExtensions.cs
@@ -3,6 +3,7 @@
namespace System.Net.Http;
using Asp.Versioning;
+using Backport;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Web.Http;
@@ -73,14 +74,11 @@ public static ApiVersioningOptions GetApiVersioningOptions( this HttpRequestMess
/// The current API versioning properties .
public static ApiVersionRequestProperties ApiVersionProperties( this HttpRequestMessage request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
- if ( request.Properties.TryGetValue( ApiVersionPropertiesKey, out ApiVersionRequestProperties properties ) )
+ if ( request.Properties.TryGetValue( ApiVersionPropertiesKey, out ApiVersionRequestProperties? properties ) )
{
- return properties;
+ return properties!;
}
var forceRouteConstraintEvaluation = !request.Properties.ContainsKey( RoutingContextKey );
@@ -122,14 +120,19 @@ internal static Tuple GetProblemDetail
{
var configuration = request.GetConfiguration();
var negotiator = configuration.Services.GetContentNegotiator();
- var result = negotiator.Negotiate( typeof( ProblemDetails ), request, configuration.Formatters ) ??
- new( configuration.Formatters.JsonFormatter ?? new(), MediaTypeHeaderValue.Parse( "application/problem+json" ) );
+ var result = negotiator.Negotiate( typeof( ProblemDetails ), request, configuration.Formatters );
return result.MediaType.MediaType switch
{
- "application/json" => Tuple.Create( MediaTypeHeaderValue.Parse( "application/problem+json" ), result.Formatter ),
- "application/xml" => Tuple.Create( MediaTypeHeaderValue.Parse( "application/problem+xml" ), result.Formatter ),
- _ => Tuple.Create( result.MediaType, result.Formatter ),
+ null => Tuple.Create(
+ MediaTypeHeaderValue.Parse( ProblemDetailsDefaults.MediaType.Json ),
+ (MediaTypeFormatter) ( configuration.Formatters.JsonFormatter ?? new() ) ),
+ "application/xml" => Tuple.Create(
+ MediaTypeHeaderValue.Parse( ProblemDetailsDefaults.MediaType.Xml ),
+ result.Formatter ),
+ _ => Tuple.Create(
+ result.MediaType,
+ result.Formatter ),
};
}
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpResponseMessageExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpResponseMessageExtensions.cs
index 4b9d2354..2f9e1510 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpResponseMessageExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Net.Http/HttpResponseMessageExtensions.cs
@@ -3,7 +3,7 @@
namespace System.Net.Http;
using Asp.Versioning;
-using System;
+using Backport;
using System.Collections.Generic;
using System.Net.Http.Headers;
@@ -22,15 +22,8 @@ public static class HttpResponseMessageExtensions
/// The sunset policy to write.
public static void WriteSunsetPolicy( this HttpResponseMessage response, SunsetPolicy sunsetPolicy )
{
- if ( response == null )
- {
- throw new ArgumentNullException( nameof( response ) );
- }
-
- if ( sunsetPolicy == null )
- {
- throw new ArgumentNullException( nameof( sunsetPolicy ) );
- }
+ ArgumentNullException.ThrowIfNull( response );
+ ArgumentNullException.ThrowIfNull( sunsetPolicy );
var headers = response.Headers;
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpActionDescriptorExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpActionDescriptorExtensions.cs
index 0821d758..77a1f230 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpActionDescriptorExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpActionDescriptorExtensions.cs
@@ -3,6 +3,7 @@
namespace System.Web.Http;
using Asp.Versioning;
+using Backport;
using System.ComponentModel;
using System.Web.Http.Controllers;
@@ -20,14 +21,11 @@ public static class HttpActionDescriptorExtensions
/// The API version information for the action.
public static ApiVersionMetadata GetApiVersionMetadata( this HttpActionDescriptor action )
{
- if ( action == null )
- {
- throw new ArgumentNullException( nameof( action ) );
- }
+ ArgumentNullException.ThrowIfNull( action );
- if ( action.Properties.TryGetValue( typeof( ApiVersionMetadata ), out ApiVersionMetadata value ) )
+ if ( action.Properties.TryGetValue( typeof( ApiVersionMetadata ), out ApiVersionMetadata? value ) )
{
- return value;
+ return value!;
}
return ApiVersionMetadata.Empty;
@@ -42,11 +40,7 @@ public static ApiVersionMetadata GetApiVersionMetadata( this HttpActionDescripto
[EditorBrowsable( EditorBrowsableState.Never )]
public static void SetApiVersionMetadata( this HttpActionDescriptor action, ApiVersionMetadata value )
{
- if ( action == null )
- {
- throw new ArgumentNullException( nameof( action ) );
- }
-
+ ArgumentNullException.ThrowIfNull( action );
action.Properties.AddOrUpdate( typeof( ApiVersionMetadata ), value, ( key, oldValue ) => value );
}
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpConfigurationExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpConfigurationExtensions.cs
index 932639c4..0051de64 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpConfigurationExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpConfigurationExtensions.cs
@@ -4,7 +4,11 @@ namespace System.Web.Http;
using Asp.Versioning;
using Asp.Versioning.Controllers;
+using Asp.Versioning.Dependencies;
using Asp.Versioning.Dispatcher;
+using Asp.Versioning.Formatting;
+using Backport;
+using System.Globalization;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using static Asp.Versioning.ApiVersionParameterLocation;
@@ -14,7 +18,7 @@ namespace System.Web.Http;
///
public static class HttpConfigurationExtensions
{
- private const string ApiVersioningOptionsKey = "MS_ApiVersioningOptions";
+ private const string ApiVersioningServicesKey = "MS_ApiVersioningServices";
///
/// Gets the current API versioning options.
@@ -23,12 +27,22 @@ public static class HttpConfigurationExtensions
/// The current API versioning options .
public static ApiVersioningOptions GetApiVersioningOptions( this HttpConfiguration configuration )
{
- if ( configuration == null )
- {
- throw new ArgumentNullException( nameof( configuration ) );
- }
+ ArgumentNullException.ThrowIfNull( configuration );
+ return configuration.ApiVersioningServices().ApiVersioningOptions;
+ }
- return (ApiVersioningOptions) configuration.Properties.GetOrAdd( ApiVersioningOptionsKey, key => new ApiVersioningOptions() );
+ ///
+ /// Converts problem details into error objects.
+ ///
+ /// The current configuration .
+ /// This enables backward compatibility by converting into Error Objects that
+ /// conform to the Error Responses
+ /// in the Microsoft REST API Guidelines and
+ /// OData Error Responses .
+ public static void ConvertProblemDetailsToErrorObject( this HttpConfiguration configuration )
+ {
+ ArgumentNullException.ThrowIfNull( configuration );
+ configuration.Initializer += EnableErrorObjectResponses;
}
///
@@ -37,11 +51,7 @@ public static ApiVersioningOptions GetApiVersioningOptions( this HttpConfigurati
/// The configuration that will use service versioning.
public static void AddApiVersioning( this HttpConfiguration configuration )
{
- if ( configuration == null )
- {
- throw new ArgumentNullException( nameof( configuration ) );
- }
-
+ ArgumentNullException.ThrowIfNull( configuration );
configuration.AddApiVersioning( new ApiVersioningOptions() );
}
@@ -52,19 +62,13 @@ public static void AddApiVersioning( this HttpConfiguration configuration )
/// An action used to configure the provided options.
public static void AddApiVersioning( this HttpConfiguration configuration, Action setupAction )
{
- if ( configuration == null )
- {
- throw new ArgumentNullException( nameof( configuration ) );
- }
-
- if ( setupAction == null )
- {
- throw new ArgumentNullException( nameof( setupAction ) );
- }
+ ArgumentNullException.ThrowIfNull( configuration );
+ ArgumentNullException.ThrowIfNull( setupAction );
var options = new ApiVersioningOptions();
setupAction( options );
+ ValidateApiVersioningOptions( options );
configuration.AddApiVersioning( options );
}
@@ -92,20 +96,59 @@ private static void AddApiVersioning( this HttpConfiguration configuration, ApiV
}
}
- configuration.Properties.AddOrUpdate( ApiVersioningOptionsKey, options, ( key, oldValue ) => options );
+ configuration.ApiVersioningServices().ApiVersioningOptions = options;
configuration.ParameterBindingRules.Add( typeof( ApiVersion ), ApiVersionParameterBinding.Create );
- SunsetPolicyManager.Default = new SunsetPolicyManager( options );
+ configuration.Formatters.Insert( 0, new ProblemDetailsMediaTypeFormatter( configuration.Formatters.JsonFormatter ?? new() ) );
}
- internal static IReportApiVersions GetApiVersionReporter( this HttpConfiguration configuration )
+ // ApiVersion.Neutral does not have the same meaning as IApiVersionNeutral. setting
+ // ApiVersioningOptions.DefaultApiVersion this value will not make all APIs version-neutral
+ // and will likely lead to many unexpected side effects. this is a best-effort, one-time
+ // validation check to help prevent people from going off the rails. if someone bypasses
+ // this validation by removing the check or updating the value later, then caveat emptor.
+ //
+ // REF: https://github.com/dotnet/aspnet-api-versioning/issues/1011
+ private static void ValidateApiVersioningOptions( ApiVersioningOptions options )
{
- var options = configuration.GetApiVersioningOptions();
-
- if ( options.ReportApiVersions )
+ if ( options.DefaultApiVersion == ApiVersion.Neutral )
{
- return configuration.DependencyResolver.GetApiVersionReporter();
+ var message = string.Format(
+ CultureInfo.CurrentCulture,
+ SR.InvalidDefaultApiVersion,
+ nameof( ApiVersion ),
+ nameof( ApiVersion.Neutral ),
+ nameof( ApiVersioningOptions ),
+ nameof( ApiVersioningOptions.DefaultApiVersion ),
+ nameof( IApiVersionNeutral ) );
+
+ throw new InvalidOperationException( message );
}
+ }
+
+ private static void EnableErrorObjectResponses( HttpConfiguration configuration )
+ {
+ configuration.ApiVersioningServices().Replace(
+ typeof( IProblemDetailsFactory ),
+ static ( sc, t ) => new ErrorObjectFactory() );
+
+ var formatters = configuration.Formatters;
+ var problemDetails = ProblemDetailsMediaTypeFormatter.DefaultMediaType;
- return DoNotReportApiVersions.Instance;
+ for ( var i = 0; i < formatters.Count; i++ )
+ {
+ var mediaTypes = formatters[i].SupportedMediaTypes;
+
+ for ( var j = 0; j < mediaTypes.Count; j++ )
+ {
+ if ( mediaTypes[j].Equals( problemDetails ) )
+ {
+ formatters.RemoveAt( i );
+ return;
+ }
+ }
+ }
}
+
+ internal static DefaultContainer ApiVersioningServices( this HttpConfiguration configuration ) =>
+ (DefaultContainer) configuration.Properties.GetOrAdd( ApiVersioningServicesKey, key => new DefaultContainer() );
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpControllerDescriptorExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpControllerDescriptorExtensions.cs
index 3e66c57d..8efe2d8c 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpControllerDescriptorExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpControllerDescriptorExtensions.cs
@@ -4,6 +4,7 @@ namespace System.Web.Http;
using Asp.Versioning;
using Asp.Versioning.Controllers;
+using Backport;
using System.ComponentModel;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
@@ -34,14 +35,11 @@ public static class HttpControllerDescriptorExtensions
[EditorBrowsable( EditorBrowsableState.Never )]
public static ApiVersionModel GetApiVersionModel( this HttpControllerDescriptor controllerDescriptor )
{
- if ( controllerDescriptor == null )
- {
- throw new ArgumentNullException( nameof( controllerDescriptor ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerDescriptor );
- if ( controllerDescriptor.Properties.TryGetValue( typeof( ApiVersionModel ), out ApiVersionModel value ) )
+ if ( controllerDescriptor.Properties.TryGetValue( typeof( ApiVersionModel ), out ApiVersionModel? value ) )
{
- return value;
+ return value!;
}
return ApiVersionModel.Empty;
@@ -56,10 +54,7 @@ public static ApiVersionModel GetApiVersionModel( this HttpControllerDescriptor
[EditorBrowsable( EditorBrowsableState.Never )]
public static void SetApiVersionModel( this HttpControllerDescriptor controllerDescriptor, ApiVersionModel value )
{
- if ( controllerDescriptor == null )
- {
- throw new ArgumentNullException( nameof( controllerDescriptor ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerDescriptor );
controllerDescriptor.Properties.AddOrUpdate( typeof( ApiVersionModel ), value, ( key, oldValue ) => value );
@@ -84,10 +79,7 @@ public static IEnumerable AsEnumerable( this HttpContr
internal static IEnumerable AsEnumerable( this HttpControllerDescriptor controllerDescriptor, bool includeCandidates )
{
- if ( controllerDescriptor == null )
- {
- throw new ArgumentNullException( nameof( controllerDescriptor ) );
- }
+ ArgumentNullException.ThrowIfNull( controllerDescriptor );
var visited = new HashSet();
@@ -107,12 +99,12 @@ internal static IEnumerable AsEnumerable( this HttpCon
yield return controllerDescriptor;
}
- if ( !includeCandidates || !controllerDescriptor.Properties.TryGetValue( PossibleControllerCandidatesKey, out IEnumerable candidates ) )
+ if ( !includeCandidates || !controllerDescriptor.Properties.TryGetValue( PossibleControllerCandidatesKey, out IEnumerable? candidates ) )
{
yield break;
}
- foreach ( var candidate in candidates )
+ foreach ( var candidate in candidates! )
{
if ( visited.Add( candidate ) )
{
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteCollectionExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteCollectionExtensions.cs
index d2d3a7c1..64310188 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteCollectionExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteCollectionExtensions.cs
@@ -2,6 +2,7 @@
namespace System.Web.Http;
+using Backport;
using System.Reflection;
using System.Web.Http.Routing;
using static System.Reflection.BindingFlags;
@@ -19,10 +20,7 @@ public static class HttpRouteCollectionExtensions
/// routes mapped to their name.
public static IReadOnlyDictionary ToDictionary( this HttpRouteCollection routes )
{
- if ( routes == null )
- {
- throw new ArgumentNullException( nameof( routes ) );
- }
+ ArgumentNullException.ThrowIfNull( routes );
const string HostedHttpRouteCollection = "System.Web.Http.WebHost.Routing.HostedHttpRouteCollection";
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteDataExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteDataExtensions.cs
index 20d6990e..314c9bc9 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteDataExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteDataExtensions.cs
@@ -33,6 +33,6 @@ internal static class HttpRouteDataExtensions
}
}
- return list.ToArray();
+ return [.. list];
}
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteExtensions.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteExtensions.cs
index 5c69c15c..9971270b 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteExtensions.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/System.Web.Http/HttpRouteExtensions.cs
@@ -20,7 +20,7 @@ internal static class HttpRouteExtensions
var directRouteActions = default( HttpActionDescriptor[] );
- if ( dataTokens.TryGetValue( RouteDataTokenKeys.Actions, out HttpActionDescriptor[] possibleDirectRouteActions ) &&
+ if ( dataTokens.TryGetValue( RouteDataTokenKeys.Actions, out HttpActionDescriptor[]? possibleDirectRouteActions ) &&
possibleDirectRouteActions != null &&
possibleDirectRouteActions.Length > 0 )
{
@@ -49,6 +49,6 @@ internal static class HttpRouteExtensions
candidates.Add( new CandidateAction( directRouteActions[i], order, precedence ) );
}
- return candidates.ToArray();
+ return [.. candidates];
}
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/UrlSegmentApiVersionReader.cs b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/UrlSegmentApiVersionReader.cs
index 04e31809..5cfa1e1e 100644
--- a/src/AspNet/WebApi/src/Asp.Versioning.WebApi/UrlSegmentApiVersionReader.cs
+++ b/src/AspNet/WebApi/src/Asp.Versioning.WebApi/UrlSegmentApiVersionReader.cs
@@ -11,10 +11,7 @@ public partial class UrlSegmentApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequestMessage request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull(request);
if ( reentrant )
{
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/VersionedApiExplorerTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/VersionedApiExplorerTest.cs
index e42d2651..42c21028 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/VersionedApiExplorerTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/ApiExplorer/VersionedApiExplorerTest.cs
@@ -1,7 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dcase
+//// Ignore Spelling: Dinsensitive
namespace Asp.Versioning.ApiExplorer;
+
using Asp.Versioning.Models;
using Asp.Versioning.Routing;
using Asp.Versioning.Simulators;
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiDescriptionGroupCollectionTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiDescriptionGroupCollectionTest.cs
index 55dc4e1d..bca97b12 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiDescriptionGroupCollectionTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiDescriptionGroupCollectionTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: denormalized
+
namespace Asp.Versioning.Description;
using System.Collections.ObjectModel;
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiVersionParameterDescriptionContextTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiVersionParameterDescriptionContextTest.cs
index dca4f3b8..d2fb7be2 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiVersionParameterDescriptionContextTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/ApiVersionParameterDescriptionContextTest.cs
@@ -4,13 +4,11 @@ namespace Asp.Versioning.Description;
using Asp.Versioning.ApiExplorer;
using Asp.Versioning.Routing;
-using System.Collections.ObjectModel;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
-using System.Web.Http.Filters;
using System.Web.Http.Routing;
using static Asp.Versioning.ApiVersionParameterLocation;
using static System.Web.Http.Description.ApiParameterSource;
@@ -234,11 +232,19 @@ public void add_parameter_should_add_optional_parameter_when_allowed()
var configuration = new HttpConfiguration();
var action = NewActionDescriptor();
var description = new ApiDescription() { ActionDescriptor = action };
- var version = new ApiVersion( 1, 0 );
- var options = new ApiExplorerOptions( configuration );
+ var version = new ApiVersion( 2.0 );
+ var options = new ApiExplorerOptions( configuration )
+ {
+ ApiVersionSelector = new ConstantApiVersionSelector( version ),
+ };
action.Configuration = configuration;
- configuration.AddApiVersioning( o => o.AssumeDefaultVersionWhenUnspecified = true );
+ configuration.AddApiVersioning(
+ options =>
+ {
+ options.DefaultApiVersion = ApiVersion.Default;
+ options.AssumeDefaultVersionWhenUnspecified = true;
+ } );
var context = new ApiVersionParameterDescriptionContext( description, version, options );
@@ -255,7 +261,7 @@ public void add_parameter_should_add_optional_parameter_when_allowed()
ParameterDescriptor = new
{
ParameterName = "api-version",
- DefaultValue = "1.0",
+ DefaultValue = "2.0",
IsOptional = true,
Configuration = configuration,
ActionDescriptor = action,
@@ -291,9 +297,9 @@ private static HttpActionDescriptor NewActionDescriptor()
var action = new Mock() { CallBase = true }.Object;
var controller = new Mock() { CallBase = true };
- controller.Setup( c => c.GetCustomAttributes( It.IsAny() ) ).Returns( new Collection() );
- controller.Setup( c => c.GetCustomAttributes( It.IsAny() ) ).Returns( new Collection() );
- controller.Setup( c => c.GetFilters() ).Returns( new Collection() );
+ controller.Setup( c => c.GetCustomAttributes( It.IsAny() ) ).Returns( [] );
+ controller.Setup( c => c.GetCustomAttributes( It.IsAny() ) ).Returns( [] );
+ controller.Setup( c => c.GetFilters() ).Returns( [] );
action.ControllerDescriptor = controller.Object;
return action;
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/InternalTypeExtensions.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/InternalTypeExtensions.cs
index b2458e22..4dd18890 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/InternalTypeExtensions.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Description/InternalTypeExtensions.cs
@@ -20,9 +20,9 @@ internal static void EnsureInitialized( this IHttpRoute route, Func actions, bool targetIsAction )
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/GenericMutableObject{T}.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/GenericMutableObject{T}.cs
index db85d146..98fc3dbd 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/GenericMutableObject{T}.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/GenericMutableObject{T}.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Foo
+
namespace Asp.Versioning.Models;
public class GenericMutableObject : List
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/MutableObject.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/MutableObject.cs
index d5b0daaf..cef08ef1 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/MutableObject.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.ApiExplorer.Tests/Models/MutableObject.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Foo
+
namespace Asp.Versioning.Models;
public class MutableObject
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/HttpControllerDescriptorGroupTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/HttpControllerDescriptorGroupTest.cs
index 2d4227c3..0b9ba137 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/HttpControllerDescriptorGroupTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Controllers/HttpControllerDescriptorGroupTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: eq
+
namespace Asp.Versioning.Controllers;
using System.Collections.ObjectModel;
@@ -51,12 +53,12 @@ public void get_custom_attributes_should_aggregate_attributes()
var configuration = new HttpConfiguration();
descriptor1.Setup( d => d.GetCustomAttributes( It.IsAny() ) )
- .Returns( () => new Collection() { new ApiVersionAttribute( "1.0" ) } );
+ .Returns( () => new Collection() { new( "1.0" ) } );
descriptor1.Object.Configuration = configuration;
descriptor1.Object.Properties[typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 1, 0 ) );
descriptor2.Setup( d => d.GetCustomAttributes( It.IsAny() ) )
- .Returns( () => new Collection() { new ApiVersionAttribute( "2.0" ) } );
+ .Returns( () => new Collection() { new( "2.0" ) } );
descriptor2.Object.Configuration = configuration;
descriptor2.Object.Properties[typeof( ApiVersionModel )] = new ApiVersionModel( new ApiVersion( 2, 0 ) );
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs
index d53ccda7..f99b03cf 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTTest.cs
@@ -2,7 +2,6 @@
namespace Asp.Versioning.Conventions;
-using System.Collections.ObjectModel;
using System.Web.Http;
using System.Web.Http.Controllers;
using static Asp.Versioning.ApiVersionMapping;
@@ -17,7 +16,7 @@ public void apply_to_should_assign_empty_model_without_api_versions_from_mapped_
var actionBuilder = new ActionApiVersionConventionBuilder( controllerBuilder );
var actionDescriptor = new Mock() { CallBase = true };
- actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( new Collection() );
+ actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( [] );
actionDescriptor.Object.ControllerDescriptor = new();
// act
@@ -43,7 +42,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_
var actionBuilder = new ActionApiVersionConventionBuilder( controllerBuilder );
var actionDescriptor = new Mock() { CallBase = true };
- actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( new Collection() );
+ actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( [] );
actionDescriptor.Object.ControllerDescriptor = new();
actionBuilder.MapToApiVersion( new ApiVersion( 2, 0 ) );
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs
index 12a5f6ee..e7626764 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ActionApiVersionConventionBuilderTest.cs
@@ -2,7 +2,6 @@
namespace Asp.Versioning.Conventions;
-using System.Collections.ObjectModel;
using System.Web.Http;
using System.Web.Http.Controllers;
using static Asp.Versioning.ApiVersionMapping;
@@ -17,7 +16,7 @@ public void apply_to_should_assign_empty_model_without_api_versions_from_mapped_
var actionBuilder = new ActionApiVersionConventionBuilder( controllerBuilder );
var actionDescriptor = new Mock() { CallBase = true };
- actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( new Collection() );
+ actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( [] );
actionDescriptor.Object.ControllerDescriptor = new();
// act
@@ -43,7 +42,7 @@ public void apply_to_should_assign_model_with_declared_api_versions_from_mapped_
var actionBuilder = new ActionApiVersionConventionBuilder( controllerBuilder );
var actionDescriptor = new Mock() { CallBase = true };
- actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( new Collection() );
+ actionDescriptor.Setup( ad => ad.GetCustomAttributes() ).Returns( [] );
actionDescriptor.Object.ControllerDescriptor = new();
actionBuilder.MapToApiVersion( new ApiVersion( 2, 0 ) );
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs
index e2fc1c6c..82164eb5 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTTest.cs
@@ -19,7 +19,7 @@ public void apply_to_should_assign_conventions_to_controller()
var controllerDescriptor = mock.Object;
var controllerBuilder = default( IControllerConventionBuilder );
- mock.Setup( cd => cd.GetCustomAttributes() ).Returns( new Collection() );
+ mock.Setup( cd => cd.GetCustomAttributes() ).Returns( [] );
controllerDescriptor.Configuration = configuration;
controllerDescriptor.ControllerType = typeof( UndecoratedController );
configuration.AddApiVersioning( o => controllerBuilder = o.Conventions.Controller() );
@@ -54,7 +54,7 @@ public void apply_to_should_assign_empty_conventions_to_api_version_neutral_cont
var controllerDescriptor = mock.Object;
var controllerBuilder = default( IControllerConventionBuilder );
- mock.Setup( cd => cd.GetCustomAttributes() ).Returns( new Collection() );
+ mock.Setup( cd => cd.GetCustomAttributes() ).Returns( [] );
controllerDescriptor.Configuration = configuration;
controllerDescriptor.ControllerType = typeof( UndecoratedController );
configuration.AddApiVersioning( o => controllerBuilder = o.Conventions.Controller() );
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs
index 6990e951..eedc943c 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Conventions/ControllerApiVersionConventionBuilderTest.cs
@@ -20,7 +20,7 @@ public void apply_to_should_assign_conventions_to_controller()
var controllerDescriptor = mock.Object;
var controllerBuilder = default( IControllerConventionBuilder );
- mock.Setup( cd => cd.GetCustomAttributes() ).Returns( new Collection() );
+ mock.Setup( cd => cd.GetCustomAttributes() ).Returns( [] );
controllerDescriptor.Configuration = configuration;
controllerDescriptor.ControllerType = typeof( UndecoratedController );
configuration.AddApiVersioning( o => controllerBuilder = o.Conventions.Controller( typeof( UndecoratedController ) ) );
@@ -55,7 +55,7 @@ public void apply_to_should_assign_empty_conventions_to_api_version_neutral_cont
var controllerDescriptor = mock.Object;
var controllerBuilder = default( IControllerConventionBuilder );
- mock.Setup( cd => cd.GetCustomAttributes() ).Returns( new Collection() );
+ mock.Setup( cd => cd.GetCustomAttributes() ).Returns( [] );
controllerDescriptor.Configuration = configuration;
controllerDescriptor.ControllerType = typeof( UndecoratedController );
configuration.AddApiVersioning( o => controllerBuilder = o.Conventions.Controller( typeof( UndecoratedController ) ) );
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Dispatcher/ApiVersionControllerSelectorTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Dispatcher/ApiVersionControllerSelectorTest.cs
index 25c3be0e..e4c523fb 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Dispatcher/ApiVersionControllerSelectorTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Dispatcher/ApiVersionControllerSelectorTest.cs
@@ -1,5 +1,8 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dbased
+//// Ignore Spelling: Dneutral
+
namespace Asp.Versioning.Dispatcher;
using Asp.Versioning;
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderBuilderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderBuilderTest.cs
index b980fc31..499d07fe 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderBuilderTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderBuilderTest.cs
@@ -125,7 +125,7 @@ public void read_should_retrieve_version_from_content_type_and_accept()
var versions = reader.Read( request );
// assert
- versions.Should().BeEquivalentTo( new[] { "1.5", "2.0" } );
+ versions.Should().BeEquivalentTo( ["1.5", "2.0"] );
}
[Fact]
@@ -246,7 +246,7 @@ public void read_should_only_retrieve_included_media_types()
[InlineData( "application/vnd-v{v}+json", "v", "application/vnd-v2.1+json", "2.1" )]
[InlineData( "application/vnd-v{ver}+json", "ver", "application/vnd-v2022-11-01+json", "2022-11-01" )]
[InlineData( "application/vnd-{version}+xml", "version", "application/vnd-1.1-beta+xml", "1.1-beta" )]
- public void read_should_retreive_version_from_media_type_template(
+ public void read_should_retrieve_version_from_media_type_template(
string template,
string parameterName,
string mediaType,
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderTest.cs
index bc4487b9..ea9ee138 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/MediaTypeApiVersionReaderTest.cs
@@ -111,7 +111,7 @@ public void read_should_retrieve_version_from_content_type_and_accept()
var versions = reader.Read( request );
// assert
- versions.Should().BeEquivalentTo( new[] { "1.5", "2.0" } );
+ versions.Should().BeEquivalentTo( ["1.5", "2.0"] );
}
[Fact]
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/QueryStringApiVersionReaderTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/QueryStringApiVersionReaderTest.cs
index 6355badf..c03525f9 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/QueryStringApiVersionReaderTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/QueryStringApiVersionReaderTest.cs
@@ -86,8 +86,8 @@ public void read_should_not_throw_exception_when_duplicate_api_versions_are_requ
}
[Theory]
- [InlineData( new object[] { new string[0] } )]
- [InlineData( new object[] { new[] { "api-version" } } )]
+ [InlineData( [new string[0]] )]
+ [InlineData( [new[] { "api-version" }] )]
public void add_parameters_should_add_single_parameter_from_query_string( string[] parameterNames )
{
// arrange
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/ReportApiVersionsAttributeTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/ReportApiVersionsAttributeTest.cs
index 57090ed2..ec30300a 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/ReportApiVersionsAttributeTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/ReportApiVersionsAttributeTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dneutral
+
namespace Asp.Versioning;
using Asp.Versioning.Simulators;
@@ -29,11 +31,12 @@ public void on_action_executed_should_add_version_headers()
var method = controller.GetType().GetMethod( nameof( TestController.Get ) );
var controllerDescriptor = new Mock( configuration, "Test", controller.GetType() ) { CallBase = true };
var routeData = new HttpRouteData( new HttpRoute( "api/tests" ) );
- var controllerContext = new HttpControllerContext( new HttpConfiguration(), routeData, new HttpRequestMessage() ) { Controller = controller };
+ var controllerContext = new HttpControllerContext( configuration, routeData, new HttpRequestMessage() ) { Controller = controller };
var actionDescriptor = new ReflectedHttpActionDescriptor( controllerDescriptor.Object, method );
var actionContext = new HttpActionContext( controllerContext, actionDescriptor ) { Response = new HttpResponseMessage() };
var context = new HttpActionExecutedContext( actionContext, null );
+ configuration.AddApiVersioning( options => options.ReportApiVersions = true );
controllerContext.Request.SetConfiguration( new() );
controllerContext.Request.Properties["MS_HttpActionDescriptor"] = actionDescriptor;
controllerDescriptor.Setup( cd => cd.GetCustomAttributes( It.IsAny() ) ).Returns( attributes );
@@ -69,11 +72,12 @@ public void on_action_executing_should_not_add_headers_for_versionX2Dneutral_con
var method = controller.GetType().GetMethod( nameof( TestVersionNeutralController.Get ) );
var controllerDescriptor = new Mock( configuration, "Test", controller.GetType() ) { CallBase = true };
var routeData = new HttpRouteData( new HttpRoute( "api/tests" ) );
- var controllerContext = new HttpControllerContext( new HttpConfiguration(), routeData, new HttpRequestMessage() ) { Controller = new TestVersionNeutralController() };
+ var controllerContext = new HttpControllerContext( configuration, routeData, new HttpRequestMessage() ) { Controller = new TestVersionNeutralController() };
var actionDescriptor = new ReflectedHttpActionDescriptor( controllerDescriptor.Object, method );
var actionContext = new HttpActionContext( controllerContext, actionDescriptor ) { Response = new HttpResponseMessage() };
var context = new HttpActionExecutedContext( actionContext, null );
+ configuration.AddApiVersioning();
controllerDescriptor.Setup( cd => cd.GetCustomAttributes( It.IsAny() ) ).Returns( attributes );
controllerDescriptor.Object.Properties[typeof( ApiVersionModel )] = ApiVersionModel.Neutral;
actionDescriptor.Properties[typeof( ApiVersionMetadata )] = ApiVersionMetadata.Neutral;
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/AdminController.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/AdminController.cs
index ae3bbbd2..6bcd08cc 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/AdminController.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/Simulators/AdminController.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Admin
+
namespace Asp.Versioning.Simulators;
using System.Web.Http;
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
index a77f8010..22cec40e 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpConfigurationExtensionsTest.cs
@@ -37,4 +37,17 @@ public void add_api_versioning_should_report_api_versions_when_option_is_enabled
configuration.Services.GetActionSelector().Should().BeOfType();
configuration.Filters.Single().Instance.Should().BeOfType();
}
+
+ [Fact]
+ public void add_api_versioning_should_not_allow_default_neutral_api_version()
+ {
+ // arrange
+ var configuration = new HttpConfiguration();
+
+ // act
+ Action options = () => configuration.AddApiVersioning( options => options.DefaultApiVersion = ApiVersion.Neutral );
+
+ // assert
+ options.Should().Throw();
+ }
}
\ No newline at end of file
diff --git a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpRouteCollectionExtensionsTest.cs b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpRouteCollectionExtensionsTest.cs
index 8d270992..eb271b38 100644
--- a/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpRouteCollectionExtensionsTest.cs
+++ b/src/AspNet/WebApi/test/Asp.Versioning.WebApi.Tests/System.Web.Http/HttpRouteCollectionExtensionsTest.cs
@@ -56,7 +56,7 @@ namespace System.Web.Http.WebHost.Routing
internal sealed class HostedHttpRouteCollection : HttpRouteCollection
{
#pragma warning disable SA1309 // Field names should not begin with underscore
- private readonly RouteCollection _routeCollection = new();
+ private readonly RouteCollection _routeCollection = [];
#pragma warning restore SA1309 // Field names should not begin with underscore
public override string VirtualPathRoot => throw NotUsedInUnitTest();
@@ -131,7 +131,7 @@ namespace System.Web.Http.WebHost.Routing
internal sealed class HttpWebRoute : Route
{
public HttpWebRoute( IHttpRoute httpRoute )
- : base( httpRoute.RouteTemplate, new(), new(), new(), Mock.Of() ) => HttpRoute = httpRoute;
+ : base( httpRoute.RouteTemplate, [], [], [], Mock.Of() ) => HttpRoute = httpRoute;
public IHttpRoute HttpRoute { get; }
}
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Asp.Versioning.Mvc.Acceptance.Tests.csproj b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Asp.Versioning.Mvc.Acceptance.Tests.csproj
index 4f14694a..d891b0a2 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Asp.Versioning.Mvc.Acceptance.Tests.csproj
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Asp.Versioning.Mvc.Acceptance.Tests.csproj
@@ -1,13 +1,13 @@
- net7.0
+ $(DefaultTargetFramework)
Asp.Versioning
-
+
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/FilteredControllerTypes.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/FilteredControllerTypes.cs
index 6509a017..f84e913c 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/FilteredControllerTypes.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/FilteredControllerTypes.cs
@@ -8,7 +8,7 @@ namespace Asp.Versioning;
internal sealed class FilteredControllerTypes : ControllerFeatureProvider, ICollection
{
- private readonly HashSet controllerTypes = new();
+ private readonly HashSet controllerTypes = [];
protected override bool IsController( TypeInfo typeInfo ) => base.IsController( typeInfo ) && controllerTypes.Contains( typeInfo );
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MediaTypeFixture.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MediaTypeFixture.cs
new file mode 100644
index 00000000..5736c1bf
--- /dev/null
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MediaTypeFixture.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning.Http;
+
+public class MediaTypeFixture : MinimalApiFixture
+{
+ protected override void OnAddApiVersioning( ApiVersioningOptions options ) =>
+ options.ApiVersionReader = new MediaTypeApiVersionReader();
+}
\ No newline at end of file
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MinimalApiFixture.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MinimalApiFixture.cs
index 208c5130..20789427 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MinimalApiFixture.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/MinimalApiFixture.cs
@@ -1,6 +1,9 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
-namespace Asp.Versioning;
+#pragma warning disable IDE0079 // Remove unnecessary suppression
+#pragma warning disable ASP0018 // Unused route parameter
+
+namespace Asp.Versioning.Http;
using Asp.Versioning.Conventions;
using Microsoft.AspNetCore.Builder;
@@ -11,24 +14,16 @@ public class MinimalApiFixture : HttpServerFixture
{
protected override void OnConfigureEndpoints( IEndpointRouteBuilder endpoints )
{
+ endpoints.MapGet( "api/ping", () => Results.NoContent() )
+ .WithApiVersionSet( endpoints.NewApiVersionSet().Build() )
+ .IsApiVersionNeutral();
+
var values = endpoints.NewApiVersionSet( "Values" )
.HasApiVersion( 1.0 )
.HasApiVersion( 2.0 )
.ReportApiVersions()
.Build();
- var helloWorld = endpoints.NewApiVersionSet( "Hello World" )
- .HasApiVersion( 1.0 )
- .HasApiVersion( 2.0 )
- .ReportApiVersions()
- .Build();
-
- var orders = endpoints.NewApiVersionSet( "Orders" ).Build();
-
- endpoints.MapGet( "api/ping", () => Results.NoContent() )
- .WithApiVersionSet( endpoints.NewApiVersionSet().Build() )
- .IsApiVersionNeutral();
-
endpoints.MapGet( "api/values", () => "Value 1" )
.WithApiVersionSet( values )
.MapToApiVersion( 1.0 );
@@ -37,43 +32,34 @@ protected override void OnConfigureEndpoints( IEndpointRouteBuilder endpoints )
.WithApiVersionSet( values )
.MapToApiVersion( 2.0 );
- endpoints.MapGet( "api/v{version:apiVersion}/hello", () => "Hello world!" )
- .WithApiVersionSet( helloWorld )
- .MapToApiVersion( 1.0 );
-
- endpoints.MapGet( "api/v{version:apiVersion}/hello/{text}", ( string text ) => text )
- .WithApiVersionSet( helloWorld )
- .MapToApiVersion( 1.0 );
-
- endpoints.MapGet( "api/v{version:apiVersion}/hello", () => "Hello world! (v2)" )
- .WithApiVersionSet( helloWorld )
- .MapToApiVersion( 2.0 );
-
- endpoints.MapGet( "api/v{version:apiVersion}/hello/{text}", ( string text ) => text + " (v2)" )
- .WithApiVersionSet( helloWorld )
- .MapToApiVersion( 2.0 );
-
- endpoints.MapPost( "api/v{version:apiVersion}/hello", () => { } )
- .WithApiVersionSet( helloWorld );
+ var orders = endpoints.NewVersionedApi( "Orders" )
+ .MapGroup( "api/order" )
+ .HasApiVersion( 1.0 )
+ .HasApiVersion( 2.0 );
- endpoints.MapGet( "api/order", () => { } )
- .WithApiVersionSet( orders )
- .HasApiVersion( 1.0 )
- .HasApiVersion( 2.0 );
+ orders.MapGet( "/", () => Results.Ok() );
+ orders.MapGet( "/{id}", ( int id ) => Results.Ok() ).HasDeprecatedApiVersion( 0.9 );
+ orders.MapPost( "/", () => Results.Created() );
+ orders.MapDelete( "/{id}", ( int id ) => Results.NoContent() ).IsApiVersionNeutral();
- endpoints.MapGet( "api/order/{id}", ( int id ) => { } )
- .WithApiVersionSet( orders )
- .HasDeprecatedApiVersion( 0.9 )
- .HasApiVersion( 1.0 )
- .HasApiVersion( 2.0 );
+ var helloWorld = endpoints.NewVersionedApi( "Orders" )
+ .MapGroup( "api/v{version:apiVersion}/hello" )
+ .HasApiVersion( 1.0 )
+ .HasApiVersion( 2.0 )
+ .ReportApiVersions();
- endpoints.MapPost( "api/order", () => { } )
- .WithApiVersionSet( orders )
- .HasApiVersion( 1.0 )
- .HasApiVersion( 2.0 );
+ helloWorld.MapGet( "/", () => "Hello world!" ).MapToApiVersion( 1.0 );
+ helloWorld.MapGet( "/{text}", ( string text ) => text ).MapToApiVersion( 1.0 );
+ helloWorld.MapGet( "/", () => "Hello world! (v2)" ).MapToApiVersion( 2.0 );
+ helloWorld.MapGet( "/{text}", ( string text ) => text + " (v2)" ).MapToApiVersion( 2.0 );
+ helloWorld.MapPost( "/", () => { } );
+ }
- endpoints.MapDelete( "api/order/{id}", ( int id ) => { } )
- .WithApiVersionSet( orders )
- .IsApiVersionNeutral();
+ protected override void OnAddApiVersioning( ApiVersioningOptions options )
+ {
+ options.ApiVersionReader = ApiVersionReader.Combine(
+ new QueryStringApiVersionReader(),
+ new UrlSegmentApiVersionReader(),
+ new MediaTypeApiVersionReader() );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a media type.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a media type.cs
new file mode 100644
index 00000000..ee292d3d
--- /dev/null
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/Http/given a versioned minimal API/when using a media type.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning.Http;
+
+using Asp.Versioning;
+using System.Net.Http;
+using System.Net.Http.Json;
+using static System.Net.Http.Headers.MediaTypeWithQualityHeaderValue;
+using static System.Net.Http.HttpMethod;
+using static System.Net.HttpStatusCode;
+
+public class when_using_a_media_type : AcceptanceTest, IClassFixture
+{
+ [Fact]
+ public async Task problem_details_should_be_returned_for_accept_header_with_unsupported_api_version()
+ {
+ // arrange
+ using var request = new HttpRequestMessage( Post, "api/values" )
+ {
+ Headers = { Accept = { Parse( "application/json;v=3.0" ) } },
+ Content = JsonContent.Create( new { test = true }, Parse( "application/json;v=3.0" ) ),
+ };
+
+ // act
+ var response = await Client.SendAsync( request );
+ var problem = await response.Content.ReadAsProblemDetailsAsync();
+
+ // assert
+ response.StatusCode.Should().Be( UnsupportedMediaType );
+ problem.Type.Should().Be( ProblemDetailsDefaults.Unsupported.Type );
+ }
+
+ public when_using_a_media_type( MediaTypeFixture fixture, ITestOutputHelper console )
+ : base( fixture ) => console.WriteLine( fixture.DirectedGraphVisualizationUrl );
+}
\ No newline at end of file
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs
index e04031d1..57c872e1 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/HttpServerFixture.cs
@@ -1,5 +1,8 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+// Ignore Spelling: app
+// Ignore Spelling: Mvc
namespace Asp.Versioning;
using Microsoft.AspNetCore.Builder;
@@ -18,7 +21,7 @@ public abstract partial class HttpServerFixture
{
private string visualizationUrl;
- public string DirectedGraphVisualizationUrl =>
+ internal string DirectedGraphVisualizationUrl =>
visualizationUrl ??= GenerateEndpointDirectedGraph( Server.Services );
protected virtual void OnConfigurePartManager( ApplicationPartManager partManager ) =>
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/Controllers/WeatherForecastsController.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/Controllers/WeatherForecastsController.cs
index e138f931..0294fe55 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/Controllers/WeatherForecastsController.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/Controllers/WeatherForecastsController.cs
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
#pragma warning disable IDE0060 // Remove unused parameter
+#pragma warning disable IDE0079
#pragma warning disable CA1822 // Mark members as static
namespace Asp.Versioning.OData.Basic.Controllers;
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a query string.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a query string.cs
index 6b63fd64..e85c1b9b 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a query string.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a query string.cs
@@ -1,5 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable CA2000 // Dispose objects before losing scope
namespace given_versioned_batch_middleware;
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a url segment.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a url segment.cs
index 560cd298..8d873512 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a url segment.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Basic/given versioned batch middleware/when using a url segment.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
+#pragma warning disable CA2000 // Dispose objects before losing scope
namespace given_versioned_batch_middleware;
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/CustomerModelConfiguration.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/CustomerModelConfiguration.cs
index 4f48ea20..b6ba5605 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/CustomerModelConfiguration.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/CustomerModelConfiguration.cs
@@ -25,6 +25,9 @@ private static EntityTypeConfiguration ConfigureCurrent( ODataModelBui
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+ ArgumentNullException.ThrowIfNull( apiVersion );
+
switch ( apiVersion.MajorVersion )
{
case 1:
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/OrderModelConfiguration.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/OrderModelConfiguration.cs
index 5d5655a9..20cbfd11 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/OrderModelConfiguration.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/OrderModelConfiguration.cs
@@ -22,6 +22,8 @@ private static EntityTypeConfiguration ConfigureCurrent( ODataModelBuilde
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
if ( supportedApiVersion == null || supportedApiVersion == apiVersion )
{
ConfigureCurrent( builder );
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/PersonModelConfiguration.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/PersonModelConfiguration.cs
index b3fb9f01..0c49a498 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/PersonModelConfiguration.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/PersonModelConfiguration.cs
@@ -25,6 +25,9 @@ private static EntityTypeConfiguration ConfigureCurrent( ODataModelBuild
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+ ArgumentNullException.ThrowIfNull( apiVersion );
+
switch ( apiVersion.MajorVersion )
{
case 1:
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/WeatherForecastModelConfiguration.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/WeatherForecastModelConfiguration.cs
index c53de03f..4456fa39 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/WeatherForecastModelConfiguration.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/Configuration/WeatherForecastModelConfiguration.cs
@@ -22,6 +22,8 @@ private static EntityTypeConfiguration ConfigureCurrent( ODataM
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
if ( supportedApiVersion == null || supportedApiVersion == apiVersion )
{
ConfigureCurrent( builder );
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/ODataAcceptanceTest.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/ODataAcceptanceTest.cs
index 940703e6..4287b9a1 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/ODataAcceptanceTest.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/OData/ODataAcceptanceTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dspecific
+
namespace Asp.Versioning.OData;
using static System.Net.HttpStatusCode;
diff --git a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/TestApplicationPart.cs b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/TestApplicationPart.cs
index e107ef34..03c8f591 100644
--- a/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/TestApplicationPart.cs
+++ b/src/AspNetCore/Acceptance/Asp.Versioning.Mvc.Acceptance.Tests/TestApplicationPart.cs
@@ -1,27 +1,26 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
-namespace Asp.Versioning
-{
- using Microsoft.AspNetCore.Mvc.ApplicationParts;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Reflection;
+namespace Asp.Versioning;
+
+using Microsoft.AspNetCore.Mvc.ApplicationParts;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
- internal sealed class TestApplicationPart : ApplicationPart, IApplicationPartTypeProvider
- {
- public TestApplicationPart() => Types = Enumerable.Empty();
+internal sealed class TestApplicationPart : ApplicationPart, IApplicationPartTypeProvider
+{
+ public TestApplicationPart() => Types = Enumerable.Empty();
- public TestApplicationPart( params TypeInfo[] types ) => Types = types;
+ public TestApplicationPart( params TypeInfo[] types ) => Types = types;
- public TestApplicationPart( IEnumerable types ) => Types = types;
+ public TestApplicationPart( IEnumerable types ) => Types = types;
- public TestApplicationPart( IEnumerable types ) : this( types.Select( t => t.GetTypeInfo() ) ) { }
+ public TestApplicationPart( IEnumerable types ) : this( types.Select( t => t.GetTypeInfo() ) ) { }
- public TestApplicationPart( params Type[] types ) : this( types.Select( t => t.GetTypeInfo() ) ) { }
+ public TestApplicationPart( params Type[] types ) : this( types.Select( t => t.GetTypeInfo() ) ) { }
- public override string Name => "Test Part";
+ public override string Name => "Test Part";
- public IEnumerable Types { get; }
- }
+ public IEnumerable Types { get; }
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiDescriptionProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiDescriptionProvider.cs
index 06611d5b..fdb4ff8d 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiDescriptionProvider.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiDescriptionProvider.cs
@@ -92,10 +92,7 @@ public ODataApiDescriptionProvider(
/// The default implementation performs no action.
public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
var results = context.Results;
var visited = new HashSet( capacity: results.Count, new ApiDescriptionComparer() );
@@ -145,6 +142,7 @@ public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context )
else
{
UpdateModelTypes( result, matched );
+ UpdateFunctionCollectionParameters( result, matched );
}
}
@@ -201,7 +199,7 @@ private static bool IsNavigationPropertyLink( ODataPathTemplate template ) =>
private static bool TryMatchModelVersion(
ApiDescription description,
- IReadOnlyList items,
+ IODataRoutingMetadata[] items,
[NotNullWhen( true )] out IODataRoutingMetadata? metadata )
{
if ( description.GetApiVersion() is not ApiVersion apiVersion )
@@ -218,7 +216,7 @@ private static bool TryMatchModelVersion(
return false;
}
- for ( var i = 0; i < items.Count; i++ )
+ for ( var i = 0; i < items.Length; i++ )
{
var item = items[i];
var otherApiVersion = item.Model.GetApiVersion();
@@ -459,6 +457,75 @@ private void UpdateModelTypes( ApiDescription description, IODataRoutingMetadata
}
}
+ private static void UpdateFunctionCollectionParameters( ApiDescription description, IODataRoutingMetadata metadata )
+ {
+ var parameters = description.ParameterDescriptions;
+
+ if ( parameters.Count == 0 )
+ {
+ return;
+ }
+
+ var function = default( IEdmFunction );
+ var mapping = default( IDictionary );
+
+ for ( var i = 0; i < metadata.Template.Count; i++ )
+ {
+ var segment = metadata.Template[i];
+
+ if ( segment is FunctionSegmentTemplate func )
+ {
+ function = func.Function;
+ mapping = func.ParameterMappings;
+ break;
+ }
+ else if ( segment is FunctionImportSegmentTemplate import )
+ {
+ function = import.FunctionImport.Function;
+ mapping = import.ParameterMappings;
+ break;
+ }
+ }
+
+ if ( function is null || mapping is null )
+ {
+ return;
+ }
+
+ var name = default( string );
+
+ foreach ( var parameter in function.Parameters )
+ {
+ if ( parameter.Type.IsCollection() &&
+ mapping.TryGetValue( parameter.Name, out name ) &&
+ parameters.SingleOrDefault( p => p.Name == name ) is { } param )
+ {
+ param.Source = BindingSource.Path;
+ break;
+ }
+ }
+
+ var path = description.RelativePath;
+
+ if ( string.IsNullOrEmpty( name ) || string.IsNullOrEmpty( path ) )
+ {
+ return;
+ }
+
+ var span = name.AsSpan();
+ Span oldValue = stackalloc char[name.Length + 2];
+ Span newValue = stackalloc char[name.Length + 4];
+
+ newValue[1] = oldValue[0] = '{';
+ newValue[^2] = oldValue[^1] = '}';
+ newValue[0] = '[';
+ newValue[^1] = ']';
+ span.CopyTo( oldValue.Slice( 1, name.Length ) );
+ span.CopyTo( newValue.Slice( 2, name.Length ) );
+
+ description.RelativePath = path.Replace( oldValue.ToString(), newValue.ToString(), Ordinal );
+ }
+
private sealed class ApiDescriptionComparer : IEqualityComparer
{
private readonly IEqualityComparer comparer = StringComparer.OrdinalIgnoreCase;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiExplorerOptionsFactory.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiExplorerOptionsFactory.cs
index ae057645..5e494688 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiExplorerOptionsFactory.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataApiExplorerOptionsFactory.cs
@@ -69,8 +69,12 @@ public ODataApiExplorerOptionsFactory(
}
///
- protected override ODataApiExplorerOptions CreateInstance( string name ) =>
- new( new( CollateApiVersions( providers, Options ), modelConfigurations ) );
+ protected override ODataApiExplorerOptions CreateInstance( string name )
+ {
+ var options = new ODataApiExplorerOptions( new( CollateApiVersions( providers, Options ), modelConfigurations ) );
+ CopyOptions( Options, options );
+ return options;
+ }
private static ODataApiVersionCollectionProvider CollateApiVersions(
IApiVersionMetadataCollationProvider[] providers,
@@ -107,6 +111,6 @@ private static ODataApiVersionCollectionProvider CollateApiVersions(
private sealed class ODataApiVersionCollectionProvider : IODataApiVersionCollectionProvider
{
- required public IReadOnlyList ApiVersions { get; set; }
+ public required IReadOnlyList ApiVersions { get; set; }
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataQueryOptionModelMetadata.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataQueryOptionModelMetadata.cs
index 062a95aa..bc2a8825 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataQueryOptionModelMetadata.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/ODataQueryOptionModelMetadata.cs
@@ -23,10 +23,7 @@ public sealed class ODataQueryOptionModelMetadata : ModelMetadata
public ODataQueryOptionModelMetadata( IModelMetadataProvider modelMetadataProvider, Type modelType, string description )
: base( ModelMetadataIdentity.ForType( modelType ) )
{
- if ( modelMetadataProvider == null )
- {
- throw new ArgumentNullException( nameof( modelMetadataProvider ) );
- }
+ ArgumentNullException.ThrowIfNull( modelMetadataProvider );
inner = modelMetadataProvider.GetMetadataForType( modelType );
Description = description;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/PartialODataDescriptionProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/PartialODataDescriptionProvider.cs
index 31824b08..47eac901 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/PartialODataDescriptionProvider.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/PartialODataDescriptionProvider.cs
@@ -18,7 +18,7 @@ namespace Asp.Versioning.ApiExplorer;
using Opts = Microsoft.Extensions.Options.Options;
///
-/// Reprensents an API description provider for partial OData support.
+/// Represents an API description provider for partial OData support.
///
[CLSCompliant( false )]
public class PartialODataDescriptionProvider : IApiDescriptionProvider
@@ -87,9 +87,11 @@ protected ODataApiExplorerOptions Options
///
public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context )
{
+ ArgumentNullException.ThrowIfNull( context );
+
var results = FilterResults( context.Results, Conventions );
- if ( results.Count == 0 )
+ if ( results.Length == 0 )
{
return;
}
@@ -104,7 +106,7 @@ public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context
odata.AddRouteComponents( model );
- for ( var j = 0; j < results.Count; j++ )
+ for ( var j = 0; j < results.Length; j++ )
{
var result = results[j];
var metadata = result.ActionDescriptor.GetApiVersionMetadata();
@@ -120,6 +122,8 @@ public virtual void OnProvidersExecuting( ApiDescriptionProviderContext context
///
public virtual void OnProvidersExecuted( ApiDescriptionProviderContext context )
{
+ ArgumentNullException.ThrowIfNull( context );
+
var actions = context.Actions;
for ( var i = 0; i < actions.Count; i++ )
@@ -141,20 +145,16 @@ private static int ODataOrder() =>
new ODataApiDescriptionProvider(
new StubModelMetadataProvider(),
new StubModelTypeBuilder(),
- new OptionsFactory(
- Enumerable.Empty>(),
- Enumerable.Empty>() ),
+ new OptionsFactory( [], [] ),
Opts.Create(
new ODataApiExplorerOptions(
- new(
- new StubODataApiVersionCollectionProvider(),
- Enumerable.Empty() ) ) ) ).Order;
+ new( new StubODataApiVersionCollectionProvider(), [] ) ) ) ).Order;
[MethodImpl( MethodImplOptions.AggressiveInlining )]
private static void MarkAsAdHoc( ODataModelBuilder builder, IEdmModel model ) =>
model.SetAnnotationValue( model, AdHocAnnotation.Instance );
- private static IReadOnlyList FilterResults(
+ private static ApiDescription[] FilterResults(
IList results,
IReadOnlyList conventions )
{
@@ -189,7 +189,7 @@ private static IReadOnlyList FilterResults(
}
}
- return filtered?.ToArray() ?? Array.Empty();
+ return filtered?.ToArray() ?? [];
}
private sealed class StubModelMetadataProvider : IModelMetadataProvider
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/SubstitutedModelMetadata.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/SubstitutedModelMetadata.cs
index ec670cde..5ee9b575 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/SubstitutedModelMetadata.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ApiExplorer/SubstitutedModelMetadata.cs
@@ -1,5 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
#pragma warning disable CA1812
namespace Asp.Versioning.ApiExplorer;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Asp.Versioning.OData.ApiExplorer.csproj b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Asp.Versioning.OData.ApiExplorer.csproj
index 36b8c467..a443fa16 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Asp.Versioning.OData.ApiExplorer.csproj
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Asp.Versioning.OData.ApiExplorer.csproj
@@ -1,9 +1,9 @@
- 7.0.0
- 7.0.0.0
- net7.0
+ 8.2.0
+ 8.2.0.0
+ $(DefaultTargetFramework)
Asp.Versioning
ASP.NET Core API Versioning API Explorer for OData v4.0
The API Explorer extensions for ASP.NET Core API Versioning and OData v4.0.
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ImplicitModelBoundSettingsConvention.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ImplicitModelBoundSettingsConvention.cs
index 3800afd6..6ca75048 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ImplicitModelBoundSettingsConvention.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ImplicitModelBoundSettingsConvention.cs
@@ -2,10 +2,7 @@
namespace Asp.Versioning.Conventions;
-using Asp.Versioning;
-using Asp.Versioning.OData;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
-using Microsoft.OData.ModelBuilder;
///
/// Provides additional implementation specific to ASP.NET Core.
@@ -16,10 +13,7 @@ public partial class ImplicitModelBoundSettingsConvention
///
public void ApplyTo( ApiDescription apiDescription )
{
- if ( apiDescription == null )
- {
- throw new ArgumentNullException( nameof( apiDescription ) );
- }
+ ArgumentNullException.ThrowIfNull( apiDescription );
var responses = apiDescription.SupportedResponseTypes;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs
index 6986e816..3c876e07 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataQueryOptionsConventionBuilder.cs
@@ -4,6 +4,7 @@ namespace Asp.Versioning.Conventions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
+using System.Reflection;
///
/// Provides additional implementation specific to Microsoft ASP.NET Core.
@@ -11,13 +12,13 @@ namespace Asp.Versioning.Conventions;
[CLSCompliant( false )]
public partial class ODataQueryOptionsConventionBuilder
{
- private static Type GetController( ApiDescription apiDescription )
+ private static TypeInfo GetController( ApiDescription apiDescription )
{
if ( apiDescription.ActionDescriptor is ControllerActionDescriptor action )
{
return action.ControllerTypeInfo;
}
- return typeof( object );
+ return typeof( object ).GetTypeInfo();
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs
index cef80319..bfab96ce 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Conventions/ODataValidationSettingsConvention.cs
@@ -16,10 +16,7 @@ public partial class ODataValidationSettingsConvention
///
public virtual void ApplyTo( ApiDescription apiDescription )
{
- if ( apiDescription == null )
- {
- throw new ArgumentNullException( nameof( apiDescription ) );
- }
+ ArgumentNullException.ThrowIfNull( apiDescription );
if ( !IsSupported( apiDescription.HttpMethod ) )
{
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
index d19c8ed5..9304fe5d 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
@@ -24,11 +24,7 @@ public static class IApiVersioningBuilderExtensions
/// The original .
public static IApiVersioningBuilder AddODataApiExplorer( this IApiVersioningBuilder builder )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
AddApiExplorerServices( builder );
return builder;
}
@@ -41,11 +37,7 @@ public static IApiVersioningBuilder AddODataApiExplorer( this IApiVersioningBuil
/// The original .
public static IApiVersioningBuilder AddODataApiExplorer( this IApiVersioningBuilder builder, Action setupAction )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
AddApiExplorerServices( builder );
builder.Services.Configure( setupAction );
return builder;
@@ -65,14 +57,12 @@ private static void AddApiExplorerServices( IApiVersioningBuilder builder )
services.Replace( Singleton, ODataApiExplorerOptionsAdapter>() );
}
+#pragma warning disable IDE0079
#pragma warning disable CA1812
- private sealed class ODataApiExplorerOptionsAdapter : IOptionsFactory
+ private sealed class ODataApiExplorerOptionsAdapter( IOptionsFactory factory )
+ : IOptionsFactory
{
- private readonly IOptionsFactory factory;
-
- public ODataApiExplorerOptionsAdapter( IOptionsFactory factory ) => this.factory = factory;
-
public ApiExplorerOptions Create( string name ) => factory.Create( name );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Format.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Format.cs
new file mode 100644
index 00000000..11126284
--- /dev/null
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/Format.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using System.Text;
+
+internal static class Format
+{
+ internal static readonly CompositeFormat UnsupportedQueryOption = CompositeFormat.Parse( ODataExpSR.UnsupportedQueryOption );
+ internal static readonly CompositeFormat MaxExpressionDesc = CompositeFormat.Parse( ODataExpSR.MaxExpressionDesc );
+ internal static readonly CompositeFormat AllowedPropertiesDesc = CompositeFormat.Parse( ODataExpSR.AllowedPropertiesDesc );
+ internal static readonly CompositeFormat MaxDepthDesc = CompositeFormat.Parse( ODataExpSR.MaxDepthDesc );
+ internal static readonly CompositeFormat MaxValueDesc = CompositeFormat.Parse( ODataExpSR.MaxValueDesc );
+ internal static readonly CompositeFormat AllowedLogicalOperatorsDesc = CompositeFormat.Parse( ODataExpSR.AllowedLogicalOperatorsDesc );
+ internal static readonly CompositeFormat AllowedArithmeticOperatorsDesc = CompositeFormat.Parse( ODataExpSR.AllowedArithmeticOperatorsDesc );
+ internal static readonly CompositeFormat AmbiguousActionMethod = CompositeFormat.Parse( ODataExpSR.AmbiguousActionMethod );
+ internal static readonly CompositeFormat InvalidActionMethodExpression = CompositeFormat.Parse( ODataExpSR.InvalidActionMethodExpression );
+ internal static readonly CompositeFormat ActionMethodNotFound = CompositeFormat.Parse( ODataExpSR.ActionMethodNotFound );
+ internal static readonly CompositeFormat AllowedFunctionsDesc = CompositeFormat.Parse( ODataExpSR.AllowedFunctionsDesc );
+ internal static readonly CompositeFormat RequiredInterfaceNotImplemented = CompositeFormat.Parse( ODataExpSR.RequiredInterfaceNotImplemented );
+ internal static readonly CompositeFormat ConventionStyleMismatch = CompositeFormat.Parse( ODataExpSR.ConventionStyleMismatch );
+}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ReleaseNotes.txt b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ReleaseNotes.txt
index 5f282702..b5318606 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ReleaseNotes.txt
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData.ApiExplorer/ReleaseNotes.txt
@@ -1 +1 @@
-
\ No newline at end of file
+Support OData 9.0 ([#1103](https://github.com/dotnet/aspnet-api-versioning/issues/1103))
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/ApplicationModels/ODataControllerSpecification.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/ApplicationModels/ODataControllerSpecification.cs
index 2a778ee8..eb1cebd0 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/ApplicationModels/ODataControllerSpecification.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/ApplicationModels/ODataControllerSpecification.cs
@@ -14,10 +14,7 @@ public sealed class ODataControllerSpecification : IApiControllerSpecification
///
public bool IsSatisfiedBy( ControllerModel controller )
{
- if ( controller == null )
- {
- throw new ArgumentNullException( nameof( controller ) );
- }
+ ArgumentNullException.ThrowIfNull( controller );
if ( ODataControllerSpecification.IsSatisfiedBy( controller ) )
{
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Asp.Versioning.OData.csproj b/src/AspNetCore/OData/src/Asp.Versioning.OData/Asp.Versioning.OData.csproj
index 55e90b91..e69c80d7 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Asp.Versioning.OData.csproj
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Asp.Versioning.OData.csproj
@@ -1,9 +1,9 @@
- 7.0.0
- 7.0.0.0
- net7.0
+ 8.2.0
+ 8.2.0.0
+ $(DefaultTargetFramework)
Asp.Versioning
ASP.NET Core API Versioning with OData v4.0
A service API versioning library for Microsoft ASP.NET Core with OData v4.0.
@@ -15,7 +15,7 @@
-
+
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Builder/IApplicationBuilderExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/Builder/IApplicationBuilderExtensions.cs
index 0ce54373..c6248429 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Builder/IApplicationBuilderExtensions.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Builder/IApplicationBuilderExtensions.cs
@@ -17,11 +17,7 @@ public static class IApplicationBuilderExtensions
/// The original .
public static IApplicationBuilder UseVersionedODataBatching( this IApplicationBuilder app )
{
- if ( app == null )
- {
- throw new ArgumentNullException( nameof( app ) );
- }
-
+ ArgumentNullException.ThrowIfNull( app );
return app.UseMiddleware();
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Controllers/VersionedMetadataController.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/Controllers/VersionedMetadataController.cs
index e29e2ede..1f4b0266 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Controllers/VersionedMetadataController.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Controllers/VersionedMetadataController.cs
@@ -4,7 +4,6 @@ namespace Asp.Versioning.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Routing.Controllers;
-using Microsoft.Extensions.Primitives;
using static Microsoft.OData.ODataConstants;
using static Microsoft.OData.ODataUtils;
using static Microsoft.OData.ODataVersion;
@@ -17,6 +16,8 @@ namespace Asp.Versioning.Controllers;
[ControllerName( "OData" )]
public class VersionedMetadataController : MetadataController
{
+ private static readonly string[] values = ["GET", "OPTIONS"];
+
///
/// Handles a request for the HTTP OPTIONS method.
///
@@ -55,8 +56,8 @@ public virtual IActionResult GetOptions()
{
var headers = Response.Headers;
- headers.Add( "Allow", new StringValues( new[] { "GET", "OPTIONS" } ) );
- headers.Add( ODataVersionHeader, ODataVersionToString( V4 ) );
+ headers.Allow = new( values );
+ headers[ODataVersionHeader] = ODataVersionToString( V4 );
return Ok();
}
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IApiVersioningBuilderExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IApiVersioningBuilderExtensions.cs
index 79bba8dd..8d90b695 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IApiVersioningBuilderExtensions.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IApiVersioningBuilderExtensions.cs
@@ -31,11 +31,7 @@ public static class IApiVersioningBuilderExtensions
/// The original .
public static IApiVersioningBuilder AddOData( this IApiVersioningBuilder builder )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
AddServices( builder.AddMvc().Services );
return builder;
}
@@ -49,10 +45,7 @@ public static IApiVersioningBuilder AddOData( this IApiVersioningBuilder builder
[CLSCompliant( false )]
public static IApiVersioningBuilder AddOData( this IApiVersioningBuilder builder, Action setupAction )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
+ ArgumentNullException.ThrowIfNull( builder );
var services = builder.AddMvc().Services;
AddServices( services );
@@ -109,7 +102,13 @@ private static void TryRemoveODataService( this IServiceCollection services, Typ
}
}
- var message = string.Format( CultureInfo.CurrentCulture, SR.UnableToFindServices, nameof( IMvcBuilder ), "AddOData", "ConfigureServices(...)" );
+ var message = string.Format(
+ CultureInfo.CurrentCulture,
+ Format.UnableToFindServices,
+ nameof( IMvcBuilder ),
+ "AddOData",
+ "ConfigureServices(...)" );
+
throw new InvalidOperationException( message );
}
@@ -162,12 +161,8 @@ IHttpContextFactory NewFactory( IServiceProvider serviceProvider )
return Describe( typeof( IHttpContextFactory ), NewFactory, lifetime );
}
- private sealed class HttpContextFactoryDecorator : IHttpContextFactory
+ private sealed class HttpContextFactoryDecorator( IHttpContextFactory decorated ) : IHttpContextFactory
{
- private readonly IHttpContextFactory decorated;
-
- public HttpContextFactoryDecorator( IHttpContextFactory decorated ) => this.decorated = decorated;
-
public HttpContext Create( IFeatureCollection featureCollection )
{
// features do not support cloning or DI, which is precisely why ASP.NET Core no longer supports
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IServiceCollectionExtensions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IServiceCollectionExtensions.cs
index cb86af84..16432665 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IServiceCollectionExtensions.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/DependencyInjection/IServiceCollectionExtensions.cs
@@ -61,10 +61,7 @@ internal static bool ConfigureDefaultFeatureProviders( this ApplicationPartManag
/// The extended .
public static void AddModelConfigurationsAsServices( this IServiceCollection services )
{
- if ( services == null )
- {
- throw new ArgumentNullException( nameof( services ) );
- }
+ ArgumentNullException.ThrowIfNull( services );
var partManager = services.GetOrCreateApplicationPartManager();
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Format.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/Format.cs
new file mode 100644
index 00000000..c4202f8c
--- /dev/null
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Format.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using System.Text;
+
+internal static class Format
+{
+ internal static readonly CompositeFormat UnableToFindServices = CompositeFormat.Parse( SR.UnableToFindServices );
+}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/ODataBatchPathMapping.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/ODataBatchPathMapping.cs
index dede3b74..8cf589be 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/ODataBatchPathMapping.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/ODataBatchPathMapping.cs
@@ -26,7 +26,7 @@ public void Add( string prefixName, string routeTemplate, ODataBatchHandler hand
Debug.Assert( count < mappings.Length, "The capacity has been exceeded." );
var template = TemplateParser.Parse( routeTemplate.TrimStart( '/' ) );
- var matcher = new TemplateMatcher( template, new() );
+ var matcher = new TemplateMatcher( template, [] );
handler.PrefixName = prefixName;
mappings[count++] = (matcher, handler, version);
@@ -40,11 +40,56 @@ public bool TryGetHandler( HttpContext context, [NotNullWhen( true )] out ODataB
return false;
}
+ var routeData = new RouteValueDictionary();
+ var candidates = new Dictionary( capacity: mappings.Length );
+
+ batchHandler = SelectExactMatch( context, routeData, candidates ) ??
+ SelectBestCandidate( context, candidates, routeData );
+
+ return batchHandler is not null;
+ }
+
+ public ValueTask TryGetHandlerAsync( HttpContext context, CancellationToken cancellationToken )
+ {
+ if ( count == 0 )
+ {
+ return ValueTask.FromResult( default( ODataBatchHandler ) );
+ }
+
+ var routeData = new RouteValueDictionary();
+ var candidates = new Dictionary( capacity: mappings.Length );
+
+ if ( SelectExactMatch( context, routeData, candidates ) is var handler )
+ {
+ return ValueTask.FromResult( handler );
+ }
+
+ return SelectBestCandidateAsync( context, candidates, routeData, cancellationToken );
+ }
+
+ private static void MergeRouteData( HttpContext context, RouteValueDictionary routeData )
+ {
+ if ( routeData.Count == 0 )
+ {
+ return;
+ }
+
+ var batchRouteData = context.ODataFeature().BatchRouteData;
+
+ foreach ( var (key, value) in routeData )
+ {
+ batchRouteData.Add( key, value );
+ }
+ }
+
+ private ODataBatchHandler? SelectExactMatch(
+ HttpContext context,
+ RouteValueDictionary routeData,
+ Dictionary candidates )
+ {
var path = context.Request.Path;
var feature = context.ApiVersioningFeature();
var unspecified = feature.RawRequestedApiVersions.Count == 0;
- var routeData = new RouteValueDictionary();
- var candidates = new Dictionary( capacity: mappings.Length );
for ( var i = 0; i < count; i++ )
{
@@ -73,33 +118,40 @@ public bool TryGetHandler( HttpContext context, [NotNullWhen( true )] out ODataB
}
MergeRouteData( context, routeData );
- batchHandler = handler;
- return true;
+ return handler;
}
- batchHandler = SelectBestCandidate( context, ref path, candidates, routeData );
- return batchHandler is not null;
+ return default;
}
- private static void MergeRouteData( HttpContext context, RouteValueDictionary routeData )
+ private ODataBatchHandler? SelectBestCandidate(
+ HttpContext context,
+ Dictionary candidates,
+ RouteValueDictionary routeData,
+ ApiVersion version )
{
- if ( routeData.Count == 0 )
+ if ( version is null || !candidates.TryGetValue( version, out var index ) )
{
- return;
+ return default;
}
- var batchRouteData = context.ODataFeature().BatchRouteData;
+ ref readonly var mapping = ref mappings[index];
+ var (matcher, handler, _) = mapping;
- foreach ( var (key, value) in routeData )
- {
- batchRouteData.Add( key, value );
- }
+ routeData.Clear();
+ matcher.TryMatch( context.Request.Path, routeData );
+ MergeRouteData( context, routeData );
+
+ // it's important that the resolved api version be set here to ensure the correct
+ // ODataOptions are resolved by ODataBatchHandler when executed
+ context.ApiVersioningFeature().RequestedApiVersion = version;
+
+ return handler;
}
private ODataBatchHandler? SelectBestCandidate(
HttpContext context,
- ref PathString path,
- IReadOnlyDictionary candidates,
+ Dictionary candidates,
RouteValueDictionary routeData )
{
if ( candidates.Count == 0 )
@@ -114,22 +166,27 @@ private static void MergeRouteData( HttpContext context, RouteValueDictionary ro
var model = new ApiVersionModel( candidates.Keys, Enumerable.Empty() );
var version = selector.SelectVersion( context.Request, model );
- if ( version is null || !candidates.TryGetValue( version, out var index ) )
+ return SelectBestCandidate( context, candidates, routeData, version );
+ }
+
+ private async ValueTask SelectBestCandidateAsync(
+ HttpContext context,
+ Dictionary candidates,
+ RouteValueDictionary routeData,
+ CancellationToken cancellationToken )
+ {
+ if ( candidates.Count == 0 )
{
return default;
}
- ref readonly var mapping = ref mappings[index];
- var (matcher, handler, _) = mapping;
-
- routeData.Clear();
- matcher.TryMatch( path, routeData );
- MergeRouteData( context, routeData );
-
- // it's important that the resolved api version be set here to ensure the correct
- // ODataOptions are resolved by ODataBatchHandler when executed
- context.ApiVersioningFeature().RequestedApiVersion = version;
+ // ~/$batch is always version-neutral so there is no need to check
+ // ApiVersioningOptions.AllowDefaultVersionWhenUnspecified. use the
+ // configured IApiVersionSelector to provide a chance to select the
+ // most appropriate version.
+ var model = new ApiVersionModel( candidates.Keys, Enumerable.Empty() );
+ var version = await selector.SelectVersionAsync( context.Request, model, cancellationToken ).ConfigureAwait( false );
- return handler;
+ return SelectBestCandidate( context, candidates, routeData, version );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/VersionedODataBatchMiddleware.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/VersionedODataBatchMiddleware.cs
index 1eff0220..df89490d 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/VersionedODataBatchMiddleware.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/Batch/VersionedODataBatchMiddleware.cs
@@ -31,10 +31,7 @@ public VersionedODataBatchMiddleware( RequestDelegate next, VersionedODataOption
/// A task representing the asynchronous operation.
public Task Invoke( HttpContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
if ( HttpMethods.IsPost( context.Request.Method ) &&
options.TryGetBatchHandler( context, out var handler ) )
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeature.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeature.cs
index 95a2cdc7..5ab6b579 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeature.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeature.cs
@@ -21,5 +21,5 @@ public class ModelConfigurationFeature
/// Gets the collection of model configurations in an application.
///
/// The collection of model configurations in an application.
- public ICollection ModelConfigurations => modelConfigurations ??= new();
+ public ICollection ModelConfigurations => modelConfigurations ??= [];
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeatureProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeatureProvider.cs
index 7f6254b3..66cf48e9 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeatureProvider.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ModelConfigurationFeatureProvider.cs
@@ -15,15 +15,8 @@ public class ModelConfigurationFeatureProvider : IApplicationFeatureProvider
public void PopulateFeature( IEnumerable parts, ModelConfigurationFeature feature )
{
- if ( parts == null )
- {
- throw new ArgumentNullException( nameof( parts ) );
- }
-
- if ( feature == null )
- {
- throw new ArgumentNullException( nameof( feature ) );
- }
+ ArgumentNullException.ThrowIfNull( parts );
+ ArgumentNullException.ThrowIfNull( feature );
var types = from part in parts.OfType()
from type in part.Types
@@ -43,10 +36,7 @@ where IsModelConfiguration( type )
/// True if the type is a model configuration ; otherwise false .
protected virtual bool IsModelConfiguration( Type type )
{
- if ( type == null )
- {
- throw new ArgumentNullException( nameof( type ) );
- }
+ ArgumentNullException.ThrowIfNull( type );
if ( !type.IsClass || type.IsAbstract || !type.IsPublic || type.ContainsGenericParameters )
{
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApiVersionCollectionProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApiVersionCollectionProvider.cs
index 76cada9f..0aa16562 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApiVersionCollectionProvider.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApiVersionCollectionProvider.cs
@@ -1,5 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
#pragma warning disable CA1812
namespace Asp.Versioning.OData;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApplicationModelProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApplicationModelProvider.cs
index 0d9cdcc0..9ef71b7b 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApplicationModelProvider.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataApplicationModelProvider.cs
@@ -62,10 +62,7 @@ public virtual void OnProvidersExecuted( ApplicationModelProviderContext context
///
public virtual void OnProvidersExecuting( ApplicationModelProviderContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
var (metadataControllers, supported, deprecated) = CollateApiVersions( context.Result );
@@ -92,7 +89,7 @@ private static
if ( controller.ControllerType.IsMetadataController() )
{
- metadataControllers ??= new();
+ metadataControllers ??= [];
metadataControllers.Add( controller );
continue;
}
@@ -119,7 +116,7 @@ private static
if ( supported == null && versions.Count > 0 )
{
- supported = new();
+ supported = [];
}
for ( var k = 0; k < versions.Count; k++ )
@@ -131,7 +128,7 @@ private static
if ( deprecated == null && versions.Count > 0 )
{
- deprecated = new();
+ deprecated = [];
}
for ( var k = 0; k < versions.Count; k++ )
@@ -144,7 +141,7 @@ private static
return (metadataControllers, supported, deprecated);
}
- private static ControllerModel? SelectBestMetadataController( IReadOnlyList controllers )
+ private static ControllerModel? SelectBestMetadataController( List controllers )
{
// note: there should be at least 2 metadata controllers, but there could be 3+
// if a developer defines their own custom controller. ultimately, there can be
@@ -223,7 +220,7 @@ private void ApplyMetadataControllerConventions(
builder.ApplyTo( metadataController );
}
- private IReadOnlyList MergeApiVersions(
+ private ApiVersion[] MergeApiVersions(
SortedSet? supported,
SortedSet? deprecated )
{
@@ -231,14 +228,14 @@ private IReadOnlyList MergeApiVersions(
{
if ( supported == null )
{
- return new[] { Options.DefaultApiVersion };
+ return [Options.DefaultApiVersion];
}
- return supported.ToArray();
+ return [.. supported];
}
else if ( supported == null )
{
- return deprecated.ToArray();
+ return [.. deprecated];
}
return supported.Union( deprecated ).ToArray();
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs
index 7fb591e2..cc798280 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataMultiModelApplicationModelProvider.cs
@@ -1,5 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable IDE0079
#pragma warning disable CA1812
namespace Asp.Versioning.OData;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataOptionsPostSetup.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataOptionsPostSetup.cs
index a8e4f1ff..dfe7314f 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataOptionsPostSetup.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/ODataOptionsPostSetup.cs
@@ -35,10 +35,7 @@ public ODataOptionsPostSetup(
///
public void PostConfigure( string? name, ODataOptions options )
{
- if ( options == null )
- {
- throw new ArgumentNullException( nameof( options ) );
- }
+ ArgumentNullException.ThrowIfNull( options );
var conventions = options.Conventions;
var replacements = 0;
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataModelBuilder.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataModelBuilder.cs
index 8f560845..9b576129 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataModelBuilder.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataModelBuilder.cs
@@ -20,12 +20,10 @@ public VersionedODataModelBuilder(
IODataApiVersionCollectionProvider apiVersionCollectionProvider,
IEnumerable modelConfigurations )
{
- if ( modelConfigurations == null )
- {
- throw new ArgumentNullException( nameof( modelConfigurations ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersionCollectionProvider );
+ ArgumentNullException.ThrowIfNull( modelConfigurations );
- this.apiVersionCollectionProvider = apiVersionCollectionProvider ?? throw new ArgumentNullException( nameof( apiVersionCollectionProvider ) );
+ this.apiVersionCollectionProvider = apiVersionCollectionProvider;
foreach ( var configuration in modelConfigurations )
{
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataOptions.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataOptions.cs
index 7319d4a7..5c08429e 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataOptions.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataOptions.cs
@@ -85,12 +85,11 @@ public IReadOnlyDictionary Mapping
/// The current HTTP context .
/// The retrieved OData batch handler or null .
/// True if the was successfully retrieved; otherwise, false.
+ /// Prefer the asynchronous version of this method
+ /// .
public virtual bool TryGetBatchHandler( HttpContext context, [NotNullWhen( true )] out ODataBatchHandler? handler )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
if ( batchMapping is null )
{
@@ -101,12 +100,33 @@ public virtual bool TryGetBatchHandler( HttpContext context, [NotNullWhen( true
return batchMapping.TryGetHandler( context, out handler );
}
+ ///
+ /// Attempts to retrieve the configured batch handler for the current context.
+ ///
+ /// The current HTTP context .
+ /// The token that can be used to cancel the operation.
+ /// A task containing the matched
+ /// or null if the no match was found.
+ public virtual ValueTask TryGetBatchHandlerAsync( HttpContext context, CancellationToken cancellationToken )
+ {
+ ArgumentNullException.ThrowIfNull( context );
+
+ if ( batchMapping is null )
+ {
+ return ValueTask.FromResult( default( ODataBatchHandler? ) );
+ }
+
+ return batchMapping.TryGetHandlerAsync( context, cancellationToken );
+ }
+
///
/// Attempts to get the current OData options.
///
/// The current HTTP context .
/// The resolved OData options or null .
/// True if the current OData were successfully resolved; otherwise, false.
+ /// Prefer the asynchronous version of this method
+ /// .
public virtual bool TryGetValue( HttpContext? context, [NotNullWhen( true )] out ODataOptions? options )
{
if ( context == null || mapping == null || mapping.Count == 0 )
@@ -132,6 +152,36 @@ public virtual bool TryGetValue( HttpContext? context, [NotNullWhen( true )] out
return mapping.TryGetValue( apiVersion, out options );
}
+ ///
+ /// Attempts to get the current OData options.
+ ///
+ /// The current HTTP context .
+ /// The token that can be used to cancel the operation.
+ /// A task containing the matched
+ /// or null if the no match was found.
+ public virtual async ValueTask TryGetValueAsync( HttpContext? context, CancellationToken cancellationToken )
+ {
+ if ( context == null || mapping == null || mapping.Count == 0 )
+ {
+ return default;
+ }
+
+ var apiVersion = context.GetRequestedApiVersion();
+
+ if ( apiVersion == null )
+ {
+ var model = new ApiVersionModel( mapping.Keys, Array.Empty() );
+ apiVersion = await ApiVersionSelector.SelectVersionAsync( context.Request, model, cancellationToken ).ConfigureAwait( false );
+
+ if ( apiVersion == null )
+ {
+ return default;
+ }
+ }
+
+ return mapping.TryGetValue( apiVersion, out var options ) ? options : default;
+ }
+
///
/// Attempts to resolve the current OData options.
///
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs
index 8f29de7b..93d92b9c 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/OData/VersionedODataTemplateTranslator.cs
@@ -17,15 +17,8 @@ public sealed class VersionedODataTemplateTranslator : IODataTemplateTranslator
///
public ODataPath? Translate( ODataPathTemplate path, ODataTemplateTranslateContext context )
{
- if ( path == null )
- {
- throw new ArgumentNullException( nameof( path ) );
- }
-
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( path );
+ ArgumentNullException.ThrowIfNull( context );
var apiVersion = context.HttpContext.GetRequestedApiVersion();
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt b/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt
index 5f282702..b5318606 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/ReleaseNotes.txt
@@ -1 +1 @@
-
\ No newline at end of file
+Support OData 9.0 ([#1103](https://github.com/dotnet/aspnet-api-versioning/issues/1103))
\ No newline at end of file
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/DefaultMetadataMatcherPolicy.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/DefaultMetadataMatcherPolicy.cs
index b1e7c390..a4b7bfd3 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/DefaultMetadataMatcherPolicy.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/DefaultMetadataMatcherPolicy.cs
@@ -36,11 +36,7 @@ public DefaultMetadataMatcherPolicy(
IApiVersionParameterSource parameterSource,
IOptions options )
{
- if ( parameterSource == null )
- {
- throw new ArgumentNullException( nameof( parameterSource ) );
- }
-
+ ArgumentNullException.ThrowIfNull( parameterSource );
versionsByUrl = parameterSource.VersionsByUrl();
this.options = options;
}
@@ -51,10 +47,7 @@ public DefaultMetadataMatcherPolicy(
///
public virtual bool AppliesToEndpoints( IReadOnlyList endpoints )
{
- if ( endpoints == null )
- {
- throw new ArgumentNullException( nameof( endpoints ) );
- }
+ ArgumentNullException.ThrowIfNull( endpoints );
for ( var i = 0; i < endpoints.Count; i++ )
{
@@ -70,10 +63,7 @@ public virtual bool AppliesToEndpoints( IReadOnlyList endpoints )
///
public IReadOnlyList GetEdges( IReadOnlyList endpoints )
{
- if ( endpoints == null )
- {
- throw new ArgumentNullException( nameof( endpoints ) );
- }
+ ArgumentNullException.ThrowIfNull( endpoints );
var edges = default( List );
var lowestApiVersion = default( ApiVersion );
@@ -89,7 +79,7 @@ public IReadOnlyList GetEdges( IReadOnlyList endpoints
continue;
}
- edges ??= new();
+ edges ??= [];
edges.Add( endpoint );
var model = endpoint.Metadata.GetMetadata()!.Map( Explicit | Implicit );
@@ -131,17 +121,14 @@ public IReadOnlyList GetEdges( IReadOnlyList endpoints
return Array.Empty();
}
- var state = (lowestApiVersion, routePatterns?.ToArray() ?? Array.Empty());
+ var state = (lowestApiVersion, routePatterns?.ToArray() ?? []);
return new PolicyNodeEdge[] { new( state, edges ) };
}
///
public PolicyJumpTable BuildJumpTable( int exitDestination, IReadOnlyList edges )
{
- if ( edges == null )
- {
- throw new ArgumentNullException( nameof( edges ) );
- }
+ ArgumentNullException.ThrowIfNull( edges );
Debug.Assert( edges.Count == 1, $"Only a single edge was expected, but {edges.Count} edges were provided" );
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs
index 8ea4c5a2..230017ed 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedAttributeRoutingConvention.cs
@@ -3,7 +3,6 @@
namespace Asp.Versioning.Routing;
using Asp.Versioning.ApplicationModels;
-using Asp.Versioning.OData;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.OData.Routing.Conventions;
using Microsoft.AspNetCore.OData.Routing.Parser;
@@ -29,10 +28,7 @@ public VersionedAttributeRoutingConvention(
///
public override bool AppliesToAction( ODataControllerActionContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
var metadata = context.Action
.Selectors
diff --git a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedMetadataRoutingConvention.cs b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedMetadataRoutingConvention.cs
index 77ff8f71..c506e50f 100644
--- a/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedMetadataRoutingConvention.cs
+++ b/src/AspNetCore/OData/src/Asp.Versioning.OData/Routing/VersionedMetadataRoutingConvention.cs
@@ -19,11 +19,7 @@ public class VersionedMetadataRoutingConvention : MetadataRoutingConvention
///
public override bool AppliesToController( ODataControllerActionContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
-
+ ArgumentNullException.ThrowIfNull( context );
metadataController ??= typeof( VersionedMetadataController );
return metadataController.IsAssignableFrom( context.Controller.ControllerType );
}
@@ -31,10 +27,7 @@ public override bool AppliesToController( ODataControllerActionContext context )
///
public override bool AppliesToAction( ODataControllerActionContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
var action = context.Action;
var actionName = action.ActionMethod.Name;
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/ApiExplorer/ODataApiDescriptionProviderTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/ApiExplorer/ODataApiDescriptionProviderTest.cs
index 511513b4..532abb00 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/ApiExplorer/ODataApiDescriptionProviderTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/ApiExplorer/ODataApiDescriptionProviderTest.cs
@@ -256,9 +256,9 @@ private static void AssertQueryOptionWithoutOData( ApiDescription description, s
parameter.ModelMetadata.Description.Should().EndWith( suffix + '.' );
}
- private void PrintGroup( IReadOnlyList items )
+ private void PrintGroup( ApiDescription[] items )
{
- for ( var i = 0; i < items.Count; i++ )
+ for ( var i = 0; i < items.Length; i++ )
{
var item = items[i];
console.WriteLine( $"[{item.GroupName}] {item.HttpMethod} {item.RelativePath}" );
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Asp.Versioning.OData.ApiExplorer.Tests.csproj b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Asp.Versioning.OData.ApiExplorer.Tests.csproj
index afcd5741..00feabbc 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Asp.Versioning.OData.ApiExplorer.Tests.csproj
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Asp.Versioning.OData.ApiExplorer.Tests.csproj
@@ -1,7 +1,7 @@
- net7.0
+ $(DefaultTargetFramework)
Asp.Versioning
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs
index 36ed3198..fd3717ba 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Conventions/ODataValidationSettingsConventionTest.cs
@@ -1,5 +1,10 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dlike
+//// Ignore Spelling: Multipart
+//// Ignore Spelling: nonaction
+//// Ignore Spelling: nonquery
+
namespace Asp.Versioning.Conventions;
using Asp.Versioning.OData;
@@ -19,6 +24,7 @@ namespace Asp.Versioning.Conventions;
using Microsoft.OData.ModelBuilder;
using Microsoft.OData.ModelBuilder.Config;
using System.Reflection;
+using Xunit;
using static Microsoft.AspNetCore.Http.StatusCodes;
using static Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource;
using static Microsoft.AspNetCore.OData.Query.AllowedArithmeticOperators;
@@ -469,6 +475,50 @@ public void apply_to_should_use_model_bound_query_attributes()
options => options.ExcludingMissingMembers() );
}
+ [Fact]
+ public void apply_should_override_model_bound_settings_with_enable_query_attribute()
+ {
+ // arrange
+ var builder = new ODataConventionModelBuilder().EnableLowerCamelCase();
+
+ builder.EntitySet( "Customers" );
+
+ var validationSettings = new ODataValidationSettings()
+ {
+ AllowedQueryOptions = AllowedQueryOptions.None,
+ AllowedArithmeticOperators = AllowedArithmeticOperators.None,
+ AllowedLogicalOperators = AllowedLogicalOperators.None,
+ AllowedFunctions = AllowedFunctions.None,
+ };
+ var settings = new TestODataQueryOptionSettings( typeof( Customer ) );
+ var convention = new ODataValidationSettingsConvention( validationSettings, settings );
+ var model = builder.GetEdmModel();
+ var description = NewApiDescription( typeof( CustomersController ), typeof( IEnumerable ), model );
+
+ // act
+ convention.ApplyTo( description );
+
+ // assert
+ var parameter = description.ParameterDescriptions.Single();
+
+ parameter.Should().BeEquivalentTo(
+ new
+ {
+ Name = "$filter",
+ Source = Query,
+ Type = typeof( string ),
+ DefaultValue = default( object ),
+ IsRequired = false,
+ ModelMetadata = new { Description = "Test" },
+ ParameterDescriptor = new
+ {
+ Name = "$filter",
+ ParameterType = typeof( string ),
+ },
+ },
+ options => options.ExcludingMissingMembers() );
+ }
+
[Fact]
public void apply_to_should_process_odataX2Dlike_api_description()
{
@@ -589,7 +639,7 @@ public static IEnumerable EnableQueryAttributeData
MethodInfo = typeof( ControllerBase ).GetRuntimeMethod( nameof( ControllerBase.Ok ), Type.EmptyTypes ),
EndpointMetadata = new object[]
{
- new ODataRoutingMetadata( string.Empty, model, new() ),
+ new ODataRoutingMetadata( string.Empty, model, [] ),
},
},
HttpMethod = method,
@@ -620,7 +670,7 @@ private static ApiDescription NewApiDescription( Type controllerType, Type respo
MethodInfo = controllerType.GetRuntimeMethods().Single( m => m.Name == "Get" ),
EndpointMetadata = new object[]
{
- new ODataRoutingMetadata( string.Empty, model, new() ),
+ new ODataRoutingMetadata( string.Empty, model, [] ),
},
},
HttpMethod = "GET",
@@ -637,6 +687,7 @@ private static ApiDescription NewApiDescription( Type controllerType, Type respo
}
#pragma warning disable IDE0060 // Remove unused parameter
+#pragma warning disable CA1034 // Nested types should not be visible
public class SinglePartController : ODataController
{
@@ -677,6 +728,13 @@ public class OrdersController : ODataController
public IActionResult Get( ODataQueryOptions options ) => Ok();
}
+ public class CustomersController : ODataController
+ {
+ [EnableQuery( AllowedQueryOptions = Filter )]
+ [ProducesResponseType( typeof( IEnumerable ), Status200OK )]
+ public IActionResult Get( ODataQueryOptions options ) => Ok();
+ }
+
[Select]
[Filter]
[Count]
@@ -692,6 +750,12 @@ public class Order
public int Quantity { get; set; }
}
+ [Page( MaxTop = 25, PageSize = 25 )]
+ public class Customer
+ {
+ public int CustomerId { get; set; }
+ }
+
private sealed class TestODataQueryOptionSettings : ODataQueryOptionSettings
{
internal TestODataQueryOptionSettings( Type type, bool dollarPrefix = true ) :
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/AllConfigurations.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/AllConfigurations.cs
index fcbda180..a70422b7 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/AllConfigurations.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/AllConfigurations.cs
@@ -13,6 +13,8 @@ public class AllConfigurations : IModelConfiguration
///
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
builder.Function( "GetSalesTaxRate" ).Returns().Parameter( "PostalCode" );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/OrderModelConfiguration.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/OrderModelConfiguration.cs
index 6542f84f..573b67a0 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/OrderModelConfiguration.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/OrderModelConfiguration.cs
@@ -14,6 +14,8 @@ public class OrderModelConfiguration : IModelConfiguration
///
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
var order = builder.EntitySet( "Orders" ).EntityType.HasKey( o => o.Id );
if ( apiVersion < ApiVersions.V2 )
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/PersonModelConfiguration.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/PersonModelConfiguration.cs
index dd2f04ce..662bf0ba 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/PersonModelConfiguration.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/PersonModelConfiguration.cs
@@ -14,6 +14,8 @@ public class PersonModelConfiguration : IModelConfiguration
///
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
var person = builder.EntitySet( "People" ).EntityType.HasKey( p => p.Id );
if ( apiVersion < ApiVersions.V3 )
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/ProductConfiguration.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/ProductConfiguration.cs
index b1741080..eb71daf6 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/ProductConfiguration.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/ProductConfiguration.cs
@@ -14,6 +14,8 @@ public class ProductConfiguration : IModelConfiguration
///
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
if ( apiVersion < ApiVersions.V3 )
{
return;
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/SupplierConfiguration.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/SupplierConfiguration.cs
index 3b9d0f45..9bcd5bbc 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/SupplierConfiguration.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Configuration/SupplierConfiguration.cs
@@ -14,6 +14,8 @@ public class SupplierConfiguration : IModelConfiguration
///
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
+ ArgumentNullException.ThrowIfNull( builder );
+
if ( apiVersion < ApiVersions.V3 )
{
return;
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Models/Supplier.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Models/Supplier.cs
index 19470142..7b503c6b 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Models/Supplier.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/Models/Supplier.cs
@@ -8,5 +8,7 @@ public class Supplier
public string Name { get; set; }
+#pragma warning disable CA2227 // Collection properties should be read only
public ICollection Products { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs
index 438a0099..d6b7e96a 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.ApiExplorer.Tests/Simulators/V1/BooksController.cs
@@ -16,15 +16,15 @@ namespace Asp.Versioning.Simulators.V1;
[Route( "api/[controller]" )]
public class BooksController : ControllerBase
{
- private static readonly Book[] books = new Book[]
- {
+ private static readonly Book[] books =
+ [
new() { Id = "9781847490599", Title = "Anna Karenina", Author = "Leo Tolstoy", Published = 1878 },
new() { Id = "9780198800545", Title = "War and Peace", Author = "Leo Tolstoy", Published = 1869 },
new() { Id = "9780684801520", Title = "The Great Gatsby", Author = "F. Scott Fitzgerald", Published = 1925 },
new() { Id = "9780486280615", Title = "The Adventures of Huckleberry Finn", Author = "Mark Twain", Published = 1884 },
new() { Id = "9780140430820", Title = "Moby Dick", Author = "Herman Melville", Published = 1851 },
new() { Id = "9780060934347", Title = "Don Quixote", Author = "Miguel de Cervantes", Published = 1605 },
- };
+ ];
///
/// Gets all books.
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/ApplicationModels/ODataControllerSpecificationTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/ApplicationModels/ODataControllerSpecificationTest.cs
index 7855beca..889d3b34 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/ApplicationModels/ODataControllerSpecificationTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/ApplicationModels/ODataControllerSpecificationTest.cs
@@ -29,25 +29,26 @@ public void is_satisfied_by_should_return_expected_value( Type controllerType, b
result.Should().Be( expected );
}
+#pragma warning disable IDE0079
#pragma warning disable CA1812
private sealed class NormalODataController : ODataController
{
[EnableQuery]
- public IActionResult Get() => Ok();
+ public OkResult Get() => Ok();
}
[ODataAttributeRouting]
private sealed class CustomODataController : ControllerBase
{
[EnableQuery]
- public IActionResult Get() => Ok();
+ public OkResult Get() => Ok();
}
[Route( "api/test" )]
private sealed class NonODataController : ControllerBase
{
[HttpGet]
- public IActionResult Get() => Ok();
+ public OkResult Get() => Ok();
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Asp.Versioning.OData.Tests.csproj b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Asp.Versioning.OData.Tests.csproj
index e72dee9a..37771eff 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Asp.Versioning.OData.Tests.csproj
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Asp.Versioning.OData.Tests.csproj
@@ -1,7 +1,7 @@
- net7.0
+ $(DefaultTargetFramework)
Asp.Versioning
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Controllers/VersionedMetadataControllerTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
index 516ef02c..e363fa4c 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Controllers/VersionedMetadataControllerTest.cs
@@ -34,6 +34,7 @@ public async Task options_should_return_expected_headers()
response.Content.Headers.Allow.Should().BeEquivalentTo( "GET", "OPTIONS" );
}
+#pragma warning disable IDE0079
#pragma warning disable CA1812
#pragma warning disable CA1822 // Mark members as static
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ModelConfigurationFeatureProviderTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ModelConfigurationFeatureProviderTest.cs
index e344a34d..0987219f 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ModelConfigurationFeatureProviderTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/OData/ModelConfigurationFeatureProviderTest.cs
@@ -28,10 +28,11 @@ public void populate_feature_should_discover_valid_model_configurations()
provider.PopulateFeature( partManager.ApplicationParts, feature );
// assert
- feature.ModelConfigurations.Should().Equal( new[] { typeof( PublicModelConfiguration ) } );
+ feature.ModelConfigurations.Should().Equal( [typeof( PublicModelConfiguration )] );
}
}
+#pragma warning disable IDE0079
#pragma warning disable CA1812
#pragma warning disable SA1402 // File may only contain a single type
#pragma warning disable SA1403 // File may only contain a single namespace
@@ -40,10 +41,10 @@ namespace ModelConfigurations
{
internal struct ValueTypeModelConfiguration : IModelConfiguration
{
- public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix ) { }
+ public readonly void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix ) { }
}
- internal class PrivateModelConfiguration : IModelConfiguration
+ internal sealed class PrivateModelConfiguration : IModelConfiguration
{
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix ) { }
}
@@ -53,12 +54,12 @@ public abstract class AbstractModelConfiguration : IModelConfiguration
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix ) { }
}
- public class GenericModelConfiguration : IModelConfiguration
+ public sealed class GenericModelConfiguration : IModelConfiguration
{
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix ) { }
}
- public class PublicModelConfiguration : IModelConfiguration
+ public sealed class PublicModelConfiguration : IModelConfiguration
{
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix ) { }
}
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/DefaultMetadataMatcherPolicyTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/DefaultMetadataMatcherPolicyTest.cs
index 2e3273c3..0d2bd095 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/DefaultMetadataMatcherPolicyTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/DefaultMetadataMatcherPolicyTest.cs
@@ -17,7 +17,7 @@ public void applies_to_endpoints_should_return_true_for_service_document()
var paramSource = Mock.Of();
var options = Options.Create( new ApiVersioningOptions() );
var policy = new DefaultMetadataMatcherPolicy( paramSource, options );
- var metadata = new ODataRoutingMetadata( string.Empty, EdmCoreModel.Instance, new ODataPathTemplate() );
+ var metadata = new ODataRoutingMetadata( string.Empty, EdmCoreModel.Instance, [] );
var items = new object[] { metadata };
var endpoints = new Endpoint[] { new( Limbo, new( items ), default ) };
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs
index 8f39cd8d..9181574e 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedAttributeRoutingConventionTest.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+//// Ignore Spelling: Dneutral
+
namespace Asp.Versioning.Routing;
using Asp.Versioning.OData;
diff --git a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs
index 8559374e..0346cac2 100644
--- a/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs
+++ b/src/AspNetCore/OData/test/Asp.Versioning.OData.Tests/Routing/VersionedMetadataRoutingConventionTest.cs
@@ -52,6 +52,7 @@ public void applied_to_action_should_return_true()
action.Selectors.Should().HaveCount( 1 );
}
+#pragma warning disable IDE0079
#pragma warning disable CA1812
private sealed class AnotherVersionedMetadataController : VersionedMetadataController
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationCollection.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationCollection.cs
index 0d728c19..85427ba8 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationCollection.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationCollection.cs
@@ -17,8 +17,8 @@ public class ApiVersionMetadataCollationCollection : IList,
///
public ApiVersionMetadataCollationCollection()
{
- items = new();
- groups = new();
+ items = [];
+ groups = [];
}
///
@@ -47,7 +47,11 @@ ApiVersionMetadata IList.this[int index]
///
public int Count => items.Count;
+#pragma warning disable IDE0079
+#pragma warning disable CA1033 // Interface methods should be callable by child types
bool ICollection.IsReadOnly => ( (ICollection) items ).IsReadOnly;
+#pragma warning restore CA1033 // Interface methods should be callable by child types
+#pragma warning restore IDE0079
///
public void Add( ApiVersionMetadata item ) => Insert( Count, item, default );
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationContext.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationContext.cs
index dc1dfa25..313c2f26 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationContext.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/ApiVersionMetadataCollationContext.cs
@@ -11,5 +11,5 @@ public class ApiVersionMetadataCollationContext
/// Gets the read-only list of collation results.
///
/// The read-only list of collation results.
- public ApiVersionMetadataCollationCollection Results { get; } = new();
+ public ApiVersionMetadataCollationCollection Results { get; } = [];
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs
new file mode 100644
index 00000000..7109d87f
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/DefaultEndpointInspector.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning.ApiExplorer;
+
+using Microsoft.AspNetCore.Http;
+
+///
+/// Represents the default endpoint inspector .
+///
+[CLSCompliant(false)]
+public sealed class DefaultEndpointInspector : IEndpointInspector
+{
+ ///
+ public bool IsControllerAction( Endpoint endpoint ) => false;
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs
index 3ad5433a..e5ce20fb 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/EndpointApiVersionMetadataCollationProvider.cs
@@ -12,15 +12,29 @@ namespace Asp.Versioning.ApiExplorer;
public sealed class EndpointApiVersionMetadataCollationProvider : IApiVersionMetadataCollationProvider
{
private readonly EndpointDataSource endpointDataSource;
+ private readonly IEndpointInspector endpointInspector;
private int version;
///
/// Initializes a new instance of the class.
///
/// The underlying endpoint data source .
+ [Obsolete( "Use the constructor that accepts IEndpointInspector. This constructor will be removed in a future version." )]
public EndpointApiVersionMetadataCollationProvider( EndpointDataSource endpointDataSource )
+ : this( endpointDataSource, new DefaultEndpointInspector() ) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying endpoint data source .
+ /// The endpoint inspector used to inspect endpoints.
+ public EndpointApiVersionMetadataCollationProvider( EndpointDataSource endpointDataSource, IEndpointInspector endpointInspector )
{
- this.endpointDataSource = endpointDataSource ?? throw new ArgumentNullException( nameof( endpointDataSource ) );
+ ArgumentNullException.ThrowIfNull( endpointDataSource );
+ ArgumentNullException.ThrowIfNull( endpointInspector );
+
+ this.endpointDataSource = endpointDataSource;
+ this.endpointInspector = endpointInspector;
ChangeToken.OnChange( endpointDataSource.GetChangeToken, () => ++version );
}
@@ -30,10 +44,7 @@ public EndpointApiVersionMetadataCollationProvider( EndpointDataSource endpointD
///
public void Execute( ApiVersionMetadataCollationContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
var endpoints = endpointDataSource.Endpoints;
@@ -41,7 +52,8 @@ public void Execute( ApiVersionMetadataCollationContext context )
{
var endpoint = endpoints[i];
- if ( endpoint.Metadata.GetMetadata() is not ApiVersionMetadata item )
+ if ( endpoint.Metadata.GetMetadata() is not ApiVersionMetadata item ||
+ endpointInspector.IsControllerAction( endpoint ) )
{
continue;
}
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs
new file mode 100644
index 00000000..4a20e9c1
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IApiVersionDescriptionProviderFactoryExtensions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning.ApiExplorer;
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.Primitives;
+
+///
+/// Provides extension methods for .
+///
+[CLSCompliant( false )]
+public static class IApiVersionDescriptionProviderFactoryExtensions
+{
+ ///
+ /// Creates and returns an API version description provider.
+ ///
+ /// The extended .
+ /// A new API version description provider .
+ public static IApiVersionDescriptionProvider Create( this IApiVersionDescriptionProviderFactory factory )
+ {
+ ArgumentNullException.ThrowIfNull( factory );
+ return factory.Create( new EmptyEndpointDataSource() );
+ }
+
+ private sealed class EmptyEndpointDataSource : EndpointDataSource
+ {
+ public override IReadOnlyList Endpoints { get; } = [];
+
+ public override IChangeToken GetChangeToken() => new CancellationChangeToken( CancellationToken.None );
+
+ public override IReadOnlyList GetGroupedEndpoints( RouteGroupContext context ) => Endpoints;
+ }
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IEndpointInspector.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IEndpointInspector.cs
new file mode 100644
index 00000000..900edf94
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiExplorer/IEndpointInspector.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning.ApiExplorer;
+
+using Microsoft.AspNetCore.Http;
+
+///
+/// Defines the behavior of an endpoint inspector.
+///
+[CLSCompliant( false )]
+public interface IEndpointInspector
+{
+ ///
+ /// Determines whether the specified endpoint is a controller action.
+ ///
+ /// The endpoint to inspect.
+ /// True if the is for a controller action; otherwise, false.
+ bool IsControllerAction( Endpoint endpoint );
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiVersioningFeature.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiVersioningFeature.cs
index 8dd49c7d..8437429a 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiVersioningFeature.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ApiVersioningFeature.cs
@@ -34,11 +34,7 @@ public IReadOnlyList RawRequestedApiVersions
{
if ( rawApiVersions is null )
{
- var reader = context.RequestServices.GetService()
- ?? ApiVersionReader.Combine(
- new QueryStringApiVersionReader(),
- new UrlSegmentApiVersionReader() );
-
+ var reader = context.RequestServices.GetService() ?? ApiVersionReader.Default;
rawApiVersions = reader.Read( context.Request );
}
@@ -58,12 +54,16 @@ public string? RawRequestedApiVersion
{
0 => default,
1 => values[0],
+#pragma warning disable IDE0079
+#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations
_ => throw NewAmbiguousApiVersionException( values ),
+#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
+#pragma warning restore IDE0079
};
}
set
{
- rawApiVersions = string.IsNullOrEmpty( value ) ? default : new[] { value };
+ rawApiVersions = string.IsNullOrEmpty( value ) ? default : [value];
}
}
@@ -105,7 +105,7 @@ public ApiVersion? RequestedApiVersion
if ( apiVersion is not null &&
( rawApiVersions is null || rawApiVersions.Count == 0 ) )
{
- rawApiVersions = new[] { apiVersion.ToString() };
+ rawApiVersions = [apiVersion.ToString()];
}
}
}
@@ -115,7 +115,7 @@ private static AmbiguousApiVersionException NewAmbiguousApiVersionException( IRe
new(
string.Format(
CultureInfo.CurrentCulture,
- CommonSR.MultipleDifferentApiVersionsRequested,
- string.Join( ", ", values.ToArray(), 0, values.Count ) ),
+ Format.MultipleDifferentApiVersionsRequested,
+ string.Join( ", ", [.. values], 0, values.Count ) ),
values );
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Asp.Versioning.Http.csproj b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Asp.Versioning.Http.csproj
index 4dc9d40d..3abd2a2c 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Asp.Versioning.Http.csproj
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Asp.Versioning.Http.csproj
@@ -1,13 +1,14 @@
- 7.0.0
- 7.0.0.0
- net7.0
+ 8.1.0
+ 8.1.0.0
+ $(DefaultTargetFramework)
Asp.Versioning
ASP.NET Core API Versioning
A service API versioning library for Microsoft ASP.NET Core.
Asp;AspNet;AspNetCore;Versioning
+ true
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/ApiVersionSetBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/ApiVersionSetBuilder.cs
index 3a0303f6..e24853fc 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/ApiVersionSetBuilder.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/ApiVersionSetBuilder.cs
@@ -110,10 +110,7 @@ public virtual ApiVersionSetBuilder AdvertisesDeprecatedApiVersion( ApiVersion a
/// A new API version model .
protected internal virtual ApiVersionModel BuildApiVersionModel( ApiVersioningOptions options )
{
- if ( options == null )
- {
- throw new ArgumentNullException( nameof( options ) );
- }
+ ArgumentNullException.ThrowIfNull( options );
if ( VersionNeutral )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/EndpointBuilderFinalizer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/EndpointBuilderFinalizer.cs
index baf0fd12..250083cd 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/EndpointBuilderFinalizer.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/EndpointBuilderFinalizer.cs
@@ -163,11 +163,11 @@ private static bool TryGetApiVersions( IList metadata, out ApiVersionBuc
var versions = provider.Versions;
var target = provider.Options switch
{
- None => supported ??= new(),
- Mapped => mapped ??= new(),
- Deprecated => deprecated ??= new(),
- Advertised => advertised ??= new(),
- Advertised | Deprecated => deprecatedAdvertised ??= new(),
+ None => supported ??= [],
+ Mapped => mapped ??= [],
+ Deprecated => deprecated ??= [],
+ Advertised => advertised ??= [],
+ Advertised | Deprecated => deprecatedAdvertised ??= [],
_ => default,
};
@@ -183,11 +183,11 @@ private static bool TryGetApiVersions( IList metadata, out ApiVersionBuc
}
buckets = new(
- mapped?.ToArray() ?? Array.Empty(),
- supported?.ToArray() ?? Array.Empty(),
- deprecated?.ToArray() ?? Array.Empty(),
- advertised?.ToArray() ?? Array.Empty(),
- deprecatedAdvertised?.ToArray() ?? Array.Empty() );
+ mapped?.ToArray() ?? [],
+ supported?.ToArray() ?? [],
+ deprecated?.ToArray() ?? [],
+ advertised?.ToArray() ?? [],
+ deprecatedAdvertised?.ToArray() ?? [] );
return true;
}
@@ -212,14 +212,8 @@ private static ApiVersionMetadata Build( IList metadata, ApiVersionSet v
ApiVersion[] emptyVersions;
var inheritedSupported = apiModel.SupportedApiVersions;
var inheritedDeprecated = apiModel.DeprecatedApiVersions;
- var (mapped, supported, deprecated, advertised, advertisedDeprecated) = buckets;
- var isEmpty = mapped.Count == 0 &&
- supported.Count == 0 &&
- deprecated.Count == 0 &&
- advertised.Count == 0 &&
- advertisedDeprecated.Count == 0;
-
- if ( isEmpty )
+
+ if ( buckets.AreEmpty )
{
var noInheritedApiVersions = inheritedSupported.Count == 0 &&
inheritedDeprecated.Count == 0;
@@ -230,7 +224,7 @@ private static ApiVersionMetadata Build( IList metadata, ApiVersionSet v
}
else
{
- emptyVersions = Array.Empty();
+ emptyVersions = [];
endpointModel = new(
declaredVersions: emptyVersions,
inheritedSupported,
@@ -239,24 +233,29 @@ private static ApiVersionMetadata Build( IList metadata, ApiVersionSet v
emptyVersions );
}
}
- else if ( mapped.Count == 0 )
- {
- endpointModel = new(
- declaredVersions: supported.Union( deprecated ),
- supported.Union( inheritedSupported ),
- deprecated.Union( inheritedDeprecated ),
- advertised,
- advertisedDeprecated );
- }
else
{
- emptyVersions = Array.Empty();
- endpointModel = new(
- declaredVersions: mapped,
- supportedVersions: inheritedSupported,
- deprecatedVersions: inheritedDeprecated,
- advertisedVersions: emptyVersions,
- deprecatedAdvertisedVersions: emptyVersions );
+ var (mapped, supported, deprecated, advertised, advertisedDeprecated) = buckets;
+
+ if ( mapped.Count == 0 )
+ {
+ endpointModel = new(
+ declaredVersions: supported.Union( deprecated ),
+ supported.Union( inheritedSupported ),
+ deprecated.Union( inheritedDeprecated ),
+ advertised,
+ advertisedDeprecated );
+ }
+ else
+ {
+ emptyVersions = [];
+ endpointModel = new(
+ declaredVersions: mapped,
+ supportedVersions: inheritedSupported,
+ deprecatedVersions: inheritedDeprecated,
+ advertisedVersions: emptyVersions,
+ deprecatedAdvertisedVersions: emptyVersions );
+ }
}
return new( apiModel, endpointModel, name );
@@ -268,7 +267,7 @@ private static RequestDelegate EnsureRequestDelegate( RequestDelegate? current,
throw new InvalidOperationException(
string.Format(
CultureInfo.CurrentCulture,
- SR.UnsetRequestDelegate,
+ Format.UnsetRequestDelegate,
nameof( RequestDelegate ),
nameof( RouteEndpoint ) ) );
@@ -277,5 +276,12 @@ private record struct ApiVersionBuckets(
IReadOnlyList Supported,
IReadOnlyList Deprecated,
IReadOnlyList Advertised,
- IReadOnlyList AdvertisedDeprecated );
+ IReadOnlyList AdvertisedDeprecated )
+ {
+ internal readonly bool AreEmpty = Mapped.Count == 0
+ && Supported.Count == 0
+ && Deprecated.Count == 0
+ && Advertised.Count == 0
+ && AdvertisedDeprecated.Count == 0;
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointConventionBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointConventionBuilderExtensions.cs
index 97e53f42..15525d66 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointConventionBuilderExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointConventionBuilderExtensions.cs
@@ -7,6 +7,7 @@ namespace Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Collections;
using System.Globalization;
+using System.Runtime.Serialization;
using static Asp.Versioning.ApiVersionProviderOptions;
///
@@ -27,10 +28,7 @@ public static TBuilder WithApiVersionSet(
ApiVersionSet apiVersionSet )
where TBuilder : notnull, IEndpointConventionBuilder
{
- if ( apiVersionSet == null )
- {
- throw new ArgumentNullException( nameof( apiVersionSet ) );
- }
+ ArgumentNullException.ThrowIfNull( apiVersionSet );
builder.Add( endpoint => AddMetadata( endpoint, apiVersionSet ) );
builder.Finally( EndpointBuilderFinalizer.FinalizeEndpoints );
@@ -442,7 +440,7 @@ private static void AddMetadata( EndpointBuilder builder, object item )
throw new InvalidOperationException(
string.Format(
CultureInfo.CurrentCulture,
- SR.NoVersionSet,
+ Format.NoVersionSet,
builder.DisplayName,
nameof( IEndpointRouteBuilderExtensions.NewVersionedApi ),
nameof( IEndpointRouteBuilderExtensions.WithApiVersionSet ) ) );
@@ -478,7 +476,11 @@ private sealed class SingleItemReadOnlyList : IReadOnlyList
internal SingleItemReadOnlyList( ApiVersion item ) => this.item = item;
+#pragma warning disable IDE0079
+#pragma warning disable CA2201 // Do not raise reserved exception types
public ApiVersion this[int index] => index == 0 ? item : throw new IndexOutOfRangeException();
+#pragma warning restore CA2201 // Do not raise reserved exception types
+#pragma warning restore IDE0079
public int Count => 1;
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointRouteBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointRouteBuilderExtensions.cs
index 2aad4196..e6054e34 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointRouteBuilderExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/IEndpointRouteBuilderExtensions.cs
@@ -23,14 +23,9 @@ public static class IEndpointRouteBuilderExtensions
/// A new API version set builder .
public static ApiVersionSetBuilder NewApiVersionSet( this IEndpointRouteBuilder endpoints, string? name = default )
{
- if ( endpoints == null )
- {
- throw new ArgumentNullException( nameof( endpoints ) );
- }
-
+ ArgumentNullException.ThrowIfNull( endpoints );
var create = endpoints.ServiceProvider.GetService();
-
- return create is null ? new ApiVersionSetBuilder( name ) : create( name );
+ return create is null ? new( name ) : create( name );
}
///
@@ -43,10 +38,7 @@ public static class IEndpointRouteBuilderExtensions
public static IVersionedEndpointRouteBuilder WithApiVersionSet( this TBuilder builder, string? name = default )
where TBuilder : notnull, IEndpointRouteBuilder, IEndpointConventionBuilder
{
- if ( builder is null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
+ ArgumentNullException.ThrowIfNull( builder );
if ( builder.HasMetadata() )
{
@@ -71,10 +63,7 @@ public static class IEndpointRouteBuilderExtensions
/// A new instance.
public static IVersionedEndpointRouteBuilder NewVersionedApi( this IEndpointRouteBuilder builder, string? name = default )
{
- if ( builder is null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
+ ArgumentNullException.ThrowIfNull( builder );
if ( builder.IsNestedGroup() )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/VersionedEndpointRouteBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/VersionedEndpointRouteBuilder.cs
index c1773a6c..36af4a92 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/VersionedEndpointRouteBuilder.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Builder/VersionedEndpointRouteBuilder.cs
@@ -59,20 +59,12 @@ public virtual IApplicationBuilder CreateApplicationBuilder() =>
public virtual void Add( Action convention ) =>
conventionBuilder.Add( convention );
- private sealed class ServiceProviderDecorator : IServiceProvider
+ private sealed class ServiceProviderDecorator(
+ IServiceProvider decorated,
+ ApiVersionSetBuilder versionSetBuilder ) : IServiceProvider
{
- private readonly IServiceProvider decorated;
- private readonly ApiVersionSetBuilder versionSetBuilder;
private ApiVersionSet? versionSet;
- internal ServiceProviderDecorator(
- IServiceProvider decorated,
- ApiVersionSetBuilder versionSetBuilder )
- {
- this.decorated = decorated;
- this.versionSetBuilder = versionSetBuilder;
- }
-
public object? GetService( Type serviceType )
{
if ( typeof( ApiVersionSetBuilder ).Equals( serviceType ) )
@@ -89,19 +81,10 @@ internal ServiceProviderDecorator(
}
}
- private sealed class EndpointDataSourceDecorator : EndpointDataSource
- {
- private readonly EndpointDataSource decorated;
- private readonly ApiVersionSetBuilder versionSetBuilder;
-
- internal EndpointDataSourceDecorator(
+ private sealed class EndpointDataSourceDecorator(
EndpointDataSource decorated,
- ApiVersionSetBuilder versionSetBuilder )
- {
- this.decorated = decorated;
- this.versionSetBuilder = versionSetBuilder;
- }
-
+ ApiVersionSetBuilder versionSetBuilder ) : EndpointDataSource
+ {
public override IReadOnlyList Endpoints => decorated.Endpoints;
public override IChangeToken GetChangeToken() => decorated.GetChangeToken();
@@ -175,19 +158,10 @@ private void CollateGroupApiVersions()
}
}
- private sealed class EndpointDataSourceCollectionAdapter : ICollection
- {
- private readonly ICollection adapted;
- private readonly ApiVersionSetBuilder versionSetBuilder;
-
- internal EndpointDataSourceCollectionAdapter(
+ private sealed class EndpointDataSourceCollectionAdapter(
ICollection adapted,
- ApiVersionSetBuilder versionSetBuilder )
- {
- this.adapted = adapted;
- this.versionSetBuilder = versionSetBuilder;
- }
-
+ ApiVersionSetBuilder versionSetBuilder ) : ICollection
+ {
public int Count => adapted.Count;
public bool IsReadOnly => adapted.IsReadOnly;
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DefaultApiVersionReporter.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DefaultApiVersionReporter.cs
index d495c8f6..94ecb388 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DefaultApiVersionReporter.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DefaultApiVersionReporter.cs
@@ -21,7 +21,7 @@ private static void AddApiVersionHeader( IHeaderDictionary headers, string heade
if ( versions.Count == 1 )
{
- headers.Add( headerName, versions[0].ToString() );
+ headers[headerName] = versions[0].ToString();
return;
}
@@ -55,7 +55,7 @@ private static void AddApiVersionHeader( IHeaderDictionary headers, string heade
}
}
- headers.Add( headerName, headerValue.ToString() );
+ headers[headerName] = headerValue.ToString();
pool.Return( array );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs
index 3419a436..89d6d51c 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/DependencyInjection/IServiceCollectionExtensions.cs
@@ -6,11 +6,12 @@ namespace Microsoft.Extensions.DependencyInjection;
using Asp.Versioning.ApiExplorer;
using Asp.Versioning.Routing;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
-using System;
-using static Microsoft.Extensions.DependencyInjection.ServiceDescriptor;
+using static ServiceDescriptor;
+using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;
///
/// Provides extension methods for the interface.
@@ -49,10 +50,7 @@ public static IApiVersioningBuilder AddApiVersioning( this IServiceCollection se
/// The original .
public static IApiVersioningBuilder EnableApiVersionBinding( this IApiVersioningBuilder builder )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
+ ArgumentNullException.ThrowIfNull( builder );
// currently required because there is no other hook.
// 1. TryParse does not work because:
@@ -77,44 +75,83 @@ public static IApiVersioningBuilder EnableApiVersionBinding( this IApiVersioning
return builder;
}
+ ///
+ /// Adds error object support in problem details.
+ ///
+ /// The services available in the application.
+ /// The JSON options setup to perform, if any.
+ /// The original .
+ ///
+ ///
+ /// This method is only intended to provide backward compatibility with previous library versions by converting
+ /// into Error Objects that conform to the
+ /// Error Responses
+ /// in the Microsoft REST API Guidelines and
+ /// OData Error Responses .
+ ///
+ ///
+ /// This method should be called before .
+ ///
+ ///
+ public static IServiceCollection AddErrorObjects( this IServiceCollection services, Action? setup = default ) =>
+ AddErrorObjects( services, setup );
+
+ ///
+ /// Adds error object support in problem details.
+ ///
+ /// The type of .
+ /// The services available in the application.
+ /// The JSON options setup to perform, if any.
+ /// The original .
+ ///
+ ///
+ /// This method is only intended to provide backward compatibility with previous library versions by converting
+ /// into Error Objects that conform to the
+ /// Error Responses
+ /// in the Microsoft REST API Guidelines and
+ /// OData Error Responses .
+ ///
+ ///
+ /// This method should be called before .
+ ///
+ ///
+ public static IServiceCollection AddErrorObjects<[DynamicallyAccessedMembers( PublicConstructors )] TWriter>(
+ this IServiceCollection services,
+ Action? setup = default )
+ where TWriter : ErrorObjectWriter
+ {
+ ArgumentNullException.ThrowIfNull( services );
+
+ services.TryAddEnumerable( Singleton() );
+ services.Configure( setup ?? DefaultErrorObjectJsonConfig );
+
+ // TODO: remove with TryAddErrorObjectJsonOptions in 9.0+
+ services.AddTransient();
+
+ return services;
+ }
+
+ private static void DefaultErrorObjectJsonConfig( JsonOptions options ) =>
+ options.SerializerOptions.TypeInfoResolverChain.Insert( 0, ErrorObjectWriter.ErrorObjectJsonContext.Default );
+
private static void AddApiVersioningServices( IServiceCollection services )
{
- if ( services == null )
- {
- throw new ArgumentNullException( nameof( services ) );
- }
+ ArgumentNullException.ThrowIfNull( services );
services.TryAddSingleton();
- services.Add( Singleton( sp => sp.GetRequiredService>().Value.ApiVersionReader ) );
- services.Add( Singleton( sp => (IApiVersionParameterSource) sp.GetRequiredService>().Value.ApiVersionReader ) );
- services.Add( Singleton( sp => sp.GetRequiredService>().Value.ApiVersionSelector ) );
+ services.AddSingleton( static sp => sp.GetRequiredService>().Value.ApiVersionReader );
+ services.AddSingleton( static sp => (IApiVersionParameterSource) sp.GetRequiredService>().Value.ApiVersionReader );
+ services.AddSingleton( static sp => sp.GetRequiredService>().Value.ApiVersionSelector );
services.TryAddSingleton();
services.TryAddSingleton();
+ services.TryAddEnumerable( Transient, ValidateApiVersioningOptions>() );
services.TryAddEnumerable( Transient, ApiVersioningRouteOptionsSetup>() );
services.TryAddEnumerable( Singleton() );
services.TryAddEnumerable( Singleton() );
+ services.TryAddTransient();
services.Replace( WithLinkGeneratorDecorator( services ) );
TryAddProblemDetailsRfc7231Compliance( services );
- }
-
- // REF: https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs#L125
- private static Type GetImplementationType( this ServiceDescriptor descriptor )
- {
- if ( descriptor.ImplementationType != null )
- {
- return descriptor.ImplementationType;
- }
- else if ( descriptor.ImplementationInstance != null )
- {
- return descriptor.ImplementationInstance.GetType();
- }
- else if ( descriptor.ImplementationFactory != null )
- {
- var typeArguments = descriptor.ImplementationFactory.GetType().GenericTypeArguments;
- return typeArguments[1];
- }
-
- throw new InvalidOperationException();
+ TryAddErrorObjectJsonOptions( services );
}
private static ServiceDescriptor WithLinkGeneratorDecorator( IServiceCollection services )
@@ -132,12 +169,31 @@ private static ServiceDescriptor WithLinkGeneratorDecorator( IServiceCollection
if ( factory == null )
{
- var decoratedType = descriptor.GetImplementationType();
- var decoratorType = typeof( ApiVersionLinkGenerator<> ).MakeGenericType( decoratedType );
+ // REF: https://github.com/dotnet/aspnetcore/blob/main/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs#L96
+ // REF: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs#L292
+ var decoratedType = descriptor switch
+ {
+ { ImplementationType: var type } when type is not null => type,
+ { ImplementationInstance: var instance } when instance is not null => instance.GetType(),
+ _ => throw new InvalidOperationException(),
+ };
services.Replace( Describe( decoratedType, decoratedType, lifetime ) );
- return Describe( typeof( LinkGenerator ), decoratorType, lifetime );
+ LinkGenerator NewFactory( IServiceProvider serviceProvider )
+ {
+ var instance = (LinkGenerator) serviceProvider.GetRequiredService( decoratedType! );
+ var source = serviceProvider.GetRequiredService();
+
+ if ( source.VersionsByUrl() )
+ {
+ instance = new ApiVersionLinkGenerator( instance );
+ }
+
+ return instance;
+ }
+
+ return Describe( typeof( LinkGenerator ), NewFactory, lifetime );
}
else
{
@@ -158,8 +214,8 @@ LinkGenerator NewFactory( IServiceProvider serviceProvider )
}
}
- // TODO: Remove in .NET 8.0
- // REF: https://github.com/dotnet/aspnetcore/issues/45051
+ // TODO: Remove in .NET 9.0 or .NET 8.0 patch
+ // BUG: https://github.com/dotnet/aspnetcore/issues/52577
private static void TryAddProblemDetailsRfc7231Compliance( IServiceCollection services )
{
var descriptor = services.FirstOrDefault( IsDefaultProblemDetailsWriter );
@@ -182,4 +238,49 @@ static bool IsDefaultProblemDetailsWriter( ServiceDescriptor serviceDescriptor )
static Rfc7231ProblemDetailsWriter NewProblemDetailsWriter( IServiceProvider serviceProvider, Type decoratedType ) =>
new( (IProblemDetailsWriter) serviceProvider.GetRequiredService( decoratedType ) );
}
+
+ // TODO: retain for 8.1.x back-compat, but remove in 9.0+ in favor of AddErrorObjects for perf
+ private static void TryAddErrorObjectJsonOptions( IServiceCollection services )
+ {
+ var serviceType = typeof( IProblemDetailsWriter );
+ var implementationType = typeof( ErrorObjectWriter );
+ var markerType = typeof( ErrorObjectsAdded );
+ var hasErrorObjects = false;
+ var hasErrorObjectsJsonConfig = false;
+
+ for ( var i = 0; i < services.Count; i++ )
+ {
+ var service = services[i];
+
+ if ( !hasErrorObjects &&
+ service.ServiceType == serviceType &&
+ implementationType.IsAssignableFrom( service.ImplementationType ) )
+ {
+ hasErrorObjects = true;
+
+ if ( hasErrorObjectsJsonConfig )
+ {
+ break;
+ }
+ }
+ else if ( service.ServiceType == markerType )
+ {
+ hasErrorObjectsJsonConfig = true;
+
+ if ( hasErrorObjects )
+ {
+ break;
+ }
+ }
+ }
+
+ if ( hasErrorObjects && !hasErrorObjectsJsonConfig )
+ {
+ services.Configure( DefaultErrorObjectJsonConfig );
+ }
+ }
+
+// TEMP: this is a marker class to test whether Error Objects have been explicitly added. remove in 9.0+
+#pragma warning disable CA1812 // Avoid uninstantiated internal classes
+ private sealed class ErrorObjectsAdded { }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ErrorObjectWriter.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ErrorObjectWriter.cs
new file mode 100644
index 00000000..b02d1260
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ErrorObjectWriter.cs
@@ -0,0 +1,215 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+// Ignore Spelling: Serializer
+namespace Asp.Versioning;
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using static System.Text.Json.Serialization.JsonIgnoreCondition;
+using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions;
+
+///
+/// Represents a problem details writer that outputs error objects in responses.
+///
+/// This enables backward compatibility by converting into Error Objects that
+/// conform to the Error Responses
+/// in the Microsoft REST API Guidelines and
+/// OData Error Responses .
+[CLSCompliant( false )]
+public partial class ErrorObjectWriter : IProblemDetailsWriter
+{
+ private readonly JsonSerializerOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current JSON options .
+ /// is null .
+ public ErrorObjectWriter( IOptions options ) =>
+ this.options = ( options ?? throw new ArgumentNullException( nameof( options ) ) ).Value.SerializerOptions;
+
+ ///
+ /// Gets the associated, default .
+ ///
+ /// The associated, default .
+ public static JsonSerializerContext DefaultJsonSerializerContext => ErrorObjectJsonContext.Default;
+
+ ///
+ /// Creates and returns a new associated with the writer.
+ ///
+ /// The JSON serializer options to use.
+ /// A new .
+ public static JsonSerializerContext NewJsonSerializerContext( JsonSerializerOptions options ) => new ErrorObjectJsonContext( options );
+
+ ///
+ public virtual bool CanWrite( ProblemDetailsContext context )
+ {
+ ArgumentNullException.ThrowIfNull( context );
+
+ var type = context.ProblemDetails.Type;
+
+ return type == ProblemDetailsDefaults.Unsupported.Type ||
+ type == ProblemDetailsDefaults.Unspecified.Type ||
+ type == ProblemDetailsDefaults.Invalid.Type ||
+ type == ProblemDetailsDefaults.Ambiguous.Type;
+ }
+
+ ///
+ public virtual ValueTask WriteAsync( ProblemDetailsContext context )
+ {
+ ArgumentNullException.ThrowIfNull( context );
+
+ var response = context.HttpContext.Response;
+ var obj = new ErrorObject( context.ProblemDetails );
+
+ OnBeforeWrite( context, ref obj );
+
+ return new( response.WriteAsJsonAsync( obj, options.GetTypeInfo( obj.GetType() ) ) );
+ }
+
+ ///
+ /// Occurs just before an error will be written.
+ ///
+ /// The current context.
+ /// The current error object.
+ /// Note to inheritors: The default implementation performs no action.
+ protected virtual void OnBeforeWrite( ProblemDetailsContext context, ref ErrorObject errorObject )
+ {
+ }
+
+#pragma warning disable CA1815 // Override equals and operator equals on value types
+
+ ///
+ /// Represents an error object.
+ ///
+ protected internal readonly partial struct ErrorObject
+ {
+ internal ErrorObject( ProblemDetails problemDetails ) =>
+ Error = new( problemDetails );
+
+ ///
+ /// Gets the top-level error.
+ ///
+ /// The top-level error.
+ [JsonPropertyName( "error" )]
+ public ErrorDetail Error { get; }
+ }
+
+ ///
+ /// Represents the error detail.
+ ///
+ protected internal readonly partial struct ErrorDetail
+ {
+ private const string CodeProperty = "code";
+ private readonly ProblemDetails problemDetails;
+ private readonly InnerError? innerError;
+ private readonly Dictionary extensions = [];
+
+ internal ErrorDetail( ProblemDetails problemDetails )
+ {
+ this.problemDetails = problemDetails;
+ innerError = string.IsNullOrEmpty( problemDetails.Detail ) ? default : new InnerError( problemDetails );
+ }
+
+ ///
+ /// Gets or sets one of a server-defined set of error codes.
+ ///
+ /// A server-defined error code.
+ [JsonPropertyName( CodeProperty )]
+ [JsonIgnore( Condition = WhenWritingNull )]
+ public string? Code
+ {
+ get => problemDetails.Extensions.TryGetValue( CodeProperty, out var value ) &&
+ value is string code ? code : default;
+ set
+ {
+ if ( value is null )
+ {
+ problemDetails.Extensions.Remove( CodeProperty );
+ }
+ else
+ {
+ problemDetails.Extensions[CodeProperty] = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the error message.
+ ///
+ /// A human-readable representation of the error.
+ [JsonPropertyName( "message" )]
+ [JsonIgnore( Condition = WhenWritingNull )]
+ public string? Message
+ {
+ get => problemDetails.Title;
+ set => problemDetails.Title = value;
+ }
+
+ ///
+ /// Gets or sets the target of the error.
+ ///
+ /// The error target of the error.
+ [JsonPropertyName( "target" )]
+ [JsonIgnore( Condition = WhenWritingNull )]
+ public string? Target
+ {
+ get => problemDetails.Title;
+ set => problemDetails.Title = value;
+ }
+
+ ///
+ /// Gets an object containing more specific information than the current object about the error, if any.
+ ///
+ /// The inner error or null .
+ [JsonPropertyName( "innerError" )]
+ [JsonIgnore( Condition = WhenWritingNull )]
+ public InnerError? InnerError => innerError;
+
+ ///
+ /// Gets a collection of extension key/value pair members.
+ ///
+ /// A collection of extension key/value pair members.
+ [JsonExtensionData]
+ public IDictionary Extensions => extensions;
+ }
+
+ ///
+ /// Represents an inner error.
+ ///
+ protected internal readonly partial struct InnerError
+ {
+ private readonly ProblemDetails problemDetails;
+ private readonly Dictionary extensions = [];
+
+ internal InnerError( ProblemDetails problemDetails ) =>
+ this.problemDetails = problemDetails;
+
+ ///
+ /// Gets or sets the inner error message.
+ ///
+ /// The inner error message.
+ [JsonPropertyName( "message" )]
+ [JsonIgnore( Condition = WhenWritingNull )]
+ public string? Message
+ {
+ get => problemDetails.Detail;
+ set => problemDetails.Detail = value;
+ }
+
+ ///
+ /// Gets a collection of extension key/value pair members.
+ ///
+ /// A collection of extension key/value pair members.
+ [JsonExtensionData]
+ public IDictionary Extensions => extensions;
+ }
+
+ [JsonSerializable( typeof( ErrorObject ) )]
+ internal sealed partial class ErrorObjectJsonContext : JsonSerializerContext
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Format.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Format.cs
new file mode 100644
index 00000000..060138aa
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Format.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using System.Text;
+
+internal static class Format
+{
+ internal static readonly CompositeFormat MultipleDifferentApiVersionsRequested = CompositeFormat.Parse( CommonSR.MultipleDifferentApiVersionsRequested );
+ internal static readonly CompositeFormat NoVersionSet = CompositeFormat.Parse( SR.NoVersionSet );
+ internal static readonly CompositeFormat InvalidMediaTypeTemplate = CompositeFormat.Parse( CommonSR.InvalidMediaTypeTemplate );
+ internal static readonly CompositeFormat UnsetRequestDelegate = CompositeFormat.Parse( SR.UnsetRequestDelegate );
+ internal static readonly CompositeFormat VersionedResourceNotSupported = CompositeFormat.Parse( SR.VersionedResourceNotSupported );
+ internal static readonly CompositeFormat InvalidDefaultApiVersion = CompositeFormat.Parse( SR.InvalidDefaultApiVersion );
+ internal static readonly CompositeFormat InvalidPolicyKey = CompositeFormat.Parse( CommonSR.InvalidPolicyKey );
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs
index 674cb196..96b90c63 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/HeaderApiVersionReader.cs
@@ -14,10 +14,7 @@ public partial class HeaderApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequest request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
var count = HeaderNames.Count;
@@ -73,7 +70,7 @@ public virtual IReadOnlyList Read( HttpRequest request )
if ( versions == null )
{
- return version == null ? Array.Empty() : new[] { version };
+ return version == null ? [] : [version];
}
return versions.ToArray();
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs
index e504004c..60167f90 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpContextExtensions.cs
@@ -18,10 +18,7 @@ public static class HttpContextExtensions
/// The current API versioning feature .
public static IApiVersioningFeature ApiVersioningFeature( this HttpContext context )
{
- if ( context == null )
- {
- throw new ArgumentNullException( nameof( context ) );
- }
+ ArgumentNullException.ThrowIfNull( context );
var feature = context.Features.Get();
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpRequestExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpRequestExtensions.cs
index 209d5832..6ce309bd 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpRequestExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpRequestExtensions.cs
@@ -3,9 +3,9 @@
namespace Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
-using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
using System.ComponentModel;
+using RoutePattern = Microsoft.AspNetCore.Routing.Patterns.RoutePattern;
///
/// Provides extension methods for .
@@ -16,6 +16,7 @@ public static class HttpRequestExtensions
///
/// Attempts to get the API version from current request path using the provided patterns.
///
+ /// The type of read-only list .
/// The current HTTP request .
/// The read-only list of
/// patterns to evaluate.
@@ -23,16 +24,14 @@ public static class HttpRequestExtensions
/// The raw API version, if retrieved.
/// True if the raw API version was retrieved; otherwise, false.
[EditorBrowsable( EditorBrowsableState.Never )]
- public static bool TryGetApiVersionFromPath(
+ public static bool TryGetApiVersionFromPath(
this HttpRequest request,
- IReadOnlyList routePatterns,
+ TList routePatterns,
string constraintName,
[NotNullWhen( true )] out string? apiVersion )
+ where TList : IReadOnlyList
{
- if ( routePatterns == null )
- {
- throw new ArgumentNullException( nameof( routePatterns ) );
- }
+ ArgumentNullException.ThrowIfNull( routePatterns );
if ( string.IsNullOrEmpty( constraintName ) || routePatterns.Count == 0 )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs
index 85b375fa..9b8ed20a 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Http/HttpResponseExtensions.cs
@@ -23,15 +23,8 @@ public static class HttpResponseExtensions
[CLSCompliant( false )]
public static void WriteSunsetPolicy( this HttpResponse response, SunsetPolicy sunsetPolicy )
{
- if ( response == null )
- {
- throw new ArgumentNullException( nameof( response ) );
- }
-
- if ( sunsetPolicy == null )
- {
- throw new ArgumentNullException( nameof( sunsetPolicy ) );
- }
+ ArgumentNullException.ThrowIfNull( response );
+ ArgumentNullException.ThrowIfNull( sunsetPolicy );
var headers = response.Headers;
@@ -45,7 +38,7 @@ public static void WriteSunsetPolicy( this HttpResponse response, SunsetPolicy s
if ( sunsetPolicy.Date.HasValue )
{
- headers.Add( Sunset, sunsetPolicy.Date.Value.ToString( "r" ) );
+ headers[Sunset] = sunsetPolicy.Date.Value.ToString( "r" );
}
AddLinkHeaders( headers, sunsetPolicy.Links );
@@ -60,16 +53,7 @@ private static void AddLinkHeaders( IHeaderDictionary headers, IList
@@ -81,10 +65,7 @@ private static void AddLinkHeaders( IHeaderDictionary headers, IList
public static void AddApiVersionToContentType( this HttpResponse response, string name )
{
- if ( response == null )
- {
- throw new ArgumentNullException( nameof( response ) );
- }
+ ArgumentNullException.ThrowIfNull( response );
if ( response.StatusCode < 200 && response.StatusCode > 299 )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelector.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelector.cs
new file mode 100644
index 00000000..70f1a38b
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelector.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using Microsoft.AspNetCore.Http;
+
+///
+/// content>
+/// Provides additional implementation specific to ASP.NET Core.
+///
+[CLSCompliant( false )]
+public partial interface IApiVersionSelector
+{
+ ///
+ /// Selects an API version given the specified HTTP request and API version information.
+ ///
+ /// The current HTTP request to select the version for.
+ /// The model to select the version from.
+ /// The token that can be used to cancel the operation.
+ /// A task containing the selected API version .
+ ValueTask SelectVersionAsync(
+ HttpRequest request,
+ ApiVersionModel model,
+ CancellationToken cancellationToken ) =>
+ ValueTask.FromResult( SelectVersion( request, model ) );
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelectorExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelectorExtensions.cs
new file mode 100644
index 00000000..a88a8e13
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/IApiVersionSelectorExtensions.cs
@@ -0,0 +1,25 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using Microsoft.AspNetCore.Http;
+
+///
+/// Provides extension methods for .
+///
+[CLSCompliant( false )]
+public static class IApiVersionSelectorExtensions
+{
+ ///
+ /// Selects an API version given the specified API version information.
+ ///
+ /// The extended .
+ /// The model to select the version from.
+ /// The selected API version .
+ public static ApiVersion SelectVersion( this IApiVersionSelector selector, ApiVersionModel model )
+ {
+ ArgumentNullException.ThrowIfNull( selector );
+ var context = new DefaultHttpContext();
+ return selector.SelectVersion( context.Request, model );
+ }
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReader.cs
index 19d3a291..9a040c6f 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReader.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReader.cs
@@ -13,10 +13,7 @@ public partial class MediaTypeApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequest request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
var headers = request.GetTypedHeaders();
var contentType = headers.ContentType;
@@ -25,18 +22,18 @@ public virtual IReadOnlyList Read( HttpRequest request )
if ( accept is null || ReadAcceptHeader( accept ) is not string otherVersion )
{
- return version is null ? Array.Empty() : new[] { version };
+ return version is null ? [] : [version];
}
var comparer = StringComparer.OrdinalIgnoreCase;
if ( version is null || comparer.Equals( version, otherVersion ) )
{
- return new[] { otherVersion };
+ return [otherVersion];
}
- return comparer.Compare( version, otherVersion ) <= 0 ?
- new[] { version, otherVersion } :
- new[] { otherVersion, version };
+ return comparer.Compare( version, otherVersion ) <= 0
+ ? [version, otherVersion]
+ : [otherVersion, version];
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReaderBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReaderBuilder.cs
index 22eeef58..92bcbd12 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReaderBuilder.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/MediaTypeApiVersionReaderBuilder.cs
@@ -22,18 +22,19 @@ public partial class MediaTypeApiVersionReaderBuilder
/// If a value is not specified, there is expected to be a single template parameter.
/// The current .
/// The template syntax is the same used by route templates; however, constraints are not supported.
+#pragma warning disable IDE0079
+#pragma warning disable CA1716 // Identifiers should not match keywords
public virtual MediaTypeApiVersionReaderBuilder Template( string template, string? parameterName = default )
+#pragma warning restore CA1716 // Identifiers should not match keywords
+#pragma warning restore IDE0079
{
- if ( string.IsNullOrEmpty( template ) )
- {
- throw new ArgumentNullException( nameof( template ) );
- }
+ ArgumentException.ThrowIfNullOrEmpty( template );
var routePattern = RoutePatternFactory.Parse( template );
if ( string.IsNullOrEmpty( parameterName ) && routePattern.Parameters.Count > 1 )
{
- var message = string.Format( CultureInfo.CurrentCulture, CommonSR.InvalidMediaTypeTemplate, template );
+ var message = string.Format( CultureInfo.CurrentCulture, Format.InvalidMediaTypeTemplate, template );
throw new ArgumentException( message, nameof( template ) );
}
@@ -45,7 +46,7 @@ public partial class MediaTypeApiVersionReaderBuilder
return this;
}
- private static IReadOnlyList ReadMediaTypePattern(
+ private static string[] ReadMediaTypePattern(
IReadOnlyList mediaTypes,
TemplateMatcher matcher,
string? parameterName )
@@ -102,11 +103,6 @@ private static IReadOnlyList ReadMediaTypePattern(
}
}
- if ( version is null )
- {
- return Array.Empty();
- }
-
- return versions is null ? new[] { version } : versions.ToArray();
+ return ToArray( ref version, versions );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs
index 29d0d03e..144f5b34 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/QueryStringApiVersionReader.cs
@@ -14,10 +14,7 @@ public partial class QueryStringApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequest request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
var count = ParameterNames.Count;
@@ -69,7 +66,7 @@ public virtual IReadOnlyList Read( HttpRequest request )
if ( versions == null )
{
- return version == null ? Array.Empty() : new[] { version };
+ return version == null ? [] : [version];
}
return versions.ToArray();
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs
index 261060d6..3b380197 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Rfc7231ProblemDetailsWriter.cs
@@ -9,7 +9,7 @@ namespace Asp.Versioning;
internal sealed class Rfc7231ProblemDetailsWriter : IProblemDetailsWriter
{
private static readonly MediaTypeHeaderValue jsonMediaType = new( "application/json" );
- private static readonly MediaTypeHeaderValue problemDetailsJsonMediaType = new( "application/problem+json" );
+ private static readonly MediaTypeHeaderValue problemDetailsJsonMediaType = new( ProblemDetailsDefaults.MediaType.Json );
private readonly IProblemDetailsWriter decorated;
public Rfc7231ProblemDetailsWriter( IProblemDetailsWriter decorated ) => this.decorated = decorated;
@@ -36,8 +36,11 @@ public bool CanWrite( ProblemDetailsContext context )
{
var acceptHeaderValue = acceptHeader[i];
- if ( jsonMediaType.IsSubsetOf( acceptHeaderValue ) ||
- problemDetailsJsonMediaType.IsSubsetOf( acceptHeaderValue ) )
+ // TODO: the logic is inverted in .NET 8. remove when fixed
+ // BUG: https://github.com/dotnet/aspnetcore/issues/52577
+ // REF: https://github.com/dotnet/aspnetcore/blob/release/8.0/src/Http/Http.Extensions/src/DefaultProblemDetailsWriter.cs#L38
+ if ( acceptHeaderValue.IsSubsetOf( jsonMediaType ) ||
+ acceptHeaderValue.IsSubsetOf( problemDetailsJsonMediaType ) )
{
return true;
}
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs
index 4cde72f6..80eb5456 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/AmbiguousApiVersionEndpoint.cs
@@ -19,7 +19,7 @@ private static Task OnExecute( HttpContext context, ILogger logger )
{
var apiVersions = context.ApiVersioningFeature().RawRequestedApiVersions;
- logger.ApiVersionAmbiguous( apiVersions.ToArray() );
+ logger.ApiVersionAmbiguous( [.. apiVersions] );
context.Response.StatusCode = StatusCodes.Status400BadRequest;
if ( !context.TryGetProblemDetailsService( out var problemDetails ) )
@@ -29,10 +29,10 @@ private static Task OnExecute( HttpContext context, ILogger logger )
var detail = string.Format(
CultureInfo.CurrentCulture,
- CommonSR.MultipleDifferentApiVersionsRequested,
+ Format.MultipleDifferentApiVersionsRequested,
string.Join( ", ", apiVersions ) );
- return problemDetails.WriteAsync(
+ return problemDetails.TryWriteAsync(
EndpointProblem.New(
context,
ProblemDetailsDefaults.Ambiguous,
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionLinkGenerator.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionLinkGenerator.cs
index c3040163..abdaa172 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionLinkGenerator.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionLinkGenerator.cs
@@ -65,7 +65,7 @@ public class ApiVersionLinkGenerator : LinkGenerator
public override string? GetUriByAddress(
TAddress address,
RouteValueDictionary values,
- string? scheme,
+ string scheme,
HostString host,
PathString pathBase = default,
FragmentString fragment = default,
@@ -73,15 +73,8 @@ public class ApiVersionLinkGenerator : LinkGenerator
private static void AddApiVersionRouteValueIfNecessary( HttpContext httpContext, RouteValueDictionary values )
{
- if ( httpContext == null )
- {
- throw new ArgumentNullException( nameof( httpContext ) );
- }
-
- if ( values == null )
- {
- throw new ArgumentNullException( nameof( values ) );
- }
+ ArgumentNullException.ThrowIfNull( httpContext );
+ ArgumentNullException.ThrowIfNull( values );
var feature = httpContext.ApiVersioningFeature();
var key = feature.RouteParameter;
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs
index 1ba4358e..e43223aa 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionMatcherPolicy.cs
@@ -10,6 +10,7 @@ namespace Asp.Versioning.Routing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Buffers;
+using System.Collections.Frozen;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using static Asp.Versioning.ApiVersionMapping;
@@ -40,10 +41,15 @@ public ApiVersionMatcherPolicy(
IOptions options,
ILogger logger )
{
- this.apiVersionParser = apiVersionParser ?? throw new ArgumentNullException( nameof( apiVersionParser ) );
- collator = new( providers ?? throw new ArgumentNullException( nameof( providers ) ), options );
- this.options = options ?? throw new ArgumentNullException( nameof( options ) );
- this.logger = logger ?? throw new ArgumentNullException( nameof( logger ) );
+ ArgumentNullException.ThrowIfNull( apiVersionParser );
+ ArgumentNullException.ThrowIfNull( providers );
+ ArgumentNullException.ThrowIfNull( options );
+ ArgumentNullException.ThrowIfNull( logger );
+
+ this.apiVersionParser = apiVersionParser;
+ collator = new( providers, options );
+ this.options = options;
+ this.logger = logger;
}
///
@@ -58,10 +64,7 @@ public ApiVersionMatcherPolicy(
///
public bool AppliesToEndpoints( IReadOnlyList endpoints )
{
- if ( endpoints == null )
- {
- throw new ArgumentNullException( nameof( endpoints ) );
- }
+ ArgumentNullException.ThrowIfNull( endpoints );
for ( var i = 0; i < endpoints.Count; i++ )
{
@@ -75,24 +78,17 @@ public bool AppliesToEndpoints( IReadOnlyList endpoints )
}
///
- public Task ApplyAsync( HttpContext httpContext, CandidateSet candidates )
+ public async Task ApplyAsync( HttpContext httpContext, CandidateSet candidates )
{
- if ( httpContext == null )
- {
- throw new ArgumentNullException( nameof( httpContext ) );
- }
-
- if ( candidates == null )
- {
- throw new ArgumentNullException( nameof( candidates ) );
- }
+ ArgumentNullException.ThrowIfNull( httpContext );
+ ArgumentNullException.ThrowIfNull( candidates );
var feature = httpContext.ApiVersioningFeature();
var apiVersion = feature.RequestedApiVersion;
if ( apiVersion == null && Options.AssumeDefaultVersionWhenUnspecified )
{
- apiVersion = TrySelectApiVersion( httpContext, candidates );
+ apiVersion = await TrySelectApiVersionAsync( httpContext, candidates ).ConfigureAwait( false );
feature.RequestedApiVersion = apiVersion;
}
@@ -103,17 +99,12 @@ public Task ApplyAsync( HttpContext httpContext, CandidateSet candidates )
var builder = new ClientErrorEndpointBuilder( feature, candidates, Options, logger );
httpContext.SetEndpoint( builder.Build() );
}
-
- return Task.CompletedTask;
}
///
public PolicyJumpTable BuildJumpTable( int exitDestination, IReadOnlyList edges )
{
- if ( edges == null )
- {
- throw new ArgumentNullException( nameof( edges ) );
- }
+ ArgumentNullException.ThrowIfNull( edges );
var rejection = new RouteDestination( exitDestination );
var capacity = edges.Count - EdgeBuilder.NumberOfRejectionEndpoints;
@@ -159,7 +150,7 @@ public PolicyJumpTable BuildJumpTable( int exitDestination, IReadOnlyList(),
+ routePatterns ?? [],
apiVersionParser,
source,
Options );
@@ -178,10 +169,7 @@ public PolicyJumpTable BuildJumpTable( int exitDestination, IReadOnlyList
public IReadOnlyList GetEdges( IReadOnlyList endpoints )
{
- if ( endpoints == null )
- {
- throw new ArgumentNullException( nameof( endpoints ) );
- }
+ ArgumentNullException.ThrowIfNull( endpoints );
var capacity = endpoints.Count;
var builder = new EdgeBuilder( capacity, ApiVersionSource, Options, logger );
@@ -203,7 +191,7 @@ public IReadOnlyList GetEdges( IReadOnlyList endpoints
if ( model.IsApiVersionNeutral )
{
builder.Add( endpoint, ApiVersion.Neutral, metadata );
- neutralEndpoints ??= new();
+ neutralEndpoints ??= [];
neutralEndpoints.Add( (endpoint, metadata) );
}
else
@@ -320,7 +308,7 @@ private static void Collate(
if ( versions.Count > 0 )
{
- supported ??= new();
+ supported ??= [];
for ( var j = 0; j < versions.Count; j++ )
{
@@ -335,7 +323,7 @@ private static void Collate(
return;
}
- deprecated ??= new();
+ deprecated ??= [];
for ( var j = 0; j < versions.Count; j++ )
{
@@ -464,7 +452,7 @@ private static (bool Matched, bool HasCandidates) MatchApiVersion( CandidateSet
return (matched, hasCandidates);
}
- private ApiVersion TrySelectApiVersion( HttpContext httpContext, CandidateSet candidates )
+ private ValueTask TrySelectApiVersionAsync( HttpContext httpContext, CandidateSet candidates )
{
var models = new List( capacity: candidates.Count );
@@ -484,24 +472,20 @@ private ApiVersion TrySelectApiVersion( HttpContext httpContext, CandidateSet ca
}
}
- return ApiVersionSelector.SelectVersion( httpContext.Request, models.Aggregate() );
+ return ApiVersionSelector.SelectVersionAsync(
+ httpContext.Request,
+ models.Aggregate(),
+ httpContext.RequestAborted );
}
bool INodeBuilderPolicy.AppliesToEndpoints( IReadOnlyList endpoints ) =>
!ContainsDynamicEndpoints( endpoints ) && AppliesToEndpoints( endpoints );
- private readonly struct Match
+ private readonly struct Match( int index, int score, bool isExplicit )
{
- internal readonly int Index;
- internal readonly int Score;
- internal readonly bool IsExplicit;
-
- internal Match( int index, int score, bool isExplicit )
- {
- Index = index;
- Score = score;
- IsExplicit = isExplicit;
- }
+ internal readonly int Index = index;
+ internal readonly int Score = score;
+ internal readonly bool IsExplicit = isExplicit;
internal int CompareTo( in Match other )
{
@@ -510,22 +494,15 @@ internal int CompareTo( in Match other )
}
}
- private sealed class ApiVersionCollator
+ private sealed class ApiVersionCollator(
+ IEnumerable providers,
+ IOptions options )
{
- private readonly IApiVersionMetadataCollationProvider[] providers;
- private readonly IOptions options;
+ private readonly IApiVersionMetadataCollationProvider[] providers = providers.ToArray();
private readonly object syncRoot = new();
private IReadOnlyList? items;
private int version;
- internal ApiVersionCollator(
- IEnumerable providers,
- IOptions options )
- {
- this.providers = providers.ToArray();
- this.options = options;
- }
-
public IReadOnlyList Items
{
get
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs
index a7eb03da..24bb4a23 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionPolicyJumpTable.cs
@@ -6,6 +6,7 @@ namespace Asp.Versioning.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Net.Http.Headers;
+using System.Collections.Frozen;
using System.Runtime.CompilerServices;
internal sealed class ApiVersionPolicyJumpTable : PolicyJumpTable
@@ -14,17 +15,17 @@ internal sealed class ApiVersionPolicyJumpTable : PolicyJumpTable
private readonly bool versionsByUrlOnly;
private readonly bool versionsByMediaTypeOnly;
private readonly RouteDestination rejection;
- private readonly IReadOnlyDictionary destinations;
+ private readonly FrozenDictionary destinations;
private readonly ApiVersionPolicyFeature? policyFeature;
- private readonly IReadOnlyList routePatterns;
+ private readonly RoutePattern[] routePatterns;
private readonly IApiVersionParser parser;
private readonly ApiVersioningOptions options;
internal ApiVersionPolicyJumpTable(
RouteDestination rejection,
- IReadOnlyDictionary destinations,
+ FrozenDictionary destinations,
ApiVersionPolicyFeature? policyFeature,
- IReadOnlyList routePatterns,
+ RoutePattern[] routePatterns,
IApiVersionParser parser,
IApiVersionParameterSource source,
ApiVersioningOptions options )
@@ -35,7 +36,7 @@ internal ApiVersionPolicyJumpTable(
this.routePatterns = routePatterns;
this.parser = parser;
this.options = options;
- versionsByUrl = routePatterns.Count > 0;
+ versionsByUrl = routePatterns.Length > 0;
versionsByUrlOnly = source.VersionsByUrl( allowMultipleLocations: false );
versionsByMediaTypeOnly = source.VersionsByMediaType( allowMultipleLocations: false );
}
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionRouteConstraint.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionRouteConstraint.cs
index 943315f5..6b4cfd76 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionRouteConstraint.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersionRouteConstraint.cs
@@ -23,10 +23,7 @@ public sealed class ApiVersionRouteConstraint : IRouteConstraint
/// True if the route constraint is matched; otherwise, false.
public bool Match( HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection )
{
- if ( values == null )
- {
- throw new ArgumentNullException( nameof( values ) );
- }
+ ArgumentNullException.ThrowIfNull( values );
if ( string.IsNullOrEmpty( routeKey ) )
{
@@ -43,10 +40,7 @@ public bool Match( HttpContext? httpContext, IRouter? route, string routeKey, Ro
return !string.IsNullOrEmpty( value );
}
- if ( httpContext == null )
- {
- throw new ArgumentNullException( nameof( httpContext ) );
- }
+ ArgumentNullException.ThrowIfNull( httpContext );
var parser = httpContext.RequestServices.GetRequiredService();
var feature = httpContext.ApiVersioningFeature();
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersioningRouteOptionsSetup.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersioningRouteOptionsSetup.cs
index f41b6ed3..8d6a5755 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersioningRouteOptionsSetup.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ApiVersioningRouteOptionsSetup.cs
@@ -2,8 +2,8 @@
namespace Asp.Versioning.Routing;
-using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
+using RouteOptions = Microsoft.AspNetCore.Routing.RouteOptions;
///
/// Represents the API versioning configuration for ASP.NET Core routing options .
@@ -22,12 +22,9 @@ public class ApiVersioningRouteOptionsSetup : IPostConfigureOptions
public virtual void PostConfigure( string? name, RouteOptions options )
{
- if ( options == null )
- {
- throw new ArgumentNullException( nameof( options ) );
- }
+ ArgumentNullException.ThrowIfNull( options );
- var key = versioningOptions.Value.RouteConstraintName;
- options.ConstraintMap.Add( key, typeof( ApiVersionRouteConstraint ) );
+ var token = versioningOptions.Value.RouteConstraintName;
+ options.SetParameterPolicy( token );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ClientErrorEndpointBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ClientErrorEndpointBuilder.cs
index 8077e8f9..cad20584 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ClientErrorEndpointBuilder.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/ClientErrorEndpointBuilder.cs
@@ -57,7 +57,7 @@ private string[] GetDisplayNames()
{
if ( candidates.Count == 0 )
{
- return Array.Empty();
+ return [];
}
ref readonly var candidate = ref candidates[0];
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs
index 010210f7..8fb60798 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EdgeBuilder.cs
@@ -86,7 +86,7 @@ private void Add( ref EdgeKey key, RouteEndpoint endpoint )
if ( !edges.TryGetValue( key, out var endpoints ) )
{
- edges.Add( key, endpoints = new() );
+ edges.Add( key, endpoints = [] );
}
endpoints.Add( endpoint );
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EndpointProblem.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EndpointProblem.cs
index bb64582f..9a8b539b 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EndpointProblem.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/EndpointProblem.cs
@@ -52,11 +52,11 @@ internal static Task UnsupportedApiVersion(
{
var detail = string.Format(
CultureInfo.CurrentCulture,
- SR.VersionedResourceNotSupported,
+ Format.VersionedResourceNotSupported,
new Uri( context.Request.GetDisplayUrl() ).SafeFullPath(),
context.ApiVersioningFeature().RawRequestedApiVersion );
- return problemDetails.WriteAsync( New( context, Unsupported, detail ) ).AsTask();
+ return problemDetails.TryWriteAsync( New( context, Unsupported, detail ) ).AsTask();
}
return Task.CompletedTask;
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs
index 568de2d2..c129794a 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/MalformedApiVersionEndpoint.cs
@@ -29,11 +29,11 @@ private static Task OnExecute( HttpContext context, ILogger logger )
var detail = string.Format(
CultureInfo.CurrentCulture,
- SR.VersionedResourceNotSupported,
+ Format.VersionedResourceNotSupported,
new Uri( context.Request.GetDisplayUrl() ).SafeFullPath(),
requestedVersion );
- return problemDetails.WriteAsync(
+ return problemDetails.TryWriteAsync(
EndpointProblem.New(
context,
ProblemDetailsDefaults.Invalid,
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternComparer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternComparer.cs
index 04d0a9d0..ca635586 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternComparer.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternComparer.cs
@@ -2,7 +2,7 @@
namespace Asp.Versioning.Routing;
-using Microsoft.AspNetCore.Routing.Patterns;
+using RoutePattern = Microsoft.AspNetCore.Routing.Patterns.RoutePattern;
///
/// Represents a comparer for comparing instances.
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternExtensions.cs
index d7582b1d..fa467989 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/RoutePatternExtensions.cs
@@ -2,8 +2,8 @@
namespace Asp.Versioning.Routing;
-using Microsoft.AspNetCore.Routing.Patterns;
using System.ComponentModel;
+using RoutePattern = Microsoft.AspNetCore.Routing.Patterns.RoutePattern;
///
/// Provides extension methods for .
@@ -20,10 +20,7 @@ public static class RoutePatternExtensions
/// True if the has the ; otherwise, false.
public static bool HasVersionConstraint( this RoutePattern routePattern, string constraintName )
{
- if ( routePattern == null )
- {
- throw new ArgumentNullException( nameof( routePattern ) );
- }
+ ArgumentNullException.ThrowIfNull( routePattern );
if ( string.IsNullOrEmpty( constraintName ) )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnspecifiedApiVersionEndpoint.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnspecifiedApiVersionEndpoint.cs
index dc97846f..6597aec9 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnspecifiedApiVersionEndpoint.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/Routing/UnspecifiedApiVersionEndpoint.cs
@@ -28,7 +28,7 @@ private static Task OnExecute( HttpContext context, string[]? candidateEndpoints
if ( context.TryGetProblemDetailsService( out var problemDetails ) )
{
- return problemDetails.WriteAsync(
+ return problemDetails.TryWriteAsync(
EndpointProblem.New(
context,
ProblemDetailsDefaults.Unspecified,
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.Designer.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.Designer.cs
index afa465b1..4a56d6b9 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.Designer.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.Designer.cs
@@ -96,6 +96,15 @@ internal static string ConventionAddedAfterEndpointBuilt {
}
}
+ ///
+ /// Looks up a localized string similar to {0}.{1} is an invalid value for {2}.{3}. Did you mean to apply {4} via attribute or convention instead?.
+ ///
+ internal static string InvalidDefaultApiVersion {
+ get {
+ return ResourceManager.GetString("InvalidDefaultApiVersion", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to An endpoint cannot apply multiple API version sets..
///
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.resx b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.resx
index b3d9185d..dde23ba1 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.resx
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/SR.resx
@@ -129,6 +129,14 @@
Conventions cannot be added after building the endpoint.
+
+ {0}.{1} is an invalid value for {2}.{3}. Did you mean to apply {4} via attribute or convention instead?
+ 0 = ApiVersion
+1 = Neutral
+2 = ApiVersioningOptions
+3 = DefaultApiVersion
+4 = IApiVersionNeutral
+
An endpoint cannot apply multiple API version sets.
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs
index 3ddb892b..ba9eec82 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/UrlSegmentApiVersionReader.cs
@@ -13,10 +13,7 @@ public partial class UrlSegmentApiVersionReader
///
public virtual IReadOnlyList Read( HttpRequest request )
{
- if ( request == null )
- {
- throw new ArgumentNullException( nameof( request ) );
- }
+ ArgumentNullException.ThrowIfNull( request );
if ( reentrant )
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ValidateApiVersioningOptions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ValidateApiVersioningOptions.cs
new file mode 100644
index 00000000..2abe453b
--- /dev/null
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Http/ValidateApiVersioningOptions.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+namespace Asp.Versioning;
+
+using Microsoft.Extensions.Options;
+using System.Globalization;
+
+// ApiVersion.Neutral does not have the same meaning as IApiVersionNeutral. setting
+// ApiVersioningOptions.DefaultApiVersion this value will not make all APIs version-neutral
+// and will likely lead to many unexpected side effects. this is a best-effort, one-time
+// validation check to help prevent people from going off the rails. if someone bypasses
+// this validation by removing the check or updating the value later, then caveat emptor.
+//
+// REF: https://github.com/dotnet/aspnet-api-versioning/issues/1011
+#pragma warning disable CA1812 // Avoid uninstantiated internal classes
+internal sealed class ValidateApiVersioningOptions : IValidateOptions
+#pragma warning restore CA1812 // Avoid uninstantiated internal classes
+{
+ public ValidateOptionsResult Validate( string? name, ApiVersioningOptions options )
+ {
+ if ( name is not null && name != Options.DefaultName )
+ {
+ return ValidateOptionsResult.Skip;
+ }
+
+ if ( options.DefaultApiVersion == ApiVersion.Neutral )
+ {
+ var message = string.Format(
+ CultureInfo.CurrentCulture,
+ Format.InvalidDefaultApiVersion,
+ nameof( ApiVersion ),
+ nameof( ApiVersion.Neutral ),
+ nameof( ApiVersioningOptions ),
+ nameof( ApiVersioningOptions.DefaultApiVersion ),
+ nameof( IApiVersionNeutral ) );
+ return ValidateOptionsResult.Fail( message );
+ }
+
+ return ValidateOptionsResult.Success;
+ }
+}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiDescriptionExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiDescriptionExtensions.cs
index ce48b2a1..30aaf0cd 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiDescriptionExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiDescriptionExtensions.cs
@@ -37,10 +37,7 @@ public static class ApiDescriptionExtensions
/// True if the API description is deprecated; otherwise, false .
public static bool IsDeprecated( this ApiDescription apiDescription )
{
- if ( apiDescription == null )
- {
- throw new ArgumentNullException( nameof( apiDescription ) );
- }
+ ArgumentNullException.ThrowIfNull( apiDescription );
var metatadata = apiDescription.ActionDescriptor.GetApiVersionMetadata();
@@ -81,15 +78,8 @@ public static bool IsDeprecated( this ApiDescription apiDescription )
/// True if the API description was updated; otherwise, false.
public static bool TryUpdateRelativePathAndRemoveApiVersionParameter( this ApiDescription apiDescription, ApiExplorerOptions options )
{
- if ( apiDescription == null )
- {
- throw new ArgumentNullException( nameof( apiDescription ) );
- }
-
- if ( options == null )
- {
- throw new ArgumentNullException( nameof( options ) );
- }
+ ArgumentNullException.ThrowIfNull( apiDescription );
+ ArgumentNullException.ThrowIfNull( options );
if ( !options.SubstituteApiVersionInUrl )
{
@@ -116,9 +106,14 @@ public static bool TryUpdateRelativePathAndRemoveApiVersionParameter( this ApiDe
return false;
}
- var token = '{' + parameter.Name + '}';
+ Span token = stackalloc char[parameter.Name.Length + 2];
+
+ token[0] = '{';
+ token[^1] = '}';
+ parameter.Name.AsSpan().CopyTo( token.Slice( 1, parameter.Name.Length ) );
+
var value = apiVersion.ToString( options.SubstitutionFormat, CultureInfo.InvariantCulture );
- var newRelativePath = relativePath.Replace( token, value, StringComparison.Ordinal );
+ var newRelativePath = relativePath.Replace( token.ToString(), value, StringComparison.Ordinal );
if ( relativePath == newRelativePath )
{
@@ -137,10 +132,7 @@ public static bool TryUpdateRelativePathAndRemoveApiVersionParameter( this ApiDe
/// A new API description .
public static ApiDescription Clone( this ApiDescription apiDescription )
{
- if ( apiDescription == null )
- {
- throw new ArgumentNullException( nameof( apiDescription ) );
- }
+ ArgumentNullException.ThrowIfNull( apiDescription );
var clone = new ApiDescription()
{
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptions.cs
index 0e0aef50..b366513a 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptions.cs
@@ -3,6 +3,7 @@
namespace Asp.Versioning.ApiExplorer;
using Asp.Versioning.Routing;
+using Microsoft.AspNetCore.Http;
///
/// Provides additional implementation specific to ASP.NET Core.
@@ -37,7 +38,7 @@ public ApiVersion DefaultApiVersion
/// The API version parameter source used to describe API version parameters.
public IApiVersionParameterSource ApiVersionParameterSource
{
- get => parameterSource ??= ApiVersionReader.Combine( new QueryStringApiVersionReader(), new UrlSegmentApiVersionReader() );
+ get => parameterSource ??= ApiVersionReader.Default;
set => parameterSource = value;
}
@@ -47,6 +48,17 @@ public IApiVersionParameterSource ApiVersionParameterSource
/// The name associated with the API version route constraint .
public string RouteConstraintName { get; set; } = string.Empty;
+ ///
+ /// Gets or sets the API version selector.
+ ///
+ /// An API version selector object.
+ [CLSCompliant( false )]
+ public IApiVersionSelector ApiVersionSelector
+ {
+ get => apiVersionSelector ??= new DefaultApiVersionSelector( this );
+ set => apiVersionSelector = value;
+ }
+
///
/// Gets or sets the function used to format the combination of a group name and API version.
///
@@ -55,4 +67,13 @@ public IApiVersionParameterSource ApiVersionParameterSource
/// The specified callback will only be invoked if a group name has been configured. The API
/// version will be provided formatted according to the group name format .
public FormatGroupNameCallback? FormatGroupName { get; set; }
+
+ private sealed class DefaultApiVersionSelector : IApiVersionSelector
+ {
+ private readonly ApiExplorerOptions options;
+
+ public DefaultApiVersionSelector( ApiExplorerOptions options ) => this.options = options;
+
+ public ApiVersion SelectVersion( HttpRequest request, ApiVersionModel model ) => options.DefaultApiVersion;
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptionsFactory{T}.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptionsFactory{T}.cs
index b2d866c9..1a79e0a3 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptionsFactory{T}.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiExplorerOptionsFactory{T}.cs
@@ -55,14 +55,25 @@ public ApiExplorerOptionsFactory(
///
protected override T CreateInstance( string name )
{
- var apiVersioningOptions = Options;
var options = base.CreateInstance( name );
+ CopyOptions( Options, options );
+ return options;
+ }
- options.AssumeDefaultVersionWhenUnspecified = apiVersioningOptions.AssumeDefaultVersionWhenUnspecified;
- options.ApiVersionParameterSource = apiVersioningOptions.ApiVersionReader;
- options.DefaultApiVersion = apiVersioningOptions.DefaultApiVersion;
- options.RouteConstraintName = apiVersioningOptions.RouteConstraintName;
+ ///
+ /// Copies the following source options to the target options.
+ ///
+ /// The source options.
+ /// The target options.
+ protected static void CopyOptions( ApiVersioningOptions sourceOptions, T targetOptions )
+ {
+ ArgumentNullException.ThrowIfNull( targetOptions, nameof( targetOptions ) );
+ ArgumentNullException.ThrowIfNull( sourceOptions, nameof( sourceOptions ) );
- return options;
+ targetOptions.AssumeDefaultVersionWhenUnspecified = sourceOptions.AssumeDefaultVersionWhenUnspecified;
+ targetOptions.ApiVersionParameterSource = sourceOptions.ApiVersionReader;
+ targetOptions.DefaultApiVersion = sourceOptions.DefaultApiVersion;
+ targetOptions.RouteConstraintName = sourceOptions.RouteConstraintName;
+ targetOptions.ApiVersionSelector = sourceOptions.ApiVersionSelector;
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs
index 4643fe39..cec78062 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionDescriptionProviderFactory.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
+#pragma warning disable CA1812 // Avoid uninstantiated internal classes
+
namespace Microsoft.AspNetCore.Builder;
using Asp.Versioning;
@@ -11,18 +13,18 @@ internal sealed class ApiVersionDescriptionProviderFactory : IApiVersionDescript
{
private readonly ISunsetPolicyManager sunsetPolicyManager;
private readonly IApiVersionMetadataCollationProvider[] providers;
+ private readonly IEndpointInspector endpointInspector;
private readonly IOptions options;
- private readonly Func, ISunsetPolicyManager, IOptions, IApiVersionDescriptionProvider> activator;
public ApiVersionDescriptionProviderFactory(
- Func, ISunsetPolicyManager, IOptions, IApiVersionDescriptionProvider> activator,
ISunsetPolicyManager sunsetPolicyManager,
IEnumerable providers,
+ IEndpointInspector endpointInspector,
IOptions options )
{
- this.activator = activator;
this.sunsetPolicyManager = sunsetPolicyManager;
this.providers = providers.ToArray();
+ this.endpointInspector = endpointInspector;
this.options = options;
}
@@ -30,11 +32,11 @@ public IApiVersionDescriptionProvider Create( EndpointDataSource endpointDataSou
{
var collators = new List( capacity: providers.Length + 1 )
{
- new EndpointApiVersionMetadataCollationProvider( endpointDataSource ),
+ new EndpointApiVersionMetadataCollationProvider( endpointDataSource, endpointInspector ),
};
collators.AddRange( providers );
- return activator( collators, sunsetPolicyManager, options );
+ return new DefaultApiVersionDescriptionProvider( collators, sunsetPolicyManager, options );
}
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionModelMetadata.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionModelMetadata.cs
index 44b08cd4..bdd9020f 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionModelMetadata.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionModelMetadata.cs
@@ -23,11 +23,7 @@ public sealed class ApiVersionModelMetadata : ModelMetadata
public ApiVersionModelMetadata( IModelMetadataProvider modelMetadataProvider, string description )
: base( ModelMetadataIdentity.ForType( typeof( string ) ) )
{
- if ( modelMetadataProvider == null )
- {
- throw new ArgumentNullException( nameof( modelMetadataProvider ) );
- }
-
+ ArgumentNullException.ThrowIfNull( modelMetadataProvider );
inner = modelMetadataProvider.GetMetadataForType( typeof( string ) );
this.description = description;
}
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionParameterDescriptionContext.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionParameterDescriptionContext.cs
index c95bc1fc..da70b954 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionParameterDescriptionContext.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/ApiVersionParameterDescriptionContext.cs
@@ -9,6 +9,7 @@ namespace Asp.Versioning.ApiExplorer;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template;
+using System.Runtime.CompilerServices;
using static Asp.Versioning.ApiVersionParameterLocation;
using static System.Linq.Enumerable;
using static System.StringComparison;
@@ -41,7 +42,7 @@ public ApiVersionParameterDescriptionContext(
ApiDescription = apiDescription ?? throw new ArgumentNullException( nameof( apiDescription ) );
ApiVersion = apiVersion ?? throw new ArgumentNullException( nameof( apiVersion ) );
ModelMetadata = modelMetadata ?? throw new ArgumentNullException( nameof( modelMetadata ) );
- optional = options.AssumeDefaultVersionWhenUnspecified && apiVersion == options.DefaultApiVersion;
+ optional = FirstParameterIsOptional( apiDescription, apiVersion, options );
}
// intentionally an internal property so the public contract doesn't change. this will be removed
@@ -304,7 +305,7 @@ routeInfo.Constraints is IEnumerable constraints &&
continue;
}
- var token = $"{parameter.Name}:{constraintName}";
+ var token = FormatToken( parameter.Name, constraintName );
parameterDescription.Name = parameter.Name;
description.RelativePath = relativePath.Replace( token, parameter.Name, Ordinal );
@@ -375,7 +376,7 @@ routeInfo.Constraints is IEnumerable constraints &&
},
Source = BindingSource.Path,
};
- var token = $"{parameter.Name}:{constraintName}";
+ var token = FormatToken( parameter.Name!, constraintName! );
description.RelativePath = relativePath.Replace( token, parameter.Name, Ordinal );
description.ParameterDescriptions.Insert( 0, result );
@@ -440,4 +441,35 @@ private void RemoveAllParametersExcept( ApiParameterDescription parameter )
}
}
}
+
+ private static bool FirstParameterIsOptional(
+ ApiDescription apiDescription,
+ ApiVersion apiVersion,
+ ApiExplorerOptions options )
+ {
+ if ( !options.AssumeDefaultVersionWhenUnspecified )
+ {
+ return false;
+ }
+
+ var mapping = ApiVersionMapping.Explicit | ApiVersionMapping.Implicit;
+ var model = apiDescription.ActionDescriptor.GetApiVersionMetadata().Map( mapping );
+ var defaultApiVersion = options.ApiVersionSelector.SelectVersion( model );
+
+ return apiVersion == defaultApiVersion;
+ }
+
+ [MethodImpl( MethodImplOptions.AggressiveInlining )]
+ private static string FormatToken( ReadOnlySpan parameterName, ReadOnlySpan constraintName )
+ {
+ var left = parameterName.Length;
+ var right = constraintName.Length;
+ Span token = stackalloc char[left + right + 1];
+
+ parameterName.CopyTo( token[..left] );
+ token[left] = ':';
+ constraintName.CopyTo( token.Slice( left + 1, right ) );
+
+ return token.ToString();
+ }
}
\ No newline at end of file
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Asp.Versioning.Mvc.ApiExplorer.csproj b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Asp.Versioning.Mvc.ApiExplorer.csproj
index ca73aefc..528a203a 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Asp.Versioning.Mvc.ApiExplorer.csproj
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/Asp.Versioning.Mvc.ApiExplorer.csproj
@@ -1,9 +1,9 @@
- 7.0.0
- 7.0.0.0
- net7.0
+ 8.1.0
+ 8.1.0.0
+ $(DefaultTargetFramework)
Asp.Versioning.ApiExplorer
ASP.NET Core API Versioning API Explorer
The API Explorer extensions for ASP.NET Core API Versioning.
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs
index 8730244c..22151e11 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DefaultApiVersionDescriptionProvider.cs
@@ -2,9 +2,8 @@
namespace Asp.Versioning.ApiExplorer;
+using Asp.Versioning.ApiExplorer.Internal;
using Microsoft.Extensions.Options;
-using static Asp.Versioning.ApiVersionMapping;
-using static System.Globalization.CultureInfo;
///
/// Represents the default implementation of an object that discovers and describes the API version information within an application.
@@ -12,7 +11,7 @@ namespace Asp.Versioning.ApiExplorer;
[CLSCompliant( false )]
public class DefaultApiVersionDescriptionProvider : IApiVersionDescriptionProvider
{
- private readonly ApiVersionDescriptionCollection collection;
+ private readonly ApiVersionDescriptionCollection collection;
private readonly IOptions options;
///
@@ -28,7 +27,7 @@ public DefaultApiVersionDescriptionProvider(
ISunsetPolicyManager sunsetPolicyManager,
IOptions apiExplorerOptions )
{
- collection = new( this, providers ?? throw new ArgumentNullException( nameof( providers ) ) );
+ collection = new( Describe, providers ?? throw new ArgumentNullException( nameof( providers ) ) );
SunsetPolicyManager = sunsetPolicyManager;
options = apiExplorerOptions;
}
@@ -56,144 +55,55 @@ public DefaultApiVersionDescriptionProvider(
/// A read-only list of API version descriptions .
protected virtual IReadOnlyList Describe( IReadOnlyList metadata )
{
- if ( metadata == null )
+ ArgumentNullException.ThrowIfNull( metadata );
+
+ // TODO: consider refactoring and removing GroupedApiVersionDescriptionProvider as both implementations are now
+ // effectively the same. this cast is safe as an internal implementation detail. if this method is
+ // overridden, then this code doesn't even run
+ //
+ // REF: https://github.com/dotnet/aspnet-api-versioning/issues/1066
+ if ( metadata is GroupedApiVersionMetadata[] groupedMetadata )
{
- throw new ArgumentNullException( nameof( metadata ) );
+ return DescriptionProvider.Describe( groupedMetadata, SunsetPolicyManager, Options );
}
- var descriptions = new List( capacity: metadata.Count );
- var supported = new HashSet();
- var deprecated = new HashSet();
-
- BucketizeApiVersions( metadata, supported, deprecated );
- AppendDescriptions( descriptions, supported, deprecated: false );
- AppendDescriptions( descriptions, deprecated, deprecated: true );
-
- return descriptions.OrderBy( d => d.ApiVersion ).ToArray();
- }
-
- private void BucketizeApiVersions( IReadOnlyList metadata, ISet supported, ISet deprecated )
- {
- var declared = new HashSet();
- var advertisedSupported = new HashSet();
- var advertisedDeprecated = new HashSet();
-
- for ( var i = 0; i < metadata.Count; i++ )
- {
- var model = metadata[i].Map( Explicit | Implicit );
- var versions = model.DeclaredApiVersions;
-
- for ( var j = 0; j < versions.Count; j++ )
- {
- declared.Add( versions[j] );
- }
-
- versions = model.SupportedApiVersions;
-
- for ( var j = 0; j < versions.Count; j++ )
- {
- var version = versions[j];
- supported.Add( version );
- advertisedSupported.Add( version );
- }
-
- versions = model.DeprecatedApiVersions;
-
- for ( var j = 0; j < versions.Count; j++ )
- {
- var version = versions[j];
- deprecated.Add( version );
- advertisedDeprecated.Add( version );
- }
- }
-
- advertisedSupported.ExceptWith( declared );
- advertisedDeprecated.ExceptWith( declared );
- supported.ExceptWith( advertisedSupported );
- deprecated.ExceptWith( supported.Concat( advertisedDeprecated ) );
-
- if ( supported.Count == 0 && deprecated.Count == 0 )
- {
- supported.Add( Options.DefaultApiVersion );
- }
- }
-
- private void AppendDescriptions( ICollection descriptions, IEnumerable versions, bool deprecated )
- {
- foreach ( var version in versions )
- {
- var groupName = version.ToString( Options.GroupNameFormat, CurrentCulture );
- var sunsetPolicy = SunsetPolicyManager.TryGetPolicy( version, out var policy ) ? policy : default;
- descriptions.Add( new( version, groupName, deprecated, sunsetPolicy ) );
- }
+ return Array.Empty();
}
- private sealed class ApiVersionDescriptionCollection
+ private sealed class GroupedApiVersionMetadata :
+ ApiVersionMetadata,
+ IEquatable,
+ IGroupedApiVersionMetadata,
+ IGroupedApiVersionMetadataFactory
{
- private readonly object syncRoot = new();
- private readonly DefaultApiVersionDescriptionProvider provider;
- private readonly IApiVersionMetadataCollationProvider[] collators;
- private IReadOnlyList? items;
- private int version;
-
- public ApiVersionDescriptionCollection(
- DefaultApiVersionDescriptionProvider provider,
- IEnumerable collators )
- {
- this.provider = provider;
- this.collators = collators.ToArray();
- }
+ private GroupedApiVersionMetadata( string? groupName, ApiVersionMetadata metadata )
+ : base( metadata ) => GroupName = groupName;
- public IReadOnlyList Items
- {
- get
- {
- if ( items is not null && version == ComputeVersion() )
- {
- return items;
- }
-
- lock ( syncRoot )
- {
- var currentVersion = ComputeVersion();
-
- if ( items is not null && version == currentVersion )
- {
- return items;
- }
-
- var context = new ApiVersionMetadataCollationContext();
+ public string? GroupName { get; }
- for ( var i = 0; i < collators.Length; i++ )
- {
- collators[i].Execute( context );
- }
+ static GroupedApiVersionMetadata IGroupedApiVersionMetadataFactory.New(
+ string? groupName,
+ ApiVersionMetadata metadata ) => new( groupName, metadata );
- items = provider.Describe( context.Results );
- version = currentVersion;
- }
+ public bool Equals( GroupedApiVersionMetadata? other ) =>
+ other is not null && other.GetHashCode() == GetHashCode();
- return items;
- }
- }
+ public override bool Equals( object? obj ) =>
+ obj is not null &&
+ GetType().Equals( obj.GetType() ) &&
+ GetHashCode() == obj.GetHashCode();
- private int ComputeVersion() =>
- collators.Length switch
- {
- 0 => 0,
- 1 => collators[0].Version,
- _ => ComputeVersion( collators ),
- };
-
- private static int ComputeVersion( IApiVersionMetadataCollationProvider[] providers )
+ public override int GetHashCode()
{
var hash = default( HashCode );
- for ( var i = 0; i < providers.Length; i++ )
+ if ( !string.IsNullOrEmpty( GroupName ) )
{
- hash.Add( providers[i].Version );
+ hash.Add( GroupName, StringComparer.Ordinal );
}
+ hash.Add( base.GetHashCode() );
+
return hash.ToHashCode();
}
}
diff --git a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
index f806121d..c1a689fe 100644
--- a/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
+++ b/src/AspNetCore/WebApi/src/Asp.Versioning.Mvc.ApiExplorer/DependencyInjection/IApiVersioningBuilderExtensions.cs
@@ -25,11 +25,7 @@ public static class IApiVersioningBuilderExtensions
/// The original .
public static IApiVersioningBuilder AddApiExplorer( this IApiVersioningBuilder builder )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
AddApiExplorerServices( builder );
return builder;
}
@@ -42,14 +38,9 @@ public static IApiVersioningBuilder AddApiExplorer( this IApiVersioningBuilder b
/// The original .
public static IApiVersioningBuilder AddApiExplorer( this IApiVersioningBuilder builder, Action setupAction )
{
- if ( builder == null )
- {
- throw new ArgumentNullException( nameof( builder ) );
- }
-
+ ArgumentNullException.ThrowIfNull( builder );
AddApiExplorerServices( builder );
builder.Services.Configure( setupAction );
-
return builder;
}
@@ -61,58 +52,17 @@ private static void AddApiExplorerServices( IApiVersioningBuilder builder )
services.AddMvcCore().AddApiExplorer();
services.TryAddSingleton, ApiExplorerOptionsFactory>();
- services.TryAddTransient( ResolveApiVersionDescriptionProviderFactory );
- services.TryAddSingleton( ResolveApiVersionDescriptionProvider );
+ services.TryAddTransient();
+ services.TryAddSingleton( static sp => sp.GetRequiredService().Create() );
// use internal constructor until ASP.NET Core fixes their bug
// BUG: https://github.com/dotnet/aspnetcore/issues/41773
services.TryAddEnumerable(
Transient(
- sp => new(
+ static sp => new(
sp.GetRequiredService(),
sp.GetRequiredService(),
sp.GetRequiredService(),
sp.GetRequiredService>() ) ) );
}
-
- private static IApiVersionDescriptionProviderFactory ResolveApiVersionDescriptionProviderFactory( IServiceProvider serviceProvider )
- {
- var sunsetPolicyManager = serviceProvider.GetRequiredService();
- var providers = serviceProvider.GetServices();
- var options = serviceProvider.GetRequiredService>();
- var mightUseCustomGroups = options.Value.FormatGroupName is not null;
-
- return new ApiVersionDescriptionProviderFactory(
- mightUseCustomGroups ? NewGroupedProvider : NewDefaultProvider,
- sunsetPolicyManager,
- providers,
- options );
-
- static IApiVersionDescriptionProvider NewDefaultProvider(
- IEnumerable providers,
- ISunsetPolicyManager sunsetPolicyManager,
- IOptions apiExplorerOptions ) =>
- new DefaultApiVersionDescriptionProvider( providers, sunsetPolicyManager, apiExplorerOptions );
-
- static IApiVersionDescriptionProvider NewGroupedProvider(
- IEnumerable providers,
- ISunsetPolicyManager sunsetPolicyManager,
- IOptions apiExplorerOptions ) =>
- new GroupedApiVersionDescriptionProvider( providers, sunsetPolicyManager, apiExplorerOptions );
- }
-
- private static IApiVersionDescriptionProvider ResolveApiVersionDescriptionProvider( IServiceProvider serviceProvider )
- {
- var providers = serviceProvider.GetServices();
- var sunsetPolicyManager = serviceProvider.GetRequiredService